2 * Copyright (c) 2023 Benjamin Stürz <benni@stuerz.xyz>
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.
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.
16 #define _XOPEN_SOURCE 700
20 #include <sys/cdefs.h>
21 #include <sys/types.h>
22 #include <sys/disklabel.h>
23 #include <sys/sysctl.h>
24 #include <sys/ioctl.h>
25 #include <sys/mount.h>
38 static __dead void die (const char *fmt, ...)
43 fputs ("Error: ", stderr);
44 vfprintf (stderr, fmt, ap);
47 fprintf (stderr, ": %s\n", strerror (errno));
55 static int diskcount (void)
57 const int mib[2] = { CTL_HW, HW_DISKCOUNT };
59 size_t len = sizeof diskcount;
61 if (sysctl (mib, 2, &diskcount, &len, NULL, 0) == -1)
62 die ("sysctl(hw.diskcount)");
67 static char *disknames (void)
69 const int num = diskcount ();
70 const int mib[2] = { CTL_HW, HW_DISKNAMES };
71 size_t len = 32 * num;
72 char *buffer = malloc (len);
74 if (sysctl (mib, 2, buffer, &len, NULL, 0) == -1)
75 die ("sysctl(hw.disknames)");
80 static char *stripdisk (char *n)
82 const char sufx[] = " disk";
83 const size_t ln = strnlen (n, 16);
84 const size_t ls = sizeof sufx - 1;
86 if (!memcmp (n + ln - ls, sufx, ls)) {
93 static void print_size (uint64_t sz)
100 const struct unit units[] = {
112 for (const struct unit *u = &units[0]; u->factor; ++u) {
113 if (sz >= (u->factor * 9 / 10)) {
120 const unsigned scaled10 = sz * 10 / factor;
121 const unsigned scaled = sz / factor;
122 if (scaled10 >= 1000) {
123 printf ("%u", scaled);
124 } else if (scaled10 >= 100) {
125 printf (" %u", scaled);
127 printf ("%u.%u", scaled, scaled10 % 10);
143 FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_MOUNT,
163 struct my_partinfo parts[MAXPARTITIONS];
166 static void print_header (int fields)
168 if (fields & FIELD_NAME)
169 printf ("%-6s ", "NAME");
171 if (fields & FIELD_LABEL)
172 printf ("%-16s ", "LABEL");
174 if (fields & FIELD_SIZE)
175 printf ("%-4s ", "SIZE");
177 if (fields & FIELD_USED)
178 printf ("%-4s ", "USED");
180 if (fields & FIELD_FREE)
181 printf ("%-4s ", "FREE");
183 if (fields & FIELD_TYPE)
184 printf ("%-8s ", "TYPE");
186 if (fields & FIELD_MOUNT)
192 static void print_part (const struct my_partinfo *part, int fields)
194 if (fields & FIELD_NAME)
195 printf (" %s ", part->name);
197 if (fields & FIELD_LABEL)
198 printf ("%16s ", "");
200 if (fields & FIELD_SIZE)
201 print_size (part->size);
203 if (fields & FIELD_USED) {
205 print_size (part->fssize - part->free);
211 if (fields & FIELD_FREE) {
213 print_size (part->free);
219 if (fields & FIELD_TYPE)
220 printf ("%-8.16s ", part->fstype);
222 if (fields & FIELD_MOUNT && part->mount)
223 printf ("%s ", part->mount);
228 static void print_disk (const struct my_diskinfo *disk, int fields)
230 if (fields & FIELD_NAME)
231 printf ("%s ", disk->name);
233 if (fields & FIELD_LABEL)
234 printf ("%-16.16s ", disk->label);
236 if (fields & FIELD_SIZE)
237 print_size (disk->size);
239 if (fields & FIELD_USED)
240 print_size (disk->used);
242 if (fields & FIELD_FREE)
243 print_size (disk->size - disk->used);
245 // Pad only upto 8 characters because most disk types are <=8 bytes long.
246 if (fields & FIELD_TYPE)
247 printf ("%-8.16s ", disk->type);
249 if (fields & FIELD_MOUNT && disk->mount)
250 printf ("%s ", disk->mount);
254 for (uint8_t i = 0; i < disk->num_parts; ++i)
255 print_part (&disk->parts[i], fields);
258 static const struct statfs *find_mount (const char *dev)
260 static struct statfs *mounts = NULL;
264 n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
266 die ("getmntinfo()");
269 for (int i = 0; i < n_mounts; ++i) {
270 if (!strcmp (dev, mounts[i].f_mntfromname))
277 static struct my_diskinfo read_disk (const char *name)
279 struct my_diskinfo disk;
280 struct disklabel label;
282 bzero (&disk, sizeof disk);
285 const struct statfs *mnt;
286 char path[5 + 4 + 1];
289 snprintf (path, sizeof path, "/dev/%.3sc", name);
290 fd = open (path, O_RDONLY);
292 die ("open(%s)", path);
293 if (ioctl (fd, DIOCGDINFO, &label) < 0)
294 die ("ioctl(%s, DIOCGDINFO)", path);
297 mnt = find_mount (path);
299 disk.mount = mnt->f_mntonname;
302 memcpy (disk.name, name, 3);
304 disk.size = DL_GETDSIZE (&label) * label.d_secsize;
305 memcpy (disk.type, label.d_typename, 16);
306 stripdisk (disk.type);
307 memcpy (disk.label, label.d_packname, 16);
310 for (uint16_t i = 0; i < label.d_npartitions; ++i) {
311 const struct partition *p = &label.d_partitions[i];
312 const struct statfs *mnt;
313 struct my_partinfo part;
314 char path[5 + 4 + 1];
316 bzero (&part, sizeof part);
318 part.size = DL_GETPSIZE (p) * label.d_secsize;
323 memcpy (part.name, disk.name, 3);
324 part.name[3] = 'a' + i;
327 snprintf (path, sizeof path, "/dev/%s", part.name);
328 mnt = find_mount (path);
331 const uint64_t bs = mnt->f_bsize;
332 part.mount = mnt->f_mntonname;
333 part.fssize = mnt->f_blocks * bs;
334 part.free = mnt->f_bfree * bs;
337 part.fstype = fstypenames[p->p_fstype];
339 disk.used += part.size;
340 disk.parts[disk.num_parts++] = part;
346 static int usage (void)
348 fputs ("Usage: lsblk [-Valnu]\n", stderr);
352 int main (int argc, char *argv[])
356 int fields = FIELD_DEFAULT;
358 if (unveil ("/dev", "r") == -1)
359 die ("unveil(/dev)");
361 if (unveil (NULL, NULL) == -1)
364 while ((option = getopt (argc, argv, ":Valnu")) != -1) {
367 puts ("lsblk-" VERSION);
373 fields |= FIELD_LABEL;
379 fields |= FIELD_USED | FIELD_FREE;
389 struct statfs *mounts;
390 char *names = disknames ();
392 if (pledge ("stdio rpath disklabel", NULL) == -1)
395 const int n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
397 die ("getmntinfo()");
399 size_t cap_disks = 10;
400 size_t num_disks = 0;
401 struct my_diskinfo *disks = calloc (cap_disks, sizeof (struct my_diskinfo));
403 for (char *disk; (disk = strsep (&names, ",")) != NULL; ) {
404 if (num_disks == cap_disks) {
405 cap_disks = cap_disks * 13 / 8;
406 disks = reallocarray (disks, cap_disks, sizeof (struct my_diskinfo));
408 disks[num_disks++] = read_disk (disk);
414 print_header (fields);
416 for (size_t i = 0; i < num_disks; ++i) {
417 print_disk (&disks[i], fields);