diff --git a/docs/usage/commands/lsblk.md b/docs/usage/commands/lsblk.md new file mode 100644 index 0000000..b2a2c39 --- /dev/null +++ b/docs/usage/commands/lsblk.md @@ -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`. diff --git a/docs/usage/terminal.md b/docs/usage/terminal.md index 2a6b0a5..4b806c2 100644 --- a/docs/usage/terminal.md +++ b/docs/usage/terminal.md @@ -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. | diff --git a/src/dev/disk_manager.c b/src/dev/disk_manager.c index 8e8a485..3ec6e0a 100644 --- a/src/dev/disk_manager.c +++ b/src/dev/disk_manager.c @@ -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; -} \ No newline at end of file +} diff --git a/src/fs/mkfs_fat32.c b/src/fs/mkfs_fat32.c index 730de84..bcf44bb 100644 --- a/src/fs/mkfs_fat32.c +++ b/src/fs/mkfs_fat32.c @@ -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="); diff --git a/src/userland/cli/help.c b/src/userland/cli/help.c index ef37cf9..d5f7903 100644 --- a/src/userland/cli/help.c +++ b/src/userland/cli/help.c @@ -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"); diff --git a/src/userland/sys/lsblk.c b/src/userland/sys/lsblk.c new file mode 100644 index 0000000..55056a2 --- /dev/null +++ b/src/userland/sys/lsblk.c @@ -0,0 +1,332 @@ +#include +#include +#include +#include +#include +#include + +#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; +}