Blob


1 /*
2 * Copyright (c) 2023 Benjamin Stürz <benni@stuerz.xyz>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 #define _XOPEN_SOURCE 700
17 #define _BSD_SOURCE 1
18 #define DKTYPENAMES
19 #include <stddef.h>
20 #include <dev/biovar.h>
21 #include <sys/cdefs.h>
22 #include <sys/types.h>
23 #include <sys/disklabel.h>
24 #include <sys/sysctl.h>
25 #include <sys/ioctl.h>
26 #include <sys/mount.h>
27 #include <sys/dkio.h>
28 #include <inttypes.h>
29 #include <stdbool.h>
30 #include <string.h>
31 #include <stdarg.h>
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <libgen.h>
36 #include <stdio.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #include <util.h>
40 #include <err.h>
42 static int diskcount (void)
43 {
44 const int mib[2] = { CTL_HW, HW_DISKCOUNT };
45 int diskcount;
46 size_t len = sizeof diskcount;
48 if (sysctl (mib, 2, &diskcount, &len, NULL, 0) == -1)
49 err (1, "sysctl(hw.diskcount)");
51 return diskcount;
52 }
54 static char *disknames (void)
55 {
56 const int num = diskcount ();
57 const int mib[2] = { CTL_HW, HW_DISKNAMES };
58 size_t len = 32 * num;
59 char *buffer = malloc (len);
61 if (sysctl (mib, 2, buffer, &len, NULL, 0) == -1)
62 err (1, "sysctl(hw.disknames)");
64 return buffer;
65 }
67 static char *stripdisk (char *n)
68 {
69 const char sufx[] = " disk";
70 const size_t ln = strnlen (n, 16);
71 const size_t ls = sizeof sufx - 1;
73 if (!memcmp (n + ln - ls, sufx, ls)) {
74 n[ln - ls] = '\0';
75 }
77 return n;
78 }
80 static void print_size (uint64_t sz)
81 {
82 struct unit {
83 char sym;
84 uint64_t factor;
85 };
87 const struct unit units[] = {
88 { 'P', 1ull << 50 },
89 { 'T', 1ull << 40 },
90 { 'G', 1ull << 30 },
91 { 'M', 1ull << 20 },
92 { 'K', 1ull << 10 },
93 { '0', 0 },
94 };
96 char sym = 'B';
97 uint64_t factor = 1;
99 for (const struct unit *u = &units[0]; u->factor; ++u) {
100 if (sz >= (u->factor * 9 / 10)) {
101 sym = u->sym;
102 factor = u->factor;
103 break;
107 const unsigned scaled10 = sz * 10 / factor;
108 const unsigned scaled = sz / factor;
109 if (scaled10 >= 1000) {
110 printf ("%u", scaled);
111 } else if (scaled10 >= 100) {
112 printf (" %u", scaled);
113 } else {
114 printf ("%u.%u", scaled, scaled10 % 10);
117 putchar (sym);
118 putchar (' ');
121 enum {
122 FIELD_NAME = 0x01,
123 FIELD_DUID = 0x02,
124 FIELD_SIZE = 0x04,
125 FIELD_USED = 0x08,
126 FIELD_FREE = 0x10,
127 FIELD_TYPE = 0x20,
128 FIELD_COMMENT = 0x40,
130 FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_COMMENT,
131 };
133 enum {
134 OPT_NOHEADER = 0x01,
135 OPT_NOUNICODE = 0x02,
136 OPT_NOBIO = 0x04,
137 };
139 struct my_diskinfo;
141 struct my_partinfo {
142 char letter;
143 uint64_t size;
144 uint64_t fssize;
145 uint64_t free;
146 const char *fstype;
147 const char *mount;
148 const struct my_diskinfo *sub; // If this is part of a RAID
149 const char *raidstatus; // Only available if (sub != NULL)
150 };
152 struct my_diskinfo {
153 char type[16];
154 char label[16];
155 char name[4];
156 uint64_t size;
157 uint64_t used;
158 u_char duid[8];
159 uint8_t num_parts;
160 struct my_partinfo parts[MAXPARTITIONS];
161 const char *raidstatus; // If this is a RAID device
162 };
164 static void print_header (int fields)
166 if (fields & FIELD_NAME)
167 printf ("%-7s ", "NAME");
169 if (fields & FIELD_DUID)
170 printf ("%-18s ", "DUID");
172 if (fields & FIELD_SIZE)
173 printf ("%-4s ", "SIZE");
175 if (fields & FIELD_USED)
176 printf ("%-4s ", "USED");
178 if (fields & FIELD_FREE)
179 printf ("%-4s ", "FREE");
181 if (fields & FIELD_TYPE)
182 printf ("%-8s ", "TYPE");
184 if (fields & FIELD_COMMENT)
185 printf ("COMMENT ");
187 putchar ('\n');
190 static void print_duid (const u_char *duid)
192 for (size_t i = 0; i < 8; ++i) {
193 printf ("%02x", duid[i]);
197 static void print_disk (const struct my_diskinfo *disk, int fields, int options, const char *raidstatus);
198 static void print_part (
199 const struct my_diskinfo *disk,
200 const struct my_partinfo *part,
201 int fields,
202 int options,
203 bool last
204 ) {
205 if (fields & FIELD_NAME) {
206 const char *prefix = (options & OPT_NOUNICODE) ? " " : (last ? "└─" : "├─");
207 printf ("%s%s%c ", prefix, disk->name, part->letter);
210 if (fields & FIELD_DUID) {
211 print_duid (disk->duid);
212 printf (".%c ", part->letter);
215 if (fields & FIELD_SIZE)
216 print_size (part->size);
218 if (fields & FIELD_USED) {
219 if (part->fssize) {
220 print_size (part->fssize - part->free);
221 } else {
222 printf (" N/A ");
226 if (fields & FIELD_FREE) {
227 if (part->fssize) {
228 print_size (part->free);
229 } else {
230 printf (" N/A ");
234 if (fields & FIELD_TYPE)
235 printf ("%-8.16s ", part->fstype);
237 if (fields & FIELD_COMMENT && part->mount)
238 printf ("%s ", part->mount);
240 putchar ('\n');
242 if (part->sub) {
243 printf ("│ └─");
244 print_disk (part->sub, fields, options, part->raidstatus);
248 static void print_disk (const struct my_diskinfo *disk, int fields, int options, const char *raidstatus)
250 if (fields & FIELD_NAME) {
251 printf ("%s ", disk->name);
252 if (!raidstatus)
253 printf (" ");
256 if (fields & FIELD_DUID) {
257 print_duid (disk->duid);
258 printf (" ");
261 if (fields & FIELD_SIZE)
262 print_size (disk->size);
264 if (fields & FIELD_USED)
265 print_size (disk->used);
267 if (fields & FIELD_FREE)
268 print_size (disk->size - disk->used);
270 // Pad only upto 8 characters because most disk types are <=8 bytes long.
271 if (fields & FIELD_TYPE)
272 printf ("%-8.16s ", disk->type);
274 if (fields & FIELD_COMMENT) {
275 if (raidstatus) {
276 printf ("%s ", raidstatus);
277 } else if (disk->raidstatus) {
278 printf ("%.16s (%s) ", disk->label, disk->raidstatus);
279 } else {
280 printf ("%.16s ", disk->label);
284 putchar ('\n');
286 if (!raidstatus) {
287 for (uint8_t i = 0; i < disk->num_parts; ++i)
288 print_part (disk, &disk->parts[i], fields, options, i == (disk->num_parts - 1));
292 static const struct statfs *find_mount (const char *dev)
294 static struct statfs *mounts = NULL;
295 static int n_mounts;
297 if (!mounts) {
298 n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
299 if (n_mounts == 0)
300 err (1, "getmntinfo()");
303 for (int i = 0; i < n_mounts; ++i) {
304 if (!strcmp (dev, mounts[i].f_mntfromname))
305 return &mounts[i];
308 return NULL;
311 static struct my_diskinfo read_disk (const char *name)
313 struct my_diskinfo disk;
314 struct disklabel label;
315 char *ppath, *letter;
317 bzero (&disk, sizeof disk);
319 { // Read disklabel.
320 size_t len;
321 int fd;
323 fd = opendev (name, O_RDONLY, OPENDEV_PART | OPENDEV_BLCK, &ppath);
324 if (fd < 0)
325 err (1, "opendev(%s)", name);
327 if (ioctl (fd, DIOCGDINFO, &label) < 0)
328 err (1, "ioctl(%s, DIOCGDINFO)", name);
329 close (fd);
331 len = strlen (ppath);
332 letter = ppath + len - 1;
335 memcpy (disk.name, name, 3);
336 disk.name[3] = '\0';
337 disk.size = DL_GETDSIZE (&label) * label.d_secsize;
338 memcpy (disk.type, label.d_typename, 16);
339 stripdisk (disk.type);
340 memcpy (disk.label, label.d_packname, 16);
341 memcpy (disk.duid, label.d_uid, sizeof disk.duid);
342 disk.num_parts = 0;
344 for (uint16_t i = 0; i < label.d_npartitions; ++i) {
345 const struct partition *p = &label.d_partitions[i];
346 const struct statfs *mnt;
347 struct my_partinfo part;
349 bzero (&part, sizeof part);
351 part.size = DL_GETPSIZE (p) * label.d_secsize;
353 if (!part.size)
354 continue;
356 part.letter = 'a' + i;
357 *letter = part.letter;
358 mnt = find_mount (ppath);
360 if (mnt) {
361 const uint64_t bs = mnt->f_bsize;
362 part.mount = mnt->f_mntonname;
363 part.fssize = mnt->f_blocks * bs;
364 part.free = mnt->f_bfree * bs;
367 part.fstype = fstypenames[p->p_fstype];
368 if (i != 2)
369 disk.used += part.size;
370 disk.parts[disk.num_parts++] = part;
373 return disk;
376 static const char *bd_statusstr (int status) {
377 switch (status) {
378 case BIOC_SDONLINE: return BIOC_SDONLINE_S;
379 case BIOC_SDOFFLINE: return BIOC_SDOFFLINE_S;
380 case BIOC_SDFAILED: return BIOC_SDFAILED_S;
381 case BIOC_SDREBUILD: return BIOC_SDREBUILD_S;
382 case BIOC_SDHOTSPARE: return BIOC_SDHOTSPARE_S;
383 case BIOC_SDUNUSED: return BIOC_SDUNUSED_S;
384 case BIOC_SDSCRUB: return BIOC_SDSCRUB_S;
385 case BIOC_SDINVALID: return BIOC_SDINVALID_S;
386 default: return "Unknown";
390 static const char *bv_statusstr (int status) {
391 switch (status) {
392 case BIOC_SVONLINE: return BIOC_SVONLINE_S;
393 case BIOC_SVOFFLINE: return BIOC_SVOFFLINE_S;
394 case BIOC_SVDEGRADED: return BIOC_SVDEGRADED_S;
395 case BIOC_SVBUILDING: return BIOC_SVBUILDING_S;
396 case BIOC_SVSCRUB: return BIOC_SVSCRUB_S;
397 case BIOC_SVREBUILD: return BIOC_SVREBUILD_S;
398 case BIOC_SVINVALID: return BIOC_SVINVALID_S;
399 default: return "Unknown";
403 static void read_raid (
404 struct my_diskinfo *disk,
405 struct my_diskinfo *disks,
406 size_t num_disks
407 ) {
408 struct bioc_inq bi;
409 int fd;
411 fd = opendev (disk->name, O_RDONLY, OPENDEV_PART | OPENDEV_BLCK, NULL);
412 if (fd < 0) {
413 warn ("read_raid(): opendev(%s)", disk->name);
414 return;
417 bzero (&bi, sizeof bi);
419 if (ioctl (fd, BIOCINQ, &bi) == -1)
420 goto ret;
422 for (int i = 0; i < bi.bi_novol; ++i) {
423 struct bioc_vol bv;
425 bzero (&bv, sizeof bv);
426 memcpy (&bv.bv_bio, &bi.bi_bio, sizeof bi.bi_bio);
427 bv.bv_volid = i;
429 if (ioctl (fd, BIOCVOL, &bv) == -1) {
430 warn ("read_raid(%s): BIOCVOL(%d)", disk->name, i);
431 continue;
434 if (strcmp (disk->name, bv.bv_dev) != 0)
435 continue;
437 disk->raidstatus = bv_statusstr (bv.bv_status);
439 for (int j = 0; j < bv.bv_nodisk; ++j) {
440 struct bioc_disk bd;
441 size_t len_vendor;
442 char letter;
444 bzero (&bd, sizeof bd);
445 memcpy (&bd.bd_bio, &bi.bi_bio, sizeof bi.bi_bio);
446 bd.bd_volid = i;
447 bd.bd_diskid = j;
449 if (ioctl (fd, BIOCDISK, &bd) == -1) {
450 warn ("read_raid(%s): BIOCDISK(%d, %d)", disk->name, i, j);
451 continue;
454 len_vendor = strlen (bd.bd_vendor);
455 if (len_vendor != 4) {
456 warnx ("read_raid(%s): unexpected vendor string: %.32s", disk->name, bd.bd_vendor);
457 continue;
459 letter = bd.bd_vendor[len_vendor - 1];
461 for (size_t k = 0; k < num_disks; ++k) {
462 if (!memcmp (bd.bd_vendor, disks[k].name, 3)) {
463 for (size_t l = 0; l < disks[k].num_parts; ++l) {
464 if (letter == disks[k].parts[l].letter) {
465 disks[k].parts[l].sub = disk;
466 disks[k].parts[l].raidstatus = bd_statusstr(bd.bd_status);
467 goto found;
472 found:;
475 ret:
476 close (fd);
479 static int usage (void)
481 fputs ("Usage: lsblk [-abinUuV] [disk...]\n", stderr);
482 return 1;
485 int main (int argc, char *argv[])
487 int option;
488 int fields = FIELD_DEFAULT;
489 int options = 0;
491 if (unveil ("/dev", "r") == -1)
492 err (1, "unveil(/dev)");
494 if (unveil (NULL, NULL) == -1)
495 err (1, "unveil()");
497 while ((option = getopt (argc, argv, ":abinUuV")) != -1) {
498 switch (option) {
499 case 'a':
500 fields = -1;
501 break;
502 case 'b':
503 options |= OPT_NOBIO;
504 break;
505 case 'i':
506 options |= OPT_NOUNICODE;
507 break;
508 case 'n':
509 options |= OPT_NOHEADER;
510 break;
511 case 'U':
512 fields |= FIELD_DUID;
513 break;
514 case 'u':
515 fields |= FIELD_USED | FIELD_FREE;
516 break;
517 case 'V':
518 puts ("lsblk-" VERSION);
519 return 0;
520 default:
521 return usage ();
525 argv += optind;
526 argc -= optind;
528 char *names = argc == 0 ? disknames () : NULL;
530 if (pledge ("stdio rpath disklabel", NULL) == -1)
531 err (1, "pledge()");
533 size_t cap_disks;
534 if (argc == 0) {
535 const char *s = names;
536 cap_disks = 0;
537 while ((s = strchr (s, ',')) != NULL)
538 ++cap_disks, ++s;
539 } else {
540 cap_disks = argc;
543 struct my_diskinfo *disks = calloc (cap_disks, sizeof (struct my_diskinfo));
544 size_t num_disks = 0;
546 if (argc == 0) {
547 for (char *disk; (disk = strsep (&names, ",")) != NULL; ) {
548 char *colon = strchr (disk, ':');
549 if (colon)
550 *colon = '\0';
551 disks[num_disks++] = read_disk (disk);
552 if (colon)
553 *colon = ':';
555 } else {
556 for (int i = 0; i < argc; ++i) {
557 char *disk = basename (argv[i]);
558 disks[num_disks++] = read_disk (disk);
562 free (names);
564 if (!(options & OPT_NOBIO)) {
565 for (size_t i = 0; i < num_disks; ++i) {
566 read_raid (&disks[i], disks, num_disks);
570 if (!(options & OPT_NOHEADER))
571 print_header (fields);
573 for (size_t i = 0; i < num_disks; ++i) {
574 print_disk (&disks[i], fields, options, false);
577 return 0;