Commit Diff


commit - fa7b2359dac517846025752716dd71ecb8caa78e
commit + 9c5d8ecc060ea66ceddf6a38fb534a5d20b124c7
blob - 05d9c0c4cd4088d749a7af9815166c15385efaa7
blob + d85e3f0b3106dae1358bc50204994bee2e43b695
--- Makefile
+++ Makefile
@@ -2,7 +2,7 @@
 # See LICENSE file for copyright and license details.
 .POSIX:
 
-VERSION = 1.0
+VERSION = 1.1
 PREFIX = /usr/local
 MANPREFIX = ${PREFIX}/man
 MY_CFLAGS = -Wall -Wextra -Werror -pedantic -std=c99 -DVERSION=\"${VERSION}\" ${CFLAGS}
blob - cd9b7911c785a05bc3b250486b501508782734d5
blob + db64be1359770b9fbbefe55530c01f16881c728d
--- README.md
+++ README.md
@@ -7,3 +7,4 @@ This project aims to implement a clone of lsblk for Op
 
 ## TODO
 - [ ] Fancy unicodes: `├─` & `└─`
+- [ ] Where should "LABEL" go? After "NAME" or before "MOUNT"?
blob - 5f5517620940cc53d22dab6a586393a78b4a344a
blob + 49ef289a0665d75471d727bc2a2a1b7cc9da2edd
--- lsblk.8
+++ lsblk.8
@@ -15,7 +15,7 @@
 .SH NAME
 lsblk \- list block devices
 .SH SYNOPSIS
-.B lsblk [-Vn]
+.B lsblk [-Vlnu]
 .SH DESCRIPTION
 The
 .B lsblk
@@ -23,9 +23,21 @@ utility can be used to examine block devices attached 
 .SH OPTIONS
 .TP
 .B \-V
-prints version information to stdout, then exits.
+prints version information to stdout, then exit.
 .TP
+.B \-l
+also print the label (packname) of each disk.
+.TP
 .B \-n
 don't print the header.
+.TP
+.B \-u
+also print the filesystem usage of each partition
+and the disklabel usage of each disk.
+
+Note: The disklabel usage is calculated by summing the size of all (except "c") partitions.
+
+Note 2: There is some error in the filesystem usage, please consult df(1) for more correct numbers.
 .SH SEE ALSO
-.BR disklabel (8)
+.BR disklabel (8) ,
+.BR df (1)
blob - 5cdd6db7b9dda8c564aafc9dd261b220f9e4049c
blob + 7dca37ea4d6456dff08809004520e0dd4d1ae999
--- lsblk.c
+++ lsblk.c
@@ -17,6 +17,7 @@
 #define _BSD_SOURCE 1
 #define DKTYPENAMES
 #include <stddef.h>
+#include <sys/cdefs.h>
 #include <sys/types.h>
 #include <sys/disklabel.h>
 #include <sys/sysctl.h>
@@ -34,7 +35,7 @@
 #include <fcntl.h>
 #include <errno.h>
 
-static void die (const char *fmt, ...)
+static __dead void die (const char *fmt, ...)
 {
     va_list ap;
 
@@ -76,8 +77,21 @@ static char *disknames (void)
     return buffer;
 }
 
-static void printsize (uint64_t sz)
+static char *stripdisk (char *n)
 {
+    const char sufx[] = " disk";
+    const size_t ln = strnlen (n, 16);
+    const size_t ls = sizeof sufx - 1;
+
+    if (!memcmp (n + ln - ls, sufx, ls)) {
+        n[ln - ls] = '\0';
+    }
+
+    return n;
+}
+
+static void print_size (uint64_t sz)
+{
     struct unit {
         char sym;
         uint64_t factor;
@@ -114,31 +128,232 @@ static void printsize (uint64_t sz)
     }
 
     putchar (sym);
+    putchar (' ');
 }
 
-static int usage (void)
+enum {
+    FIELD_NAME  = 0x01,
+    FIELD_LABEL = 0x02,
+    FIELD_SIZE  = 0x04,
+    FIELD_USED  = 0x08,
+    FIELD_FREE  = 0x10,
+    FIELD_TYPE  = 0x20,
+    FIELD_MOUNT = 0x40,
+
+    FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_MOUNT,
+};
+
+struct my_partinfo {
+    char name[5];
+    uint64_t size;
+    uint64_t fssize;
+    uint64_t free;
+    const char *fstype;
+    const char *mount;
+};
+
+struct my_diskinfo {
+    char type[16];
+    char label[16];
+    char name[4];
+    uint64_t size;
+    uint64_t used;
+    const char *mount;
+    uint8_t num_parts;
+    struct my_partinfo parts[MAXPARTITIONS];
+};
+
+static void print_header (int fields)
 {
-    fputs ("Usage: lsblk [-Vn]\n", stderr);
-    return 1;
+    if (fields & FIELD_NAME)
+        printf ("%-6s ", "NAME");
+
+    if (fields & FIELD_LABEL)
+        printf ("%-16s ", "LABEL");
+
+    if (fields & FIELD_SIZE)
+        printf ("%-4s ", "SIZE");
+
+    if (fields & FIELD_USED)
+        printf ("%-4s ", "USED");
+
+    if (fields & FIELD_FREE)
+        printf ("%-4s ", "FREE");
+
+    if (fields & FIELD_TYPE)
+        printf ("%-8s ", "TYPE");
+
+    if (fields & FIELD_MOUNT)
+        printf ("MOUNT ");
+
+    putchar ('\n');
 }
 
-static char *stripdisk (char *n)
+static void print_part (const struct my_partinfo *part, int fields)
 {
-    const char sufx[] = " disk";
-    const size_t ln = strnlen (n, 16);
-    const size_t ls = sizeof sufx - 1;
+    if (fields & FIELD_NAME)
+        printf ("  %s ", part->name);
 
-    if (!memcmp (n + ln - ls, sufx, ls)) {
-        n[ln - ls] = '\0';
+    if (fields & FIELD_LABEL)
+        printf ("%16s ", "");
+
+    if (fields & FIELD_SIZE)
+        print_size (part->size);
+
+    if (fields & FIELD_USED) {
+        if (part->fssize) {
+            print_size (part->fssize - part->free);
+        } else {
+            printf (" N/A ");
+        }
     }
 
-    return n;
+    if (fields & FIELD_FREE) {
+        if (part->fssize) {
+            print_size (part->free);
+        } else {
+            printf (" N/A ");
+        }
+    }
+
+    if (fields & FIELD_TYPE)
+        printf ("%-8.16s ", part->fstype);
+
+    if (fields & FIELD_MOUNT && part->mount)
+        printf ("%s ", part->mount);
+
+    putchar ('\n');
+}
+
+static void print_disk (const struct my_diskinfo *disk, int fields)
+{
+    if (fields & FIELD_NAME)
+        printf ("%s    ", disk->name);
+
+    if (fields & FIELD_LABEL)
+        printf ("%-16.16s ", disk->label);
+
+    if (fields & FIELD_SIZE)
+        print_size (disk->size);
+
+    if (fields & FIELD_USED)
+        print_size (disk->used);
+
+    if (fields & FIELD_FREE)
+        print_size (disk->size - disk->used);
+
+    // Pad only upto 8 characters because most disk types are <=8 bytes long.
+    if (fields & FIELD_TYPE)
+        printf ("%-8.16s ", disk->type);
+
+    if (fields & FIELD_MOUNT && disk->mount)
+        printf ("%s ", disk->mount);
+
+    putchar ('\n');
+
+    for (uint8_t i = 0; i < disk->num_parts; ++i)
+        print_part (&disk->parts[i], fields);
+}
+
+static const struct statfs *find_mount (const char *dev)
+{
+    static struct statfs *mounts = NULL;
+    static int n_mounts;
+
+    if (!mounts) {
+        n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
+        if (n_mounts == 0)
+            die ("getmntinfo()");
+    }
+
+    for (int i = 0; i < n_mounts; ++i) {
+        if (!strcmp (dev, mounts[i].f_mntfromname))
+            return &mounts[i];
+    }
+
+    return NULL;
 }
 
+static struct my_diskinfo read_disk (const char *name)
+{
+    struct my_diskinfo disk;
+    struct disklabel label;
+
+    bzero (&disk, sizeof disk);
+
+    {   // Read disklabel.
+        const struct statfs *mnt;
+        char path[5 + 4 + 1];
+        int fd;
+
+        snprintf (path, sizeof path, "/dev/%.3sc", name);
+        fd = open (path, O_RDONLY);
+        if (fd < 0)
+            die ("open(%s)", path);
+        if (ioctl (fd, DIOCGDINFO, &label) < 0)
+            die ("ioctl(%s, DIOCGDINFO)", path);
+        close (fd);
+
+        mnt = find_mount (path);
+        if (mnt)
+            disk.mount = mnt->f_mntonname;
+    }
+
+    memcpy (disk.name, name, 3);
+    disk.name[3] = '\0';
+    disk.size = DL_GETDSIZE (&label) * label.d_secsize;
+    memcpy (disk.type, label.d_typename, 16);
+    stripdisk (disk.type);
+    memcpy (disk.label, label.d_packname, 16);
+    disk.num_parts = 0;
+
+    for (uint16_t i = 0; i < label.d_npartitions; ++i) {
+        const struct partition *p = &label.d_partitions[i];
+        const struct statfs *mnt;
+        struct my_partinfo part;
+        char path[5 + 4 + 1];
+
+        bzero (&part, sizeof part);
+
+        part.size = DL_GETPSIZE (p) * label.d_secsize;
+
+        if (!part.size)
+            continue;
+
+        memcpy (part.name, disk.name, 3);
+        part.name[3] = 'a' + i;
+        part.name[4] = '\0';
+
+        snprintf (path, sizeof path, "/dev/%s", part.name);
+        mnt = find_mount (path);
+
+        if (mnt) {
+            const uint64_t bs = mnt->f_bsize;
+            part.mount = mnt->f_mntonname;
+            part.fssize = mnt->f_blocks * bs;
+            part.free = mnt->f_bfree * bs;
+        }
+
+        part.fstype = fstypenames[p->p_fstype];
+        if (i != 2)
+            disk.used += part.size;
+        disk.parts[disk.num_parts++] = part;
+    }
+
+    return disk;
+}
+
+static int usage (void)
+{
+    fputs ("Usage: lsblk [-Vlnu]\n", stderr);
+    return 1;
+}
+
 int main (int argc, char *argv[])
 {
     int option;
     bool header = true;
+    int fields = FIELD_DEFAULT;
 
     if (unveil ("/dev", "r") == -1)
         die ("unveil(/dev)");
@@ -146,7 +361,7 @@ int main (int argc, char *argv[])
     if (unveil (NULL, NULL) == -1)
         die ("unveil()");
 
-    while ((option = getopt (argc, argv, ":Vn")) != -1) {
+    while ((option = getopt (argc, argv, ":Vlnu")) != -1) {
         switch (option) {
         case 'V':
             puts ("lsblk-" VERSION);
@@ -154,6 +369,12 @@ int main (int argc, char *argv[])
         case 'n':
             header = false;
             break;
+        case 'l':
+            fields |= FIELD_LABEL;
+            break;
+        case 'u':
+            fields |= FIELD_USED | FIELD_FREE;
+            break;
         default:
             return usage ();
         }
@@ -172,60 +393,25 @@ int main (int argc, char *argv[])
     if (n_mounts == 0)
         die ("getmntinfo()");
 
-    if (header)
-        puts ("NAME   SIZE TYPE     MOUNTPOINT");
+    size_t cap_disks = 10;
+    size_t num_disks = 0;
+    struct my_diskinfo *disks = calloc (cap_disks, sizeof (struct my_diskinfo));
 
-    for (char *disk; (disk = strsep (&names, ",")) != NULL;) {
-        struct disklabel label;
-        char *colon, *name = NULL;
-        char dtype[16];
-        int fd;
-
-        colon = strchr (disk, ':');
-        if (colon)
-            *colon = '\0';
-
-        asprintf (&name, "/dev/%sc", disk);
-
-        fd = open (name, O_RDONLY);
-        if (fd < 0)
-            die ("open(%s)", name);
-
-        if (ioctl (fd, DIOCGDINFO, &label) < 0)
-            die ("ioctl(%s, IOCGDINFO)", name);
-
-        close (fd);
-
-        const uint32_t ss = label.d_secsize;
-
-        char *letter = name + strlen (name) - 1;
-
-        printf ("%s    ", disk);
-        printsize (DL_GETDSIZE (&label) * ss);
-        memcpy (dtype, label.d_typename, sizeof dtype);
-        printf (" %-8.16s\n", stripdisk (dtype));
-        for (uint16_t i = 0; i < label.d_npartitions; ++i) {
-            const struct partition *p = &label.d_partitions[i];
-            const uint64_t size = DL_GETPSIZE (p) * ss;
-            if (size == 0)
-                continue;
-
-            *letter = i + 'a';
-            const char *mp = "";
-            for (int i = 0; i < n_mounts; ++i) {
-                const struct statfs *m = &mounts[i];
-                if (!strcmp (name, m->f_mntfromname)) {
-                    mp = m->f_mntonname;
-                    break;
-                }
-            }
-
-            printf ("  %s%c ", disk, *letter);
-            printsize (size);
-            printf (" %-8s %s\n", fstypenames[p->p_fstype], mp);
+    for (char *disk; (disk = strsep (&names, ",")) != NULL; ) {
+        if (num_disks == cap_disks) {
+            cap_disks = cap_disks * 13 / 8;
+            disks = reallocarray (disks, cap_disks, sizeof (struct my_diskinfo));
         }
+        disks[num_disks++] = read_disk (disk);
     }
 
     free (names);
+
+    if (header)
+        print_header (fields);
+
+    for (size_t i = 0; i < num_disks; ++i) {
+        print_disk (&disks[i], fields);
+    }
     return 0;
 }