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 <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>
26 #include <sys/dkio.h>
27 #include <inttypes.h>
28 #include <stdbool.h>
29 #include <string.h>
30 #include <stdarg.h>
31 #include <stdint.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <fcntl.h>
36 #include <errno.h>
38 static __dead void die (const char *fmt, ...)
39 {
40 va_list ap;
42 va_start (ap, fmt);
43 fputs ("Error: ", stderr);
44 vfprintf (stderr, fmt, ap);
46 if (errno != 0) {
47 fprintf (stderr, ": %s\n", strerror (errno));
48 } else {
49 fputc ('\n', stderr);
50 }
52 exit (1);
53 }
55 static int diskcount (void)
56 {
57 const int mib[2] = { CTL_HW, HW_DISKCOUNT };
58 int diskcount;
59 size_t len = sizeof diskcount;
61 if (sysctl (mib, 2, &diskcount, &len, NULL, 0) == -1)
62 die ("sysctl(hw.diskcount)");
64 return diskcount;
65 }
67 static char *disknames (void)
68 {
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)");
77 return buffer;
78 }
80 static char *stripdisk (char *n)
81 {
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)) {
87 n[ln - ls] = '\0';
88 }
90 return n;
91 }
93 static void print_size (uint64_t sz)
94 {
95 struct unit {
96 char sym;
97 uint64_t factor;
98 };
100 const struct unit units[] = {
101 { 'P', 1ull << 50 },
102 { 'T', 1ull << 40 },
103 { 'G', 1ull << 30 },
104 { 'M', 1ull << 20 },
105 { 'K', 1ull << 10 },
106 { '0', 0 },
107 };
109 char sym = 'B';
110 uint64_t factor = 1;
112 for (const struct unit *u = &units[0]; u->factor; ++u) {
113 if (sz >= (u->factor * 9 / 10)) {
114 sym = u->sym;
115 factor = u->factor;
116 break;
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);
126 } else {
127 printf ("%u.%u", scaled, scaled10 % 10);
130 putchar (sym);
131 putchar (' ');
134 enum {
135 FIELD_NAME = 0x01,
136 FIELD_SIZE = 0x02,
137 FIELD_USED = 0x04,
138 FIELD_FREE = 0x08,
139 FIELD_TYPE = 0x10,
140 FIELD_LMNT = 0x20,
142 FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_LMNT,
143 };
145 struct my_partinfo {
146 char name[5];
147 uint64_t size;
148 uint64_t fssize;
149 uint64_t free;
150 const char *fstype;
151 const char *mount;
152 };
154 struct my_diskinfo {
155 char type[16];
156 char label[16];
157 char name[4];
158 uint64_t size;
159 uint64_t used;
160 uint8_t num_parts;
161 struct my_partinfo parts[MAXPARTITIONS];
162 };
164 static void print_header (int fields)
166 if (fields & FIELD_NAME)
167 printf ("%-6s ", "NAME");
169 if (fields & FIELD_SIZE)
170 printf ("%-4s ", "SIZE");
172 if (fields & FIELD_USED)
173 printf ("%-4s ", "USED");
175 if (fields & FIELD_FREE)
176 printf ("%-4s ", "FREE");
178 if (fields & FIELD_TYPE)
179 printf ("%-8s ", "TYPE");
181 if (fields & FIELD_LMNT)
182 printf ("LABEL/MOUNT ");
184 putchar ('\n');
187 static void print_part (const struct my_partinfo *part, int fields)
189 if (fields & FIELD_NAME)
190 printf (" %s ", part->name);
192 if (fields & FIELD_SIZE)
193 print_size (part->size);
195 if (fields & FIELD_USED) {
196 if (part->fssize) {
197 print_size (part->fssize - part->free);
198 } else {
199 printf (" N/A ");
203 if (fields & FIELD_FREE) {
204 if (part->fssize) {
205 print_size (part->free);
206 } else {
207 printf (" N/A ");
211 if (fields & FIELD_TYPE)
212 printf ("%-8.16s ", part->fstype);
214 if (fields & FIELD_LMNT && part->mount)
215 printf ("%s ", part->mount);
217 putchar ('\n');
220 static void print_disk (const struct my_diskinfo *disk, int fields)
222 if (fields & FIELD_NAME)
223 printf ("%s ", disk->name);
225 if (fields & FIELD_SIZE)
226 print_size (disk->size);
228 if (fields & FIELD_USED)
229 print_size (disk->used);
231 if (fields & FIELD_FREE)
232 print_size (disk->size - disk->used);
234 // Pad only upto 8 characters because most disk types are <=8 bytes long.
235 if (fields & FIELD_TYPE)
236 printf ("%-8.16s ", disk->type);
238 if (fields & FIELD_LMNT)
239 printf ("%.16s ", disk->label);
241 putchar ('\n');
243 for (uint8_t i = 0; i < disk->num_parts; ++i)
244 print_part (&disk->parts[i], fields);
247 static const struct statfs *find_mount (const char *dev)
249 static struct statfs *mounts = NULL;
250 static int n_mounts;
252 if (!mounts) {
253 n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
254 if (n_mounts == 0)
255 die ("getmntinfo()");
258 for (int i = 0; i < n_mounts; ++i) {
259 if (!strcmp (dev, mounts[i].f_mntfromname))
260 return &mounts[i];
263 return NULL;
266 static struct my_diskinfo read_disk (const char *name)
268 struct my_diskinfo disk;
269 struct disklabel label;
271 bzero (&disk, sizeof disk);
273 { // Read disklabel.
274 char path[5 + 4 + 1];
275 int fd;
277 snprintf (path, sizeof path, "/dev/%.3sc", name);
278 fd = open (path, O_RDONLY);
279 if (fd < 0)
280 die ("open(%s)", path);
281 if (ioctl (fd, DIOCGDINFO, &label) < 0)
282 die ("ioctl(%s, DIOCGDINFO)", path);
283 close (fd);
286 memcpy (disk.name, name, 3);
287 disk.name[3] = '\0';
288 disk.size = DL_GETDSIZE (&label) * label.d_secsize;
289 memcpy (disk.type, label.d_typename, 16);
290 stripdisk (disk.type);
291 memcpy (disk.label, label.d_packname, 16);
292 disk.num_parts = 0;
294 for (uint16_t i = 0; i < label.d_npartitions; ++i) {
295 const struct partition *p = &label.d_partitions[i];
296 const struct statfs *mnt;
297 struct my_partinfo part;
298 char path[5 + 4 + 1];
300 bzero (&part, sizeof part);
302 part.size = DL_GETPSIZE (p) * label.d_secsize;
304 if (!part.size)
305 continue;
307 memcpy (part.name, disk.name, 3);
308 part.name[3] = 'a' + i;
309 part.name[4] = '\0';
311 snprintf (path, sizeof path, "/dev/%s", part.name);
312 mnt = find_mount (path);
314 if (mnt) {
315 const uint64_t bs = mnt->f_bsize;
316 part.mount = mnt->f_mntonname;
317 part.fssize = mnt->f_blocks * bs;
318 part.free = mnt->f_bfree * bs;
321 part.fstype = fstypenames[p->p_fstype];
322 if (i != 2)
323 disk.used += part.size;
324 disk.parts[disk.num_parts++] = part;
327 return disk;
330 static int usage (void)
332 fputs ("Usage: lsblk [-Vanu]\n", stderr);
333 return 1;
336 int main (int argc, char *argv[])
338 int option;
339 bool header = true;
340 int fields = FIELD_DEFAULT;
342 if (unveil ("/dev", "r") == -1)
343 die ("unveil(/dev)");
345 if (unveil (NULL, NULL) == -1)
346 die ("unveil()");
348 while ((option = getopt (argc, argv, ":Vanu")) != -1) {
349 switch (option) {
350 case 'V':
351 puts ("lsblk-" VERSION);
352 return 0;
353 case 'a':
354 fields = -1;
355 break;
356 case 'n':
357 header = false;
358 break;
359 case 'u':
360 fields |= FIELD_USED | FIELD_FREE;
361 break;
362 default:
363 return usage ();
367 if (argc != optind)
368 return usage ();
370 struct statfs *mounts;
371 char *names = disknames ();
373 if (pledge ("stdio rpath disklabel", NULL) == -1)
374 die ("pledge()");
376 const int n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
377 if (n_mounts == 0)
378 die ("getmntinfo()");
380 size_t cap_disks = 10;
381 size_t num_disks = 0;
382 struct my_diskinfo *disks = calloc (cap_disks, sizeof (struct my_diskinfo));
384 for (char *disk; (disk = strsep (&names, ",")) != NULL; ) {
385 if (num_disks == cap_disks) {
386 cap_disks = cap_disks * 13 / 8;
387 disks = reallocarray (disks, cap_disks, sizeof (struct my_diskinfo));
389 disks[num_disks++] = read_disk (disk);
392 free (names);
394 if (header)
395 print_header (fields);
397 for (size_t i = 0; i < num_disks; ++i) {
398 print_disk (&disks[i], fields);
400 return 0;