pr: Add lslbk disk listing command (#24)
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run

* add lsblk disk listing command

* fix lsblk placeholder labels

* fix fat32 volume labels

* doc lsblk command

* add lsblk to help

* doc lsblk usage
This commit is contained in:
zeyad 2026-05-11 22:59:31 +06:00 committed by GitHub
parent a01a34b7f2
commit ed5f10eb7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 481 additions and 3 deletions

View 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`.

View file

@ -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. |

View file

@ -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;
}
}

View file

@ -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=");

View file

@ -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");

332
src/userland/sys/lsblk.c Normal file
View 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;
}