mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-13 01:48:42 +00:00
Compare commits
2 commits
e48f3674c7
...
ed5f10eb7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed5f10eb7d | ||
|
|
a01a34b7f2 |
7 changed files with 481 additions and 40 deletions
106
docs/usage/commands/lsblk.md
Normal file
106
docs/usage/commands/lsblk.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# lsblk
|
||||
|
||||
`lsblk` lists the block devices detected by BoredOS, including whole disks and their partitions.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
lsblk
|
||||
lsblk /dev/sda
|
||||
lsblk -r
|
||||
lsblk --json
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
By default, `lsblk` prints a compact tree view:
|
||||
|
||||
```text
|
||||
/dev/sda 2 GB disk
|
||||
└─ sda1 2 GB part FAT32 BOREDOS
|
||||
```
|
||||
|
||||
Fields shown by the default output:
|
||||
|
||||
- device name, such as `/dev/sda` or `sda1`
|
||||
- human-readable size, such as `512 MB` or `2 GB`
|
||||
- device type, either `disk` or `part`
|
||||
- filesystem type, currently `FAT32` when detected
|
||||
- volume label when available
|
||||
- `[ESP]` flag for EFI System Partitions
|
||||
|
||||
> [!NOTE]
|
||||
> Mount points are not shown yet because BoredOS does not currently expose mountpoint information through the disk info syscall.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| :--- | :--- |
|
||||
| `-r` | Print raw output without tree characters. |
|
||||
| `--json` | Print machine-readable JSON output. |
|
||||
| `/dev/DEVICE` | Show only one disk or partition. |
|
||||
|
||||
## Examples
|
||||
|
||||
List all block devices:
|
||||
|
||||
```sh
|
||||
lsblk
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```text
|
||||
/dev/sda 2 GB disk
|
||||
└─ sda1 2 GB part FAT32 BOREDOS
|
||||
/dev/sdb 16 GB disk
|
||||
```
|
||||
|
||||
Show one disk and its partitions:
|
||||
|
||||
```sh
|
||||
lsblk /dev/sda
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```text
|
||||
/dev/sda 2 GB disk
|
||||
└─ sda1 2 GB part FAT32 BOREDOS
|
||||
```
|
||||
|
||||
Print raw output for scripts:
|
||||
|
||||
```sh
|
||||
lsblk -r
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```text
|
||||
/dev/sda 2GB disk
|
||||
/dev/sda1 2GB part FAT32 BOREDOS
|
||||
```
|
||||
|
||||
Print JSON output:
|
||||
|
||||
```sh
|
||||
lsblk --json
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```json
|
||||
{"devices":[{"name":"/dev/sda","size":"2 GB","type":"disk","fstype":"","label":"","flags":[],"children":[{"name":"/dev/sda1","size":"2 GB","type":"part","fstype":"FAT32","label":"BOREDOS","flags":[]}]}]}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
`lsblk` reads disk metadata through the disk syscalls exposed by BoredOS:
|
||||
|
||||
- `sys_disk_get_count()` gets the number of registered block devices.
|
||||
- `sys_disk_get_info()` reads each device's name, size, type, FAT32 status, label, and flags.
|
||||
|
||||
The command treats non-partition entries as parent disks, then groups partition entries under the matching disk name. For example, `sda1` is displayed under `/dev/sda`.
|
||||
|
||||
Sizes are calculated from sector counts using 512-byte sectors, then formatted as `KB`, `MB`, or `GB`.
|
||||
|
|
@ -53,6 +53,7 @@ Below are some of the most used commands available in `/bin`:
|
|||
| `rm` | Remove a file. |
|
||||
| `mkdir` | Create a new directory. |
|
||||
| `man` | View the manual for a specific command (e.g., `man ls`). |
|
||||
| `lsblk` | List block devices and partitions with size, type, filesystem, label, and flags. |
|
||||
| `sysfetch` | Display system and hardware information. |
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,32 @@ static int dm_strlen(const char *s) {
|
|||
return n;
|
||||
}
|
||||
|
||||
static void dm_copy_fat_label(char *dst, const uint8_t *src) {
|
||||
int end = 11;
|
||||
while (end > 0 && src[end - 1] == ' ') end--;
|
||||
for (int i = 0; i < end && i < 31; i++) dst[i] = (char)src[i];
|
||||
dst[end < 31 ? end : 31] = 0;
|
||||
}
|
||||
|
||||
static void disk_load_fat32_label(Disk *disk) {
|
||||
uint8_t *buffer;
|
||||
FAT32_BootSector *bpb;
|
||||
char label[32];
|
||||
|
||||
if (!disk || !disk->read_sector) return;
|
||||
|
||||
buffer = (uint8_t*)kmalloc(512);
|
||||
if (!buffer) return;
|
||||
|
||||
if (disk->read_sector(disk, 0, buffer) == 0 && buffer[510] == 0x55 && buffer[511] == 0xAA) {
|
||||
bpb = (FAT32_BootSector*)buffer;
|
||||
dm_copy_fat_label(label, bpb->volume_label);
|
||||
if (label[0]) dm_strcpy(disk->label, label);
|
||||
}
|
||||
|
||||
kfree(buffer);
|
||||
}
|
||||
|
||||
// === ATA Definitions (Legacy IDE PIO — kept as fallback) ===
|
||||
|
||||
#define ATA_PRIMARY_IO 0x1F0
|
||||
|
|
@ -387,6 +413,8 @@ void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_
|
|||
part->is_partition = true;
|
||||
part->registered = true;
|
||||
|
||||
if (is_fat32) disk_load_fat32_label(part);
|
||||
|
||||
disks[disk_count++] = part;
|
||||
|
||||
serial_write("[DISK] Registered /dev/");
|
||||
|
|
@ -567,6 +595,7 @@ static void parse_mbr_partitions(Disk *disk) {
|
|||
serial_write("\n");
|
||||
disk->is_fat32 = true;
|
||||
disk->partition_lba_offset = 0;
|
||||
disk_load_fat32_label(disk);
|
||||
} else if (part_count == 0) {
|
||||
serial_write("[DISK] No MBR partitions found on /dev/");
|
||||
serial_write(disk->devname);
|
||||
|
|
@ -1040,4 +1069,4 @@ int disk_rescan(Disk *disk) {
|
|||
|
||||
parse_mbr_partitions(disk);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ static void mf_strncpy(char *dst, const char *src, int n) {
|
|||
while (i < n) { dst[i++] = ' '; } /* FAT labels are space-padded */
|
||||
}
|
||||
|
||||
static void mf_set_disk_label(Disk *disk, const char *label) {
|
||||
int end = 11;
|
||||
while (end > 0 && label[end - 1] == ' ') end--;
|
||||
for (int i = 0; i < end && i < 31; i++) disk->label[i] = label[i];
|
||||
disk->label[end < 31 ? end : 31] = 0;
|
||||
}
|
||||
|
||||
// On-disk BPB structures
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
|
|
@ -269,13 +276,15 @@ int mkfs_fat32_format(Disk *disk, uint32_t sector_count, const char *label) {
|
|||
|
||||
kfree(buf);
|
||||
|
||||
disk->is_fat32 = true;
|
||||
mf_set_disk_label(disk, upper_label);
|
||||
|
||||
serial_write("[MKFS] FAT32 formatted: ");
|
||||
serial_write(disk->devname);
|
||||
serial_write(" label=");
|
||||
char lb[12];
|
||||
mf_memcpy(lb, vol_label, 11);
|
||||
mf_memcpy(lb, upper_label, 11);
|
||||
lb[11] = 0;
|
||||
for (int i = 0; i < 11; i++) lb[i] = (lb[i] >= 'a' && lb[i] <= 'z') ? lb[i] - 32 : lb[i];
|
||||
for (int i = 10; i >= 0 && lb[i] == ' '; i--) lb[i] = 0;
|
||||
serial_write(lb);
|
||||
serial_write(" spc=");
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ int main(int argc, char **argv) {
|
|||
printf("date - Print current date and time\n");
|
||||
printf("uptime - Print system uptime\n");
|
||||
printf("meminfo - Print memory information\n");
|
||||
printf("lsblk - List block devices and partitions\n");
|
||||
printf("cowsay [msg] - Fun cow says something\n");
|
||||
printf("beep - Make a beep sound\n");
|
||||
printf("reboot - Reboot the system\n");
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
// BOREDOS_APP_DESC: Manual pages CLI utility
|
||||
#include <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
printf("What manual page do you want?\nExample: man ls\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[128];
|
||||
printf("Manual for: %s\n", argv[1]);
|
||||
printf("---------------------------\n");
|
||||
|
||||
strcpy(path, "/Library/man/");
|
||||
strcat(path, argv[1]);
|
||||
strcat(path, ".txt");
|
||||
|
||||
int fd = sys_open(path, "r");
|
||||
if (fd < 0) {
|
||||
printf("No manual entry for %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char buffer[4096];
|
||||
int bytes;
|
||||
while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) {
|
||||
sys_write(1, buffer, bytes);
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
332
src/userland/sys/lsblk.c
Normal file
332
src/userland/sys/lsblk.c
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <syscall.h>
|
||||
|
||||
#define LSBLK_MAX_DISKS 32
|
||||
#define LSBLK_SECTOR_SIZE 512ULL
|
||||
#define LSBLK_KB 1024ULL
|
||||
#define LSBLK_MB (1024ULL * 1024ULL)
|
||||
#define LSBLK_GB (1024ULL * 1024ULL * 1024ULL)
|
||||
|
||||
static int streq(const char *a, const char *b) {
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
static int starts_with(const char *s, const char *prefix) {
|
||||
while (*prefix) {
|
||||
if (*s++ != *prefix++) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *display_label(const disk_info_t *d) {
|
||||
if (!d->label[0]) return "";
|
||||
if (streq(d->label, "Unknown Partition")) return "";
|
||||
if (streq(d->label, "FAT32 Partition")) return "";
|
||||
if (streq(d->label, "EFI System Partition")) return "EFI";
|
||||
return d->label;
|
||||
}
|
||||
|
||||
static const char *device_name_arg(const char *arg) {
|
||||
if (starts_with(arg, "/dev/")) return arg + 5;
|
||||
return arg;
|
||||
}
|
||||
|
||||
static void format_size(uint64_t bytes, char *out, size_t out_len, int compact) {
|
||||
const char *sep = compact ? "" : " ";
|
||||
uint64_t unit = 1;
|
||||
const char *suffix = "B";
|
||||
|
||||
if (bytes >= LSBLK_GB) {
|
||||
unit = LSBLK_GB;
|
||||
suffix = "GB";
|
||||
} else if (bytes >= LSBLK_MB) {
|
||||
unit = LSBLK_MB;
|
||||
suffix = "MB";
|
||||
} else if (bytes >= LSBLK_KB) {
|
||||
unit = LSBLK_KB;
|
||||
suffix = "KB";
|
||||
}
|
||||
|
||||
if (unit == 1) {
|
||||
snprintf(out, out_len, "%llu%s%s", (unsigned long long)bytes, sep, suffix);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t whole = bytes / unit;
|
||||
uint64_t rem = bytes % unit;
|
||||
uint64_t tenth = (rem * 10ULL + unit / 2ULL) / unit;
|
||||
|
||||
if (tenth >= 10ULL) {
|
||||
whole++;
|
||||
tenth = 0;
|
||||
}
|
||||
|
||||
if (tenth == 0) {
|
||||
snprintf(out, out_len, "%llu%s%s", (unsigned long long)whole, sep, suffix);
|
||||
} else {
|
||||
snprintf(out, out_len, "%llu.%llu%s%s", (unsigned long long)whole, (unsigned long long)tenth, sep, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t disk_size_bytes(const disk_info_t *d) {
|
||||
return (uint64_t)d->total_sectors * LSBLK_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
static int is_child_partition(const disk_info_t *disk, const disk_info_t *part) {
|
||||
size_t len;
|
||||
|
||||
if (disk->is_partition || !part->is_partition) return 0;
|
||||
|
||||
len = strlen(disk->devname);
|
||||
if (strncmp(part->devname, disk->devname, len) != 0) return 0;
|
||||
|
||||
return part->devname[len] >= '0' && part->devname[len] <= '9';
|
||||
}
|
||||
|
||||
static int child_count(const disk_info_t *disk, disk_info_t *items, int count) {
|
||||
int children = 0;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (is_child_partition(disk, &items[i])) children++;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
static void print_tree_device(const disk_info_t *d, const char *branch) {
|
||||
char size[24];
|
||||
const char *type = d->is_partition ? "part" : "disk";
|
||||
|
||||
format_size(disk_size_bytes(d), size, sizeof(size), 0);
|
||||
|
||||
if (d->is_partition) {
|
||||
const char *label = display_label(d);
|
||||
if (branch[0]) printf("%s %-8s %8s %s", branch, d->devname, size, type);
|
||||
else printf("/dev/%-8s %8s %s", d->devname, size, type);
|
||||
if (d->is_fat32) printf(" FAT32");
|
||||
if (label[0]) printf(" %s", label);
|
||||
if (d->is_esp) printf(" [ESP]");
|
||||
printf("\n");
|
||||
} else {
|
||||
printf("/dev/%-8s %8s %s\n", d->devname, size, type);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_tree_disk(const disk_info_t *disk, disk_info_t *items, int count) {
|
||||
int children = child_count(disk, items, count);
|
||||
int seen = 0;
|
||||
|
||||
print_tree_device(disk, "");
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!is_child_partition(disk, &items[i])) continue;
|
||||
seen++;
|
||||
print_tree_device(&items[i], seen == children ? "└─" : "├─");
|
||||
}
|
||||
}
|
||||
|
||||
static void print_raw_device(const disk_info_t *d) {
|
||||
char size[24];
|
||||
|
||||
format_size(disk_size_bytes(d), size, sizeof(size), 1);
|
||||
printf("/dev/%s %s %s", d->devname, size, d->is_partition ? "part" : "disk");
|
||||
|
||||
if (d->is_partition) {
|
||||
const char *label = display_label(d);
|
||||
if (d->is_fat32) printf(" FAT32");
|
||||
if (label[0]) printf(" %s", label);
|
||||
if (d->is_esp) printf(" ESP");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void print_raw_disk(const disk_info_t *disk, disk_info_t *items, int count) {
|
||||
print_raw_device(disk);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (is_child_partition(disk, &items[i])) print_raw_device(&items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void json_string(const char *s) {
|
||||
putchar('"');
|
||||
|
||||
while (*s) {
|
||||
if (*s == '"' || *s == '\\') {
|
||||
putchar('\\');
|
||||
putchar(*s);
|
||||
} else if (*s == '\n') {
|
||||
printf("\\n");
|
||||
} else {
|
||||
putchar(*s);
|
||||
}
|
||||
s++;
|
||||
}
|
||||
|
||||
putchar('"');
|
||||
}
|
||||
|
||||
static void json_device_fields(const disk_info_t *d) {
|
||||
char size[24];
|
||||
char name[24];
|
||||
|
||||
format_size(disk_size_bytes(d), size, sizeof(size), 0);
|
||||
snprintf(name, sizeof(name), "/dev/%s", d->devname);
|
||||
|
||||
printf("\"name\":");
|
||||
json_string(name);
|
||||
printf(",\"size\":");
|
||||
json_string(size);
|
||||
printf(",\"type\":");
|
||||
json_string(d->is_partition ? "part" : "disk");
|
||||
printf(",\"fstype\":");
|
||||
json_string(d->is_fat32 ? "FAT32" : "");
|
||||
printf(",\"label\":");
|
||||
json_string(display_label(d));
|
||||
printf(",\"flags\":[");
|
||||
if (d->is_esp) json_string("ESP");
|
||||
printf("]");
|
||||
}
|
||||
|
||||
static void print_json_partition(const disk_info_t *d) {
|
||||
printf("{");
|
||||
json_device_fields(d);
|
||||
printf("}");
|
||||
}
|
||||
|
||||
static void print_json_disk(const disk_info_t *disk, disk_info_t *items, int count) {
|
||||
int seen = 0;
|
||||
|
||||
printf("{");
|
||||
json_device_fields(disk);
|
||||
printf(",\"children\":[");
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!is_child_partition(disk, &items[i])) continue;
|
||||
if (seen > 0) printf(",");
|
||||
print_json_partition(&items[i]);
|
||||
seen++;
|
||||
}
|
||||
|
||||
printf("]}");
|
||||
}
|
||||
|
||||
static int load_disks(disk_info_t *items, int max) {
|
||||
int total = sys_disk_get_count();
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < total && count < max; i++) {
|
||||
if (sys_disk_get_info(i, &items[count]) == 0) count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void usage(void) {
|
||||
printf("Usage: lsblk [-r] [--json] [/dev/DEVICE]\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
disk_info_t items[LSBLK_MAX_DISKS];
|
||||
const char *filter = NULL;
|
||||
int raw = 0;
|
||||
int json = 0;
|
||||
int count;
|
||||
int printed = 0;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (streq(argv[i], "-r")) {
|
||||
raw = 1;
|
||||
} else if (streq(argv[i], "--json")) {
|
||||
json = 1;
|
||||
} else if (streq(argv[i], "-h") || streq(argv[i], "--help")) {
|
||||
usage();
|
||||
return 0;
|
||||
} else if (argv[i][0] == '-') {
|
||||
printf("lsblk: unknown option: %s\n", argv[i]);
|
||||
usage();
|
||||
return 1;
|
||||
} else if (!filter) {
|
||||
filter = device_name_arg(argv[i]);
|
||||
} else {
|
||||
printf("lsblk: only one device filter is supported\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (raw && json) {
|
||||
printf("lsblk: -r and --json cannot be used together\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
count = load_disks(items, LSBLK_MAX_DISKS);
|
||||
|
||||
if (json) {
|
||||
printf("{\"devices\":[");
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (items[i].is_partition) continue;
|
||||
if (filter && !streq(items[i].devname, filter)) continue;
|
||||
if (printed > 0) printf(",");
|
||||
print_json_disk(&items[i], items, count);
|
||||
printed++;
|
||||
}
|
||||
|
||||
if (filter && printed == 0) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!items[i].is_partition || !streq(items[i].devname, filter)) continue;
|
||||
print_json_partition(&items[i]);
|
||||
printed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("]}\n");
|
||||
} else if (raw) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (items[i].is_partition) continue;
|
||||
if (filter && !streq(items[i].devname, filter)) continue;
|
||||
print_raw_disk(&items[i], items, count);
|
||||
printed++;
|
||||
}
|
||||
|
||||
if (filter && printed == 0) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!items[i].is_partition || !streq(items[i].devname, filter)) continue;
|
||||
print_raw_device(&items[i]);
|
||||
printed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (items[i].is_partition) continue;
|
||||
if (filter && !streq(items[i].devname, filter)) continue;
|
||||
print_tree_disk(&items[i], items, count);
|
||||
printed++;
|
||||
}
|
||||
|
||||
if (filter && printed == 0) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!items[i].is_partition || !streq(items[i].devname, filter)) continue;
|
||||
print_tree_device(&items[i], "");
|
||||
printed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (printed == 0 && !json) {
|
||||
if (filter) printf("lsblk: /dev/%s not found\n", filter);
|
||||
else printf("lsblk: no block devices found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (printed == 0 && filter) return 1;
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue