2 9ddaf4ab 2023-04-08 benni * Copyright (c) 2023 Benjamin Stürz <benni@stuerz.xyz>
4 9ddaf4ab 2023-04-08 benni * Permission to use, copy, modify, and distribute this software for any
5 9ddaf4ab 2023-04-08 benni * purpose with or without fee is hereby granted, provided that the above
6 9ddaf4ab 2023-04-08 benni * copyright notice and this permission notice appear in all copies.
8 9ddaf4ab 2023-04-08 benni * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 9ddaf4ab 2023-04-08 benni * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 9ddaf4ab 2023-04-08 benni * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 9ddaf4ab 2023-04-08 benni * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 9ddaf4ab 2023-04-08 benni * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 9ddaf4ab 2023-04-08 benni * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 9ddaf4ab 2023-04-08 benni * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 33bf1ca4 2023-04-08 benni #define _XOPEN_SOURCE 700
17 33bf1ca4 2023-04-08 benni #define _BSD_SOURCE 1
18 33bf1ca4 2023-04-08 benni #define DKTYPENAMES
19 33bf1ca4 2023-04-08 benni #include <stddef.h>
20 9c5d8ecc 2023-05-02 benni #include <sys/cdefs.h>
21 33bf1ca4 2023-04-08 benni #include <sys/types.h>
22 33bf1ca4 2023-04-08 benni #include <sys/disklabel.h>
23 33bf1ca4 2023-04-08 benni #include <sys/sysctl.h>
24 33bf1ca4 2023-04-08 benni #include <sys/ioctl.h>
25 33bf1ca4 2023-04-08 benni #include <sys/mount.h>
26 33bf1ca4 2023-04-08 benni #include <sys/dkio.h>
27 33bf1ca4 2023-04-08 benni #include <inttypes.h>
28 8380cc51 2023-05-01 benni #include <stdbool.h>
29 33bf1ca4 2023-04-08 benni #include <string.h>
30 33bf1ca4 2023-04-08 benni #include <stdarg.h>
31 33bf1ca4 2023-04-08 benni #include <stdint.h>
32 33bf1ca4 2023-04-08 benni #include <stdlib.h>
33 33bf1ca4 2023-04-08 benni #include <unistd.h>
34 33bf1ca4 2023-04-08 benni #include <stdio.h>
35 33bf1ca4 2023-04-08 benni #include <fcntl.h>
36 33bf1ca4 2023-04-08 benni #include <errno.h>
38 9c5d8ecc 2023-05-02 benni static __dead void die (const char *fmt, ...)
40 33bf1ca4 2023-04-08 benni va_list ap;
42 33bf1ca4 2023-04-08 benni va_start (ap, fmt);
43 33bf1ca4 2023-04-08 benni fputs ("Error: ", stderr);
44 33bf1ca4 2023-04-08 benni vfprintf (stderr, fmt, ap);
46 33bf1ca4 2023-04-08 benni if (errno != 0) {
47 33bf1ca4 2023-04-08 benni fprintf (stderr, ": %s\n", strerror (errno));
49 33bf1ca4 2023-04-08 benni fputc ('\n', stderr);
55 33bf1ca4 2023-04-08 benni static int diskcount (void)
57 33bf1ca4 2023-04-08 benni const int mib[2] = { CTL_HW, HW_DISKCOUNT };
58 33bf1ca4 2023-04-08 benni int diskcount;
59 33bf1ca4 2023-04-08 benni size_t len = sizeof diskcount;
61 33bf1ca4 2023-04-08 benni if (sysctl (mib, 2, &diskcount, &len, NULL, 0) == -1)
62 33bf1ca4 2023-04-08 benni die ("sysctl(hw.diskcount)");
64 33bf1ca4 2023-04-08 benni return diskcount;
67 33bf1ca4 2023-04-08 benni static char *disknames (void)
69 33bf1ca4 2023-04-08 benni const int num = diskcount ();
70 33bf1ca4 2023-04-08 benni const int mib[2] = { CTL_HW, HW_DISKNAMES };
71 33bf1ca4 2023-04-08 benni size_t len = 32 * num;
72 33bf1ca4 2023-04-08 benni char *buffer = malloc (len);
74 33bf1ca4 2023-04-08 benni if (sysctl (mib, 2, buffer, &len, NULL, 0) == -1)
75 33bf1ca4 2023-04-08 benni die ("sysctl(hw.disknames)");
77 33bf1ca4 2023-04-08 benni return buffer;
80 9c5d8ecc 2023-05-02 benni static char *stripdisk (char *n)
82 9c5d8ecc 2023-05-02 benni const char sufx[] = " disk";
83 9c5d8ecc 2023-05-02 benni const size_t ln = strnlen (n, 16);
84 9c5d8ecc 2023-05-02 benni const size_t ls = sizeof sufx - 1;
86 9c5d8ecc 2023-05-02 benni if (!memcmp (n + ln - ls, sufx, ls)) {
87 9c5d8ecc 2023-05-02 benni n[ln - ls] = '\0';
93 9c5d8ecc 2023-05-02 benni static void print_size (uint64_t sz)
95 33bf1ca4 2023-04-08 benni struct unit {
97 33bf1ca4 2023-04-08 benni uint64_t factor;
100 33bf1ca4 2023-04-08 benni const struct unit units[] = {
101 33bf1ca4 2023-04-08 benni { 'P', 1ull << 50 },
102 33bf1ca4 2023-04-08 benni { 'T', 1ull << 40 },
103 33bf1ca4 2023-04-08 benni { 'G', 1ull << 30 },
104 33bf1ca4 2023-04-08 benni { 'M', 1ull << 20 },
105 33bf1ca4 2023-04-08 benni { 'K', 1ull << 10 },
106 33bf1ca4 2023-04-08 benni { '0', 0 },
109 33bf1ca4 2023-04-08 benni char sym = 'B';
110 33bf1ca4 2023-04-08 benni uint64_t factor = 1;
112 33bf1ca4 2023-04-08 benni for (const struct unit *u = &units[0]; u->factor; ++u) {
113 33bf1ca4 2023-04-08 benni if (sz >= (u->factor * 9 / 10)) {
114 33bf1ca4 2023-04-08 benni sym = u->sym;
115 33bf1ca4 2023-04-08 benni factor = u->factor;
120 33bf1ca4 2023-04-08 benni const unsigned scaled10 = sz * 10 / factor;
121 33bf1ca4 2023-04-08 benni const unsigned scaled = sz / factor;
122 33bf1ca4 2023-04-08 benni if (scaled10 >= 1000) {
123 8a374bef 2023-04-08 benni printf ("%u", scaled);
124 33bf1ca4 2023-04-08 benni } else if (scaled10 >= 100) {
125 8a374bef 2023-04-08 benni printf (" %u", scaled);
127 8a374bef 2023-04-08 benni printf ("%u.%u", scaled, scaled10 % 10);
130 33bf1ca4 2023-04-08 benni putchar (sym);
131 9c5d8ecc 2023-05-02 benni putchar (' ');
135 9c5d8ecc 2023-05-02 benni FIELD_NAME = 0x01,
136 2b650bba 2023-05-04 benni FIELD_SIZE = 0x02,
137 2b650bba 2023-05-04 benni FIELD_USED = 0x04,
138 2b650bba 2023-05-04 benni FIELD_FREE = 0x08,
139 2b650bba 2023-05-04 benni FIELD_TYPE = 0x10,
140 2b650bba 2023-05-04 benni FIELD_LMNT = 0x20,
142 2b650bba 2023-05-04 benni FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_LMNT,
145 9c5d8ecc 2023-05-02 benni struct my_partinfo {
146 9c5d8ecc 2023-05-02 benni char name[5];
147 9c5d8ecc 2023-05-02 benni uint64_t size;
148 9c5d8ecc 2023-05-02 benni uint64_t fssize;
149 9c5d8ecc 2023-05-02 benni uint64_t free;
150 9c5d8ecc 2023-05-02 benni const char *fstype;
151 9c5d8ecc 2023-05-02 benni const char *mount;
154 9c5d8ecc 2023-05-02 benni struct my_diskinfo {
155 9c5d8ecc 2023-05-02 benni char type[16];
156 9c5d8ecc 2023-05-02 benni char label[16];
157 9c5d8ecc 2023-05-02 benni char name[4];
158 9c5d8ecc 2023-05-02 benni uint64_t size;
159 9c5d8ecc 2023-05-02 benni uint64_t used;
160 9c5d8ecc 2023-05-02 benni uint8_t num_parts;
161 9c5d8ecc 2023-05-02 benni struct my_partinfo parts[MAXPARTITIONS];
164 9c5d8ecc 2023-05-02 benni static void print_header (int fields)
166 9c5d8ecc 2023-05-02 benni if (fields & FIELD_NAME)
167 9c5d8ecc 2023-05-02 benni printf ("%-6s ", "NAME");
169 9c5d8ecc 2023-05-02 benni if (fields & FIELD_SIZE)
170 9c5d8ecc 2023-05-02 benni printf ("%-4s ", "SIZE");
172 9c5d8ecc 2023-05-02 benni if (fields & FIELD_USED)
173 9c5d8ecc 2023-05-02 benni printf ("%-4s ", "USED");
175 9c5d8ecc 2023-05-02 benni if (fields & FIELD_FREE)
176 9c5d8ecc 2023-05-02 benni printf ("%-4s ", "FREE");
178 9c5d8ecc 2023-05-02 benni if (fields & FIELD_TYPE)
179 9c5d8ecc 2023-05-02 benni printf ("%-8s ", "TYPE");
181 2b650bba 2023-05-04 benni if (fields & FIELD_LMNT)
182 2b650bba 2023-05-04 benni printf ("LABEL/MOUNT ");
184 9c5d8ecc 2023-05-02 benni putchar ('\n');
187 9c5d8ecc 2023-05-02 benni static void print_part (const struct my_partinfo *part, int fields)
189 9c5d8ecc 2023-05-02 benni if (fields & FIELD_NAME)
190 9c5d8ecc 2023-05-02 benni printf (" %s ", part->name);
192 9c5d8ecc 2023-05-02 benni if (fields & FIELD_SIZE)
193 9c5d8ecc 2023-05-02 benni print_size (part->size);
195 9c5d8ecc 2023-05-02 benni if (fields & FIELD_USED) {
196 9c5d8ecc 2023-05-02 benni if (part->fssize) {
197 9c5d8ecc 2023-05-02 benni print_size (part->fssize - part->free);
199 9c5d8ecc 2023-05-02 benni printf (" N/A ");
203 9c5d8ecc 2023-05-02 benni if (fields & FIELD_FREE) {
204 9c5d8ecc 2023-05-02 benni if (part->fssize) {
205 9c5d8ecc 2023-05-02 benni print_size (part->free);
207 9c5d8ecc 2023-05-02 benni printf (" N/A ");
211 9c5d8ecc 2023-05-02 benni if (fields & FIELD_TYPE)
212 9c5d8ecc 2023-05-02 benni printf ("%-8.16s ", part->fstype);
214 2b650bba 2023-05-04 benni if (fields & FIELD_LMNT && part->mount)
215 9c5d8ecc 2023-05-02 benni printf ("%s ", part->mount);
217 9c5d8ecc 2023-05-02 benni putchar ('\n');
220 9c5d8ecc 2023-05-02 benni static void print_disk (const struct my_diskinfo *disk, int fields)
222 9c5d8ecc 2023-05-02 benni if (fields & FIELD_NAME)
223 9c5d8ecc 2023-05-02 benni printf ("%s ", disk->name);
225 9c5d8ecc 2023-05-02 benni if (fields & FIELD_SIZE)
226 9c5d8ecc 2023-05-02 benni print_size (disk->size);
228 9c5d8ecc 2023-05-02 benni if (fields & FIELD_USED)
229 9c5d8ecc 2023-05-02 benni print_size (disk->used);
231 9c5d8ecc 2023-05-02 benni if (fields & FIELD_FREE)
232 9c5d8ecc 2023-05-02 benni print_size (disk->size - disk->used);
234 9c5d8ecc 2023-05-02 benni // Pad only upto 8 characters because most disk types are <=8 bytes long.
235 9c5d8ecc 2023-05-02 benni if (fields & FIELD_TYPE)
236 9c5d8ecc 2023-05-02 benni printf ("%-8.16s ", disk->type);
238 2b650bba 2023-05-04 benni if (fields & FIELD_LMNT)
239 2b650bba 2023-05-04 benni printf ("%.16s ", disk->label);
241 9c5d8ecc 2023-05-02 benni putchar ('\n');
243 9c5d8ecc 2023-05-02 benni for (uint8_t i = 0; i < disk->num_parts; ++i)
244 9c5d8ecc 2023-05-02 benni print_part (&disk->parts[i], fields);
247 9c5d8ecc 2023-05-02 benni static const struct statfs *find_mount (const char *dev)
249 9c5d8ecc 2023-05-02 benni static struct statfs *mounts = NULL;
250 9c5d8ecc 2023-05-02 benni static int n_mounts;
252 9c5d8ecc 2023-05-02 benni if (!mounts) {
253 9c5d8ecc 2023-05-02 benni n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
254 9c5d8ecc 2023-05-02 benni if (n_mounts == 0)
255 9c5d8ecc 2023-05-02 benni die ("getmntinfo()");
258 9c5d8ecc 2023-05-02 benni for (int i = 0; i < n_mounts; ++i) {
259 9c5d8ecc 2023-05-02 benni if (!strcmp (dev, mounts[i].f_mntfromname))
260 9c5d8ecc 2023-05-02 benni return &mounts[i];
263 9c5d8ecc 2023-05-02 benni return NULL;
266 9c5d8ecc 2023-05-02 benni static struct my_diskinfo read_disk (const char *name)
268 9c5d8ecc 2023-05-02 benni struct my_diskinfo disk;
269 9c5d8ecc 2023-05-02 benni struct disklabel label;
271 9c5d8ecc 2023-05-02 benni bzero (&disk, sizeof disk);
273 9c5d8ecc 2023-05-02 benni { // Read disklabel.
274 9c5d8ecc 2023-05-02 benni char path[5 + 4 + 1];
277 9c5d8ecc 2023-05-02 benni snprintf (path, sizeof path, "/dev/%.3sc", name);
278 9c5d8ecc 2023-05-02 benni fd = open (path, O_RDONLY);
279 9c5d8ecc 2023-05-02 benni if (fd < 0)
280 9c5d8ecc 2023-05-02 benni die ("open(%s)", path);
281 9c5d8ecc 2023-05-02 benni if (ioctl (fd, DIOCGDINFO, &label) < 0)
282 9c5d8ecc 2023-05-02 benni die ("ioctl(%s, DIOCGDINFO)", path);
283 9c5d8ecc 2023-05-02 benni close (fd);
286 9c5d8ecc 2023-05-02 benni memcpy (disk.name, name, 3);
287 9c5d8ecc 2023-05-02 benni disk.name[3] = '\0';
288 9c5d8ecc 2023-05-02 benni disk.size = DL_GETDSIZE (&label) * label.d_secsize;
289 9c5d8ecc 2023-05-02 benni memcpy (disk.type, label.d_typename, 16);
290 9c5d8ecc 2023-05-02 benni stripdisk (disk.type);
291 9c5d8ecc 2023-05-02 benni memcpy (disk.label, label.d_packname, 16);
292 9c5d8ecc 2023-05-02 benni disk.num_parts = 0;
294 9c5d8ecc 2023-05-02 benni for (uint16_t i = 0; i < label.d_npartitions; ++i) {
295 9c5d8ecc 2023-05-02 benni const struct partition *p = &label.d_partitions[i];
296 9c5d8ecc 2023-05-02 benni const struct statfs *mnt;
297 9c5d8ecc 2023-05-02 benni struct my_partinfo part;
298 9c5d8ecc 2023-05-02 benni char path[5 + 4 + 1];
300 9c5d8ecc 2023-05-02 benni bzero (&part, sizeof part);
302 9c5d8ecc 2023-05-02 benni part.size = DL_GETPSIZE (p) * label.d_secsize;
304 9c5d8ecc 2023-05-02 benni if (!part.size)
307 9c5d8ecc 2023-05-02 benni memcpy (part.name, disk.name, 3);
308 9c5d8ecc 2023-05-02 benni part.name[3] = 'a' + i;
309 9c5d8ecc 2023-05-02 benni part.name[4] = '\0';
311 9c5d8ecc 2023-05-02 benni snprintf (path, sizeof path, "/dev/%s", part.name);
312 9c5d8ecc 2023-05-02 benni mnt = find_mount (path);
314 9c5d8ecc 2023-05-02 benni if (mnt) {
315 9c5d8ecc 2023-05-02 benni const uint64_t bs = mnt->f_bsize;
316 9c5d8ecc 2023-05-02 benni part.mount = mnt->f_mntonname;
317 9c5d8ecc 2023-05-02 benni part.fssize = mnt->f_blocks * bs;
318 9c5d8ecc 2023-05-02 benni part.free = mnt->f_bfree * bs;
321 9c5d8ecc 2023-05-02 benni part.fstype = fstypenames[p->p_fstype];
322 9c5d8ecc 2023-05-02 benni if (i != 2)
323 9c5d8ecc 2023-05-02 benni disk.used += part.size;
324 9c5d8ecc 2023-05-02 benni disk.parts[disk.num_parts++] = part;
327 9c5d8ecc 2023-05-02 benni return disk;
330 9c5d8ecc 2023-05-02 benni static int usage (void)
332 2b650bba 2023-05-04 benni fputs ("Usage: lsblk [-Vanu]\n", stderr);
336 33bf1ca4 2023-04-08 benni int main (int argc, char *argv[])
338 8380cc51 2023-05-01 benni int option;
339 8380cc51 2023-05-01 benni bool header = true;
340 9c5d8ecc 2023-05-02 benni int fields = FIELD_DEFAULT;
342 aff15c23 2023-04-30 benni if (unveil ("/dev", "r") == -1)
343 aff15c23 2023-04-30 benni die ("unveil(/dev)");
345 aff15c23 2023-04-30 benni if (unveil (NULL, NULL) == -1)
346 aff15c23 2023-04-30 benni die ("unveil()");
348 2b650bba 2023-05-04 benni while ((option = getopt (argc, argv, ":Vanu")) != -1) {
349 33bf1ca4 2023-04-08 benni switch (option) {
351 33bf1ca4 2023-04-08 benni puts ("lsblk-" VERSION);
354 bf0e6461 2023-05-02 benni fields = -1;
357 bf0e6461 2023-05-02 benni header = false;
360 9c5d8ecc 2023-05-02 benni fields |= FIELD_USED | FIELD_FREE;
363 33bf1ca4 2023-04-08 benni return usage ();
367 33bf1ca4 2023-04-08 benni if (argc != optind)
368 33bf1ca4 2023-04-08 benni return usage ();
370 33bf1ca4 2023-04-08 benni struct statfs *mounts;
371 33bf1ca4 2023-04-08 benni char *names = disknames ();
373 33bf1ca4 2023-04-08 benni if (pledge ("stdio rpath disklabel", NULL) == -1)
374 33bf1ca4 2023-04-08 benni die ("pledge()");
376 33bf1ca4 2023-04-08 benni const int n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
377 33bf1ca4 2023-04-08 benni if (n_mounts == 0)
378 33bf1ca4 2023-04-08 benni die ("getmntinfo()");
380 9c5d8ecc 2023-05-02 benni size_t cap_disks = 10;
381 9c5d8ecc 2023-05-02 benni size_t num_disks = 0;
382 9c5d8ecc 2023-05-02 benni struct my_diskinfo *disks = calloc (cap_disks, sizeof (struct my_diskinfo));
384 9c5d8ecc 2023-05-02 benni for (char *disk; (disk = strsep (&names, ",")) != NULL; ) {
385 9c5d8ecc 2023-05-02 benni if (num_disks == cap_disks) {
386 9c5d8ecc 2023-05-02 benni cap_disks = cap_disks * 13 / 8;
387 9c5d8ecc 2023-05-02 benni disks = reallocarray (disks, cap_disks, sizeof (struct my_diskinfo));
389 9c5d8ecc 2023-05-02 benni disks[num_disks++] = read_disk (disk);
392 33bf1ca4 2023-04-08 benni free (names);
394 9c5d8ecc 2023-05-02 benni if (header)
395 9c5d8ecc 2023-05-02 benni print_header (fields);
397 9c5d8ecc 2023-05-02 benni for (size_t i = 0; i < num_disks; ++i) {
398 9c5d8ecc 2023-05-02 benni print_disk (&disks[i], fields);