/* * Copyright (c) 2023 Benjamin Stürz * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _XOPEN_SOURCE 700 #define _BSD_SOURCE 1 #define DKTYPENAMES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static __dead void die (const char *fmt, ...) { va_list ap; va_start (ap, fmt); fputs ("Error: ", stderr); vfprintf (stderr, fmt, ap); if (errno != 0) { fprintf (stderr, ": %s\n", strerror (errno)); } else { fputc ('\n', stderr); } exit (1); } static int diskcount (void) { const int mib[2] = { CTL_HW, HW_DISKCOUNT }; int diskcount; size_t len = sizeof diskcount; if (sysctl (mib, 2, &diskcount, &len, NULL, 0) == -1) die ("sysctl(hw.diskcount)"); return diskcount; } static char *disknames (void) { const int num = diskcount (); const int mib[2] = { CTL_HW, HW_DISKNAMES }; size_t len = 32 * num; char *buffer = malloc (len); if (sysctl (mib, 2, buffer, &len, NULL, 0) == -1) die ("sysctl(hw.disknames)"); return buffer; } 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; }; const struct unit units[] = { { 'P', 1ull << 50 }, { 'T', 1ull << 40 }, { 'G', 1ull << 30 }, { 'M', 1ull << 20 }, { 'K', 1ull << 10 }, { '0', 0 }, }; char sym = 'B'; uint64_t factor = 1; for (const struct unit *u = &units[0]; u->factor; ++u) { if (sz >= (u->factor * 9 / 10)) { sym = u->sym; factor = u->factor; break; } } const unsigned scaled10 = sz * 10 / factor; const unsigned scaled = sz / factor; if (scaled10 >= 1000) { printf ("%u", scaled); } else if (scaled10 >= 100) { printf (" %u", scaled); } else { printf ("%u.%u", scaled, scaled10 % 10); } putchar (sym); putchar (' '); } enum { FIELD_NAME = 0x01, FIELD_SIZE = 0x02, FIELD_USED = 0x04, FIELD_FREE = 0x08, FIELD_TYPE = 0x10, FIELD_LMNT = 0x20, FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_LMNT, }; enum { OPT_NOHEADER = 0x01, OPT_NOUNICODE = 0x02, }; 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; uint8_t num_parts; struct my_partinfo parts[MAXPARTITIONS]; }; static void print_header (int fields) { if (fields & FIELD_NAME) printf ("%-6s ", "NAME"); 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_LMNT) printf ("LABEL/MOUNT "); putchar ('\n'); } static void print_part (const struct my_partinfo *part, int fields, int options, bool last) { if (fields & FIELD_NAME) { const char *prefix = (options & OPT_NOUNICODE) ? " " : (last ? "└─" : "├─"); printf ("%s%s ", prefix, part->name); } 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 "); } } 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_LMNT && part->mount) printf ("%s ", part->mount); putchar ('\n'); } static void print_disk (const struct my_diskinfo *disk, int fields, int options) { if (fields & FIELD_NAME) printf ("%s ", disk->name); 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_LMNT) printf ("%.16s ", disk->label); putchar ('\n'); for (uint8_t i = 0; i < disk->num_parts; ++i) print_part (&disk->parts[i], fields, options, i == (disk->num_parts - 1)); } 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. 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); } 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 [-Vainu]\n", stderr); return 1; } int main (int argc, char *argv[]) { int option; int fields = FIELD_DEFAULT; int options = 0; if (unveil ("/dev", "r") == -1) die ("unveil(/dev)"); if (unveil (NULL, NULL) == -1) die ("unveil()"); while ((option = getopt (argc, argv, ":Vainu")) != -1) { switch (option) { case 'V': puts ("lsblk-" VERSION); return 0; case 'a': fields = -1; break; case 'i': options |= OPT_NOUNICODE; break; case 'n': options |= OPT_NOHEADER; break; case 'u': fields |= FIELD_USED | FIELD_FREE; break; default: return usage (); } } if (argc != optind) return usage (); struct statfs *mounts; char *names = disknames (); if (pledge ("stdio rpath disklabel", NULL) == -1) die ("pledge()"); const int n_mounts = getmntinfo (&mounts, MNT_NOWAIT); if (n_mounts == 0) die ("getmntinfo()"); 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; ) { 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 (!(options & OPT_NOHEADER)) print_header (fields); for (size_t i = 0; i < num_disks; ++i) { print_disk (&disks[i], fields, options); } return 0; }