]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/lsblk.c
lsblk: add inverse tree support (-s)
[thirdparty/util-linux.git] / misc-utils / lsblk.c
CommitLineData
2a4c734b 1/*
f77fa578 2 * lsblk(8) - list block devices
2a4c734b 3 *
270e66ec 4 * Copyright (C) 2010,2011 Red Hat, Inc. All rights reserved.
2a4c734b
MB
5 * Written by Milan Broz <mbroz@redhat.com>
6 * Karel Zak <kzak@redhat.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it would be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
7cebf0bb
SK
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2a4c734b
MB
21 */
22
23#include <stdio.h>
24#include <errno.h>
25#include <getopt.h>
2a4c734b
MB
26#include <stdlib.h>
27#include <unistd.h>
28#include <stdint.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <dirent.h>
32#include <fcntl.h>
33#include <string.h>
2a4c734b
MB
34#include <sys/ioctl.h>
35#include <inttypes.h>
36#include <stdarg.h>
37#include <locale.h>
38#include <pwd.h>
39#include <grp.h>
18eac5ba 40#include <ctype.h>
2a4c734b
MB
41
42#include <blkid.h>
43
f17f5f48
IM
44#ifdef HAVE_LIBUDEV
45#include <libudev.h>
46#endif
47
2a4c734b
MB
48#include <assert.h>
49
50#include "pathnames.h"
51#include "blkdev.h"
52#include "canonicalize.h"
53#include "ismounted.h"
54#include "nls.h"
55#include "tt.h"
56#include "xalloc.h"
57#include "strutils.h"
eb76ca98 58#include "c.h"
766ab9b0 59#include "sysfs.h"
2a4c734b
MB
60
61/* column IDs */
62enum {
63 COL_NAME = 0,
64 COL_KNAME,
65 COL_MAJMIN,
66 COL_FSTYPE,
67 COL_TARGET,
68 COL_LABEL,
69 COL_UUID,
70 COL_RO,
627970a7 71 COL_RM,
2a4c734b
MB
72 COL_MODEL,
73 COL_SIZE,
01e487c0 74 COL_STATE,
2a4c734b
MB
75 COL_OWNER,
76 COL_GROUP,
77 COL_MODE,
78 COL_ALIOFF,
79 COL_MINIO,
80 COL_OPTIO,
81 COL_PHYSEC,
82 COL_LOGSEC,
83 COL_ROTA,
84 COL_SCHED,
64c9e22a 85 COL_RQ_SIZE,
18eac5ba 86 COL_TYPE,
2d232246
MP
87 COL_DALIGN,
88 COL_DGRAN,
89 COL_DMAX,
90 COL_DZERO,
2a4c734b
MB
91};
92
93/* column names */
94struct colinfo {
95 const char *name; /* header */
96 double whint; /* width hint (N < 1 is in percent of termwidth) */
97 int flags; /* TT_FL_* */
98 const char *help;
99};
100
101/* columns descriptions */
507d39c2 102static struct colinfo infos[] = {
7aa69cf2 103 [COL_NAME] = { "NAME", 0.25, TT_FL_TREE | TT_FL_NOEXTREMES, N_("device name") },
e22d8b95 104 [COL_KNAME] = { "KNAME", 0.3, 0, N_("internal kernel device name") },
2a4c734b
MB
105 [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") },
106 [COL_FSTYPE] = { "FSTYPE", 0.1, TT_FL_TRUNC, N_("filesystem type") },
107 [COL_TARGET] = { "MOUNTPOINT", 0.10, TT_FL_TRUNC, N_("where the device is mounted") },
108 [COL_LABEL] = { "LABEL", 0.1, 0, N_("filesystem LABEL") },
109 [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") },
110 [COL_RO] = { "RO", 1, TT_FL_RIGHT, N_("read-only device") },
627970a7 111 [COL_RM] = { "RM", 1, TT_FL_RIGHT, N_("removable device") },
2a4c734b
MB
112 [COL_ROTA] = { "ROTA", 1, TT_FL_RIGHT, N_("rotational device") },
113 [COL_MODEL] = { "MODEL", 0.1, TT_FL_TRUNC, N_("device identifier") },
114 [COL_SIZE] = { "SIZE", 6, TT_FL_RIGHT, N_("size of the device") },
01e487c0 115 [COL_STATE] = { "STATE", 7, TT_FL_TRUNC, N_("state of the device") },
2a4c734b
MB
116 [COL_OWNER] = { "OWNER", 0.1, TT_FL_TRUNC, N_("user name"), },
117 [COL_GROUP] = { "GROUP", 0.1, TT_FL_TRUNC, N_("group name") },
118 [COL_MODE] = { "MODE", 10, 0, N_("device node permissions") },
119 [COL_ALIOFF] = { "ALIGNMENT", 6, TT_FL_RIGHT, N_("alignment offset") },
120 [COL_MINIO] = { "MIN-IO", 6, TT_FL_RIGHT, N_("minimum I/O size") },
121 [COL_OPTIO] = { "OPT-IO", 6, TT_FL_RIGHT, N_("optimal I/O size") },
122 [COL_PHYSEC] = { "PHY-SEC", 7, TT_FL_RIGHT, N_("physical sector size") },
123 [COL_LOGSEC] = { "LOG-SEC", 7, TT_FL_RIGHT, N_("logical sector size") },
18eac5ba 124 [COL_SCHED] = { "SCHED", 0.1, 0, N_("I/O scheduler name") },
64c9e22a 125 [COL_RQ_SIZE]= { "RQ-SIZE", 5, TT_FL_RIGHT, N_("request queue size") },
2d232246
MP
126 [COL_TYPE] = { "TYPE", 4, 0, N_("device type") },
127 [COL_DALIGN] = { "DISC-ALN", 6, TT_FL_RIGHT, N_("discard alignment offset") },
128 [COL_DGRAN] = { "DISC-GRAN", 6, TT_FL_RIGHT, N_("discard granularity") },
129 [COL_DMAX] = { "DISC-MAX", 6, TT_FL_RIGHT, N_("discard max bytes") },
130 [COL_DZERO] = { "DISC-ZERO", 1, TT_FL_RIGHT, N_("discard zeroes data") },
2a4c734b
MB
131};
132
133struct lsblk {
9feec79c
KZ
134 struct tt *tt; /* output table */
135 unsigned int all_devices:1; /* print all devices */
136 unsigned int bytes:1; /* print SIZE in bytes */
09a71aa1 137 unsigned int inverse:1; /* print inverse dependencies */
9feec79c 138 unsigned int nodeps:1; /* don't print slaves/holders */
2a4c734b
MB
139};
140
141struct lsblk *lsblk; /* global handler */
507d39c2
DB
142
143#define NCOLS ARRAY_SIZE(infos)
144int columns[NCOLS];/* enabled columns */
2a4c734b
MB
145int ncolumns; /* number of enabled columns */
146
6aace32f
KZ
147int excludes[256];
148size_t nexcludes;
2a4c734b
MB
149
150struct blkdev_cxt {
151 struct blkdev_cxt *parent;
152
153 struct tt_line *tt_line;
154 struct stat st;
155
156 char *name; /* kernel name in /sys/block */
157 char *dm_name; /* DM name (dm/block) */
158
159 char *filename; /* path to device node */
766ab9b0
KZ
160
161 struct sysfs_cxt sysfs;
2a4c734b
MB
162
163 int partition; /* is partition? TRUE/FALSE */
164
165 int probed; /* already probed */
166 char *fstype; /* detected fs, NULL or "?" if cannot detect */
167 char *uuid; /* UUID of device / filesystem */
168 char *label; /* FS label */
169
09a71aa1 170 int npartitions; /* # of partitions this device has */
2a4c734b 171 int nholders; /* # of devices mapped directly to this device
09a71aa1 172 * /sys/block/.../holders */
2a4c734b
MB
173 int nslaves; /* # of devices this device maps to */
174 int maj, min; /* devno */
98055888 175 int discard; /* supports discard */
2a4c734b
MB
176
177 uint64_t size; /* device size */
178};
179
180static int is_maj_excluded(int maj)
181{
6aace32f 182 size_t i;
2a4c734b
MB
183
184 assert(ARRAY_SIZE(excludes) > nexcludes);
185
186 for (i = 0; i < nexcludes; i++)
187 if (excludes[i] == maj)
188 return 1;
189 return 0;
190}
191
2a4c734b
MB
192/* array with IDs of enabled columns */
193static int get_column_id(int num)
194{
507d39c2 195 assert(ARRAY_SIZE(columns) == NCOLS);
2a4c734b 196 assert(num < ncolumns);
507d39c2 197 assert(columns[num] < (int) NCOLS);
2a4c734b
MB
198 return columns[num];
199}
200
201static struct colinfo *get_column_info(int num)
202{
203 return &infos[ get_column_id(num) ];
204}
205
2a4c734b
MB
206static int column_name_to_id(const char *name, size_t namesz)
207{
507d39c2 208 size_t i;
2a4c734b 209
507d39c2 210 for (i = 0; i < NCOLS; i++) {
2a4c734b
MB
211 const char *cn = infos[i].name;
212
213 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
214 return i;
215 }
216 warnx(_("unknown column: %s"), name);
217 return -1;
218}
219
220static void reset_blkdev_cxt(struct blkdev_cxt *cxt)
221{
222 if (!cxt)
223 return;
224 free(cxt->name);
225 free(cxt->dm_name);
226 free(cxt->filename);
227 free(cxt->fstype);
228 free(cxt->uuid);
229 free(cxt->label);
230
766ab9b0 231 sysfs_deinit(&cxt->sysfs);
2a4c734b
MB
232
233 memset(cxt, 0, sizeof(*cxt));
234}
235
236static int is_dm(const char *name)
237{
238 return strncmp(name, "dm-", 3) ? 0 : 1;
239}
240
241static struct dirent *xreaddir(DIR *dp)
242{
243 struct dirent *d;
244
245 assert(dp);
246
247 while ((d = readdir(dp))) {
248 if (!strcmp(d->d_name, ".") ||
249 !strcmp(d->d_name, ".."))
250 continue;
251
252 /* blacklist here? */
253 break;
254 }
255 return d;
256}
257
2a4c734b
MB
258static char *get_device_path(struct blkdev_cxt *cxt)
259{
260 char path[PATH_MAX];
261
262 assert(cxt);
263 assert(cxt->name);
264
265 if (is_dm(cxt->name))
266 return canonicalize_dm_name(cxt->name);
267
268 snprintf(path, sizeof(path), "/dev/%s", cxt->name);
269 return xstrdup(path);
270}
271
2a4c734b
MB
272static char *get_device_mountpoint(struct blkdev_cxt *cxt)
273{
274 int fl = 0;
275 char mnt[PATH_MAX];
276
270e66ec
MB
277 assert(cxt);
278 assert(cxt->filename);
279
2a4c734b
MB
280 *mnt = '\0';
281
282 /*
283 * TODO: use libmount and parse /proc/mountinfo only once
284 */
285 if (check_mount_point(cxt->filename, &fl, mnt, sizeof(mnt)) == 0 &&
286 (fl & MF_MOUNTED)) {
287 if (fl & MF_SWAP)
288 strcpy(mnt, "[SWAP]");
289 }
290 return strlen(mnt) ? xstrdup(mnt) : NULL;
291}
292
f17f5f48
IM
293#ifndef HAVE_LIBUDEV
294static int probe_device_by_udev(struct blkdev_cxt *cxt
295 __attribute__((__unused__)))
296{
297 return -1;
298}
299#else
300static int probe_device_by_udev(struct blkdev_cxt *cxt)
301{
302 struct udev *udev;
303 struct udev_device *dev;
304
305 udev = udev_new();
306 if (!udev)
307 return -1;
308
309 dev = udev_device_new_from_subsystem_sysname(udev, "block", cxt->name);
310 if (dev) {
311 const char *data;
312
313 if ((data = udev_device_get_property_value(dev, "ID_FS_LABEL")))
314 cxt->label = xstrdup(data);
315 if ((data = udev_device_get_property_value(dev, "ID_FS_TYPE")))
316 cxt->fstype = xstrdup(data);
317 if ((data = udev_device_get_property_value(dev, "ID_FS_UUID")))
318 cxt->uuid = xstrdup(data);
319
320 udev_device_unref(dev);
321 }
322
323 udev_unref(udev);
324 return 0;
325}
326#endif /* HAVE_LIBUDEV */
327
2a4c734b
MB
328static void probe_device(struct blkdev_cxt *cxt)
329{
2a4c734b
MB
330 blkid_probe pr = NULL;
331
332 if (cxt->probed)
333 return;
f17f5f48 334
2a4c734b
MB
335 cxt->probed = 1;
336
337 if (!cxt->size)
338 return;
339
f17f5f48
IM
340 /* try udev DB */
341 if (probe_device_by_udev(cxt) == 0)
342 return; /* success */
343
344 /* try libblkid */
2a4c734b
MB
345 pr = blkid_new_probe_from_filename(cxt->filename);
346 if (!pr)
347 return;
348
349 /* TODO: we have to enable partitions probing to avoid conflicts
350 * between raids and PT -- see blkid(8) code for more details
351 */
352 blkid_probe_enable_superblocks(pr, 1);
353 blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_LABEL |
354 BLKID_SUBLKS_UUID |
355 BLKID_SUBLKS_TYPE);
356 if (!blkid_do_safeprobe(pr)) {
357 const char *data = NULL;
358
359 if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
360 cxt->fstype = xstrdup(data);
361 if (!blkid_probe_lookup_value(pr, "UUID", &data, NULL))
362 cxt->uuid = xstrdup(data);
363 if (!blkid_probe_lookup_value(pr, "LABEL", &data, NULL))
364 cxt->label = xstrdup(data);
365 }
366
2a4c734b
MB
367 blkid_free_probe(pr);
368 return;
369}
370
371static int is_readonly_device(struct blkdev_cxt *cxt)
372{
373 int fd, ro = 0;
374
766ab9b0 375 if (sysfs_scanf(&cxt->sysfs, "ro", "%d", &ro) == 0)
2a4c734b
MB
376 return ro;
377
378 /* fallback if "ro" attribute does not exist */
379 fd = open(cxt->filename, O_RDONLY);
380 if (fd != -1) {
381 ioctl(fd, BLKROGET, &ro);
382 close(fd);
383 }
384 return ro;
385}
386
387static char *get_scheduler(struct blkdev_cxt *cxt)
388{
766ab9b0 389 char *str = sysfs_strdup(&cxt->sysfs, "queue/scheduler");
2a4c734b
MB
390 char *p, *res = NULL;
391
392 if (!str)
393 return NULL;
394 p = strchr(str, '[');
395 if (p) {
396 res = p + 1;
397 p = strchr(res, ']');
398 if (p) {
399 *p = '\0';
400 res = xstrdup(res);
401 } else
402 res = NULL;
403 }
404 free(str);
405 return res;
406}
407
18eac5ba
MB
408static char *get_type(struct blkdev_cxt *cxt)
409{
410 char *res = NULL, *p;
411
412 if (is_dm(cxt->name)) {
766ab9b0 413 char *dm_uuid = sysfs_strdup(&cxt->sysfs, "dm/uuid");
18eac5ba
MB
414
415 /* The DM_UUID prefix should be set to subsystem owning
416 * the device - LVM, CRYPT, DMRAID, MPATH, PART */
417 if (dm_uuid) {
418 char *tmp = dm_uuid;
419 char *dm_uuid_prefix = strsep(&tmp, "-");
420
421 if (dm_uuid_prefix) {
422 /* kpartx hack to remove partition number */
423 if (strncasecmp(dm_uuid_prefix, "part", 4) == 0)
424 dm_uuid_prefix[4] = '\0';
425
426 res = xstrdup(dm_uuid_prefix);
427 }
428 }
429
430 free(dm_uuid);
431 if (!res)
432 /* No UUID or no prefix - just mark it as DM device */
433 res = xstrdup("dm");
434
435 } else if (!strncmp(cxt->name, "loop", 4)) {
436 res = xstrdup("loop");
437
438 } else if (!strncmp(cxt->name, "md", 2)) {
766ab9b0 439 char *md_level = sysfs_strdup(&cxt->sysfs, "md/level");
18eac5ba
MB
440 res = md_level ? md_level : xstrdup("md");
441
442 } else {
443 const char *type = cxt->partition ? "part" : "disk";
90e9fcda 444 int x = 0;
18eac5ba 445
90e9fcda
KZ
446 sysfs_read_int(&cxt->sysfs, "device/type", &x);
447
448 switch (x) {
18eac5ba
MB
449 case 0x0c: /* TYPE_RAID */
450 type = "raid"; break;
451 case 0x01: /* TYPE_TAPE */
452 type = "raid"; break;
453 case 0x04: /* TYPE_WORM */
454 case 0x05: /* TYPE_ROM */
455 type = "rom"; break;
456 case 0x07: /* TYPE_MOD */
457 type = "mo-disk"; break;
458 case 0x0e: /* TYPE_RBC */
459 type = "rbc"; break;
460 }
461
90e9fcda 462 res = xstrdup(type);
18eac5ba
MB
463 }
464
465 for (p = res; p && *p; p++)
466 *p = tolower((unsigned char) *p);
467 return res;
468}
469
2a4c734b
MB
470static void set_tt_data(struct blkdev_cxt *cxt, int col, int id, struct tt_line *ln)
471{
472 char buf[1024];
9cfe1b9c 473 char *p = NULL;
1dc42eb6 474 int st_rc = 0;
2a4c734b
MB
475
476 if (!cxt->st.st_rdev && (id == COL_OWNER || id == COL_GROUP ||
477 id == COL_MODE))
1dc42eb6 478 st_rc = stat(cxt->filename, &cxt->st);
2a4c734b
MB
479
480 switch(id) {
481 case COL_NAME:
482 if (cxt->dm_name) {
205eb002
KZ
483 if ((lsblk->tt->flags & TT_FL_RAW) ||
484 (lsblk->tt->flags & TT_FL_EXPORT))
485 tt_line_set_data(ln, col, xstrdup(cxt->dm_name));
486 else {
487 snprintf(buf, sizeof(buf), "%s (%s)",
2a4c734b 488 cxt->dm_name, cxt->name);
205eb002
KZ
489 tt_line_set_data(ln, col, xstrdup(buf));
490 }
2a4c734b
MB
491 break;
492 }
493 case COL_KNAME:
494 tt_line_set_data(ln, col, xstrdup(cxt->name));
495 break;
496 case COL_OWNER:
497 {
1dc42eb6 498 struct passwd *pw = st_rc ? NULL : getpwuid(cxt->st.st_uid);
2a4c734b
MB
499 if (pw)
500 tt_line_set_data(ln, col, xstrdup(pw->pw_name));
501 break;
502 }
503 case COL_GROUP:
504 {
1dc42eb6 505 struct group *gr = st_rc ? NULL : getgrgid(cxt->st.st_gid);
2a4c734b
MB
506 if (gr)
507 tt_line_set_data(ln, col, xstrdup(gr->gr_name));
508 break;
509 }
510 case COL_MODE:
511 {
512 char md[11];
1dc42eb6
KZ
513
514 if (!st_rc) {
515 strmode(cxt->st.st_mode, md);
516 tt_line_set_data(ln, col, xstrdup(md));
517 }
2a4c734b
MB
518 break;
519 }
520 case COL_MAJMIN:
6ea1bdd3
KZ
521 if ((lsblk->tt->flags & TT_FL_RAW) ||
522 (lsblk->tt->flags & TT_FL_EXPORT))
2a4c734b
MB
523 snprintf(buf, sizeof(buf), "%u:%u", cxt->maj, cxt->min);
524 else
525 snprintf(buf, sizeof(buf), "%3u:%-3u", cxt->maj, cxt->min);
526 tt_line_set_data(ln, col, xstrdup(buf));
527 break;
528 case COL_FSTYPE:
529 probe_device(cxt);
530 if (cxt->fstype)
531 tt_line_set_data(ln, col, xstrdup(cxt->fstype));
532 break;
533 case COL_TARGET:
09a71aa1 534 if (!(cxt->nholders + cxt->npartitions)) {
2a4c734b
MB
535 p = get_device_mountpoint(cxt);
536 if (p)
537 tt_line_set_data(ln, col, p);
538 }
539 break;
540 case COL_LABEL:
541 probe_device(cxt);
542 if (cxt->label)
543 tt_line_set_data(ln, col, xstrdup(cxt->label));
544 break;
545 case COL_UUID:
546 probe_device(cxt);
547 if (cxt->uuid)
548 tt_line_set_data(ln, col, xstrdup(cxt->uuid));
549 break;
550 case COL_RO:
551 tt_line_set_data(ln, col, is_readonly_device(cxt) ?
552 xstrdup("1") : xstrdup("0"));
553 break;
627970a7 554 case COL_RM:
766ab9b0 555 p = sysfs_strdup(&cxt->sysfs, "removable");
09a71aa1
PR
556 if (!p && cxt->sysfs.parent)
557 p = sysfs_strdup(cxt->sysfs.parent, "removable");
2a4c734b
MB
558 if (p)
559 tt_line_set_data(ln, col, p);
560 break;
561 case COL_ROTA:
766ab9b0 562 p = sysfs_strdup(&cxt->sysfs, "queue/rotational");
2a4c734b
MB
563 if (p)
564 tt_line_set_data(ln, col, p);
565 break;
566 case COL_MODEL:
567 if (!cxt->partition && cxt->nslaves == 0) {
766ab9b0 568 p = sysfs_strdup(&cxt->sysfs, "device/model");
2a4c734b
MB
569 if (p)
570 tt_line_set_data(ln, col, p);
571 }
572 break;
573 case COL_SIZE:
574 if (cxt->size) {
575 if (lsblk->bytes) {
576 if (asprintf(&p, "%jd", cxt->size) < 0)
577 p = NULL;
578 } else
5d2a9849 579 p = size_to_human_string(SIZE_SUFFIX_1LETTER, cxt->size);
2a4c734b
MB
580 if (p)
581 tt_line_set_data(ln, col, p);
582 }
583 break;
01e487c0
MB
584 case COL_STATE:
585 if (!cxt->partition && !cxt->dm_name) {
586 p = sysfs_strdup(&cxt->sysfs, "device/state");
587 } else if (cxt->dm_name) {
588 int x = 0;
589 if (sysfs_read_int(&cxt->sysfs, "dm/suspended", &x) == 0)
590 p = x ? xstrdup("suspended") : xstrdup("running");
591 }
592 if (p)
593 tt_line_set_data(ln, col, p);
594 break;
2a4c734b 595 case COL_ALIOFF:
766ab9b0 596 p = sysfs_strdup(&cxt->sysfs, "alignment_offset");
2a4c734b
MB
597 if (p)
598 tt_line_set_data(ln, col, p);
599 break;
600 case COL_MINIO:
766ab9b0 601 p = sysfs_strdup(&cxt->sysfs, "queue/minimum_io_size");
2a4c734b
MB
602 if (p)
603 tt_line_set_data(ln, col, p);
604 break;
605 case COL_OPTIO:
766ab9b0 606 p = sysfs_strdup(&cxt->sysfs, "queue/optimal_io_size");
2a4c734b
MB
607 if (p)
608 tt_line_set_data(ln, col, p);
609 break;
610 case COL_PHYSEC:
766ab9b0 611 p = sysfs_strdup(&cxt->sysfs, "queue/physical_block_size");
2a4c734b
MB
612 if (p)
613 tt_line_set_data(ln, col, p);
614 break;
615 case COL_LOGSEC:
766ab9b0 616 p = sysfs_strdup(&cxt->sysfs, "queue/logical_block_size");
2a4c734b
MB
617 if (p)
618 tt_line_set_data(ln, col, p);
619 break;
620 case COL_SCHED:
621 p = get_scheduler(cxt);
622 if (p)
623 tt_line_set_data(ln, col, p);
624 break;
64c9e22a
MB
625 case COL_RQ_SIZE:
626 p = sysfs_strdup(&cxt->sysfs, "queue/nr_requests");
627 if (p)
628 tt_line_set_data(ln, col, p);
629 break;
18eac5ba
MB
630 case COL_TYPE:
631 p = get_type(cxt);
632 if (p)
633 tt_line_set_data(ln, col, p);
634 break;
2d232246 635 case COL_DALIGN:
766ab9b0 636 p = sysfs_strdup(&cxt->sysfs, "discard_alignment");
98055888 637 if (cxt->discard && p)
2d232246 638 tt_line_set_data(ln, col, p);
98055888
MP
639 else
640 tt_line_set_data(ln, col, "0");
2d232246
MP
641 break;
642 case COL_DGRAN:
9cfe1b9c
KZ
643 if (lsblk->bytes)
644 p = sysfs_strdup(&cxt->sysfs, "queue/discard_granularity");
645 else {
646 uint64_t x;
647
648 if (sysfs_read_u64(&cxt->sysfs,
649 "queue/discard_granularity", &x) == 0)
5d2a9849 650 p = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
9cfe1b9c 651 }
2d232246
MP
652 if (p)
653 tt_line_set_data(ln, col, p);
654 break;
655 case COL_DMAX:
9cfe1b9c
KZ
656 if (lsblk->bytes)
657 p = sysfs_strdup(&cxt->sysfs, "queue/discard_max_bytes");
658 else {
659 uint64_t x;
660
661 if (sysfs_read_u64(&cxt->sysfs,
662 "queue/discard_max_bytes", &x) == 0)
5d2a9849 663 p = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
9cfe1b9c 664 }
2d232246
MP
665 if (p)
666 tt_line_set_data(ln, col, p);
667 break;
668 case COL_DZERO:
766ab9b0 669 p = sysfs_strdup(&cxt->sysfs, "queue/discard_zeroes_data");
98055888 670 if (cxt->discard && p)
2d232246 671 tt_line_set_data(ln, col, p);
98055888
MP
672 else
673 tt_line_set_data(ln, col, "0");
2d232246 674 break;
2a4c734b
MB
675 };
676}
677
678static void print_device(struct blkdev_cxt *cxt, struct tt_line *tt_parent)
679{
680 int i;
681
682 cxt->tt_line = tt_add_line(lsblk->tt, tt_parent);
683
684 for (i = 0; i < ncolumns; i++)
685 set_tt_data(cxt, i, get_column_id(i), cxt->tt_line);
686}
687
688static int set_cxt(struct blkdev_cxt *cxt,
689 struct blkdev_cxt *parent,
09a71aa1
PR
690 struct blkdev_cxt *wholedisk,
691 const char *name)
2a4c734b 692{
766ab9b0 693 dev_t devno;
2a4c734b
MB
694
695 cxt->parent = parent;
696 cxt->name = xstrdup(name);
09a71aa1 697 cxt->partition = wholedisk != NULL;
2a4c734b
MB
698
699 cxt->filename = get_device_path(cxt);
270e66ec
MB
700 if (!cxt->filename) {
701 warnx(_("%s: failed to get device path"), name);
702 return -1;
703 }
2a4c734b 704
09a71aa1
PR
705 devno = sysfs_devname_to_devno(name, wholedisk ? wholedisk->name : NULL);
706
270e66ec
MB
707 if (!devno) {
708 warnx(_("%s: unknown device name"), name);
709 return -1;
710 }
2a4c734b 711
09a71aa1
PR
712 if (lsblk->inverse) {
713 if (sysfs_init(&cxt->sysfs, devno, wholedisk ? &wholedisk->sysfs : NULL)) {
714 warnx(_("%s: failed to initialize sysfs handler"), name);
715 return -1;
716 }
717 if (parent)
718 parent->sysfs.parent = &cxt->sysfs;
719 } else {
720 if (sysfs_init(&cxt->sysfs, devno, parent ? &parent->sysfs : NULL)) {
721 warnx(_("%s: failed to initialize sysfs handler"), name);
722 return -1;
723 }
270e66ec 724 }
766ab9b0
KZ
725
726 cxt->maj = major(devno);
727 cxt->min = minor(devno);
2a4c734b 728
90e9fcda
KZ
729 sysfs_read_u64(&cxt->sysfs, "size", &cxt->size); /* in sectors */
730 cxt->size <<= 9; /* in bytes */
731
732 sysfs_read_int(&cxt->sysfs, "queue/discard_granularity", &cxt->discard);
2a4c734b
MB
733
734 /* Ignore devices of zero size */
735 if (!lsblk->all_devices && cxt->size == 0)
736 return -1;
737
766ab9b0
KZ
738 if (is_dm(name)) {
739 cxt->dm_name = sysfs_strdup(&cxt->sysfs, "dm/name");
270e66ec
MB
740 if (!cxt->dm_name) {
741 warnx(_("%s: failed to get dm name"), name);
742 return -1;
743 }
766ab9b0 744 }
2a4c734b 745
09a71aa1
PR
746 cxt->npartitions = sysfs_count_partitions(&cxt->sysfs, name);
747 cxt->nholders = sysfs_count_dirents(&cxt->sysfs, "holders");
766ab9b0 748 cxt->nslaves = sysfs_count_dirents(&cxt->sysfs, "slaves");
2a4c734b
MB
749
750 return 0;
751}
752
09a71aa1
PR
753static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent,
754 int do_partitions, const char *part_name);
755
2a4c734b 756/*
09a71aa1 757 * List device partitions if any.
2a4c734b 758 */
09a71aa1
PR
759static int list_partitions(struct blkdev_cxt *wholedisk_cxt, struct blkdev_cxt *parent_cxt,
760 const char *part_name)
2a4c734b
MB
761{
762 DIR *dir;
763 struct dirent *d;
09a71aa1
PR
764 struct blkdev_cxt part_cxt = {};
765 int r = -1;
2a4c734b 766
09a71aa1 767 assert(wholedisk_cxt);
f31505fe 768
09a71aa1
PR
769 /*
770 * Do not process further if there are no partitions for
771 * this device or the device itself is a partition.
772 */
773 if (!wholedisk_cxt->npartitions || wholedisk_cxt->partition)
774 return -1;
2a4c734b 775
09a71aa1 776 dir = sysfs_opendir(&wholedisk_cxt->sysfs, NULL);
2a4c734b
MB
777 if (!dir)
778 err(EXIT_FAILURE, _("failed to open device directory in sysfs"));
779
780 while ((d = xreaddir(dir))) {
09a71aa1
PR
781 /* Process particular partition only? */
782 if (part_name && strcmp(part_name, d->d_name))
2a4c734b
MB
783 continue;
784
09a71aa1 785 if (!(sysfs_is_partition_dirent(dir, d, wholedisk_cxt->name)))
270e66ec 786 continue;
09a71aa1
PR
787
788 if (lsblk->inverse) {
789 /*
790 * <parent_cxt>
791 * `-<part_cxt>
792 * `-<wholedisk_cxt>
793 * `-...
794 */
795 if (set_cxt(&part_cxt, parent_cxt, wholedisk_cxt, d->d_name))
796 goto next;
797
798 if (!parent_cxt && part_cxt.nholders)
799 goto next;
800
801 wholedisk_cxt->parent = &part_cxt;
802 print_device(&part_cxt, parent_cxt ? parent_cxt->tt_line : NULL);
803 if (!lsblk->nodeps)
804 process_blkdev(wholedisk_cxt, &part_cxt, 0, NULL);
805 } else {
806 /*
807 * <parent_cxt>
808 * `-<wholedisk_cxt>
809 * `-<part_cxt>
810 * `-...
811 */
812 if (set_cxt(&part_cxt, wholedisk_cxt, wholedisk_cxt, d->d_name))
813 goto next;
814 /* Print whole disk only once */
815 if (r)
816 print_device(wholedisk_cxt, parent_cxt ? parent_cxt->tt_line : NULL);
817 if (!lsblk->nodeps)
818 process_blkdev(&part_cxt, wholedisk_cxt, 0, NULL);
270e66ec 819 }
09a71aa1
PR
820 next:
821 reset_blkdev_cxt(&part_cxt);
822 r = 0;
2a4c734b 823 }
09a71aa1 824
2a4c734b 825 closedir(dir);
09a71aa1
PR
826 return r;
827}
828
829static int get_wholedisk_from_partition_dirent(DIR *dir, struct dirent *d,
830 struct blkdev_cxt *cxt)
831{
832 char path[PATH_MAX];
833 char *p;
834 int len;
835
836 if ((len = readlinkat(dirfd(dir), d->d_name, path, sizeof(path))) < 0)
837 return 0;
838 path[len]='\0';
839
840 /* The path ends with ".../<device>/<partition>" */
841 p = strrchr(path, '/'); *p = '\0';
842 p = strrchr(path, '/'); p++;
2a4c734b 843
09a71aa1
PR
844 return set_cxt(cxt, NULL, NULL, p);
845}
846
847/*
848 * List device dependencies: partitions, holders (inverse = 0) or slaves (inverse = 1).
849 */
850static int list_deps(struct blkdev_cxt *cxt)
851{
852 DIR *dir;
853 struct dirent *d;
854 struct blkdev_cxt dep = {};
855
856 assert(cxt);
857
858 if (lsblk->nodeps)
859 return 0;
860
861 if (!(lsblk->inverse ? cxt->nslaves : cxt->nholders))
862 return 0;
863
864 dir = sysfs_opendir(&cxt->sysfs, lsblk->inverse ? "slaves" : "holders");
2a4c734b
MB
865 if (!dir)
866 return 0;
867
868 while ((d = xreaddir(dir))) {
09a71aa1
PR
869 /* Is the dependency a partition? */
870 if (sysfs_is_partition_dirent(dir, d, NULL)) {
871 if (!get_wholedisk_from_partition_dirent(dir, d, &dep))
872 process_blkdev(&dep, cxt, 1, d->d_name);
270e66ec 873 }
09a71aa1
PR
874 /* The dependency is a whole device. */
875 else if (!set_cxt(&dep, cxt, NULL, d->d_name))
876 process_blkdev(&dep, cxt, 1, NULL);
877
878 reset_blkdev_cxt(&dep);
2a4c734b
MB
879 }
880 closedir(dir);
881
882 return 0;
883}
884
09a71aa1
PR
885static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent,
886 int do_partitions, const char *part_name)
887{
888 if (do_partitions && cxt->npartitions)
889 return list_partitions(cxt, parent, part_name);
890
891 print_device(cxt, parent ? parent->tt_line : NULL);
892 return list_deps(cxt);
893}
894
895/* Iterate devices in sysfs */
2a4c734b
MB
896static int iterate_block_devices(void)
897{
898 DIR *dir;
899 struct dirent *d;
900 struct blkdev_cxt cxt = {};
901
902 if (!(dir = opendir(_PATH_SYS_BLOCK)))
903 return EXIT_FAILURE;
904
905 while ((d = xreaddir(dir))) {
09a71aa1 906 if (set_cxt(&cxt, NULL, NULL, d->d_name))
2a4c734b
MB
907 goto next;
908
09a71aa1 909 if (!lsblk->all_devices && is_maj_excluded(cxt.maj))
2a4c734b
MB
910 goto next;
911
09a71aa1
PR
912 /* Skip devices in the middle of dependency tree. */
913 if ((lsblk->inverse ? cxt.nholders : cxt.nslaves) > 0)
2a4c734b
MB
914 goto next;
915
09a71aa1 916 process_blkdev(&cxt, NULL, 1, NULL);
2a4c734b
MB
917 next:
918 reset_blkdev_cxt(&cxt);
919 }
920
921 closedir(dir);
922
923 return EXIT_SUCCESS;
924}
925
926static int process_one_device(char *devname)
927{
928 struct blkdev_cxt parent = {}, cxt = {};
929 struct stat st;
270e66ec 930 char buf[PATH_MAX + 1], *diskname = NULL;
2a4c734b 931 dev_t disk = 0;
270e66ec 932 int status = EXIT_FAILURE;
2a4c734b
MB
933
934 if (stat(devname, &st) || !S_ISBLK(st.st_mode)) {
935 warnx(_("%s: not a block device"), devname);
936 return EXIT_FAILURE;
937 }
938 if (blkid_devno_to_wholedisk(st.st_rdev, buf, sizeof(buf), &disk)) {
1117f18f 939 warn(_("%s: failed to get whole-disk device number"), devname);
2a4c734b
MB
940 return EXIT_FAILURE;
941 }
270e66ec 942 if (st.st_rdev == disk) {
2a4c734b 943 /*
09a71aa1 944 * Device is not a partition.
2a4c734b 945 */
09a71aa1 946 if (set_cxt(&cxt, NULL, NULL, buf))
270e66ec 947 goto leave;
09a71aa1 948 process_blkdev(&cxt, NULL, !lsblk->inverse, NULL);
270e66ec 949 } else {
2a4c734b 950 /*
09a71aa1 951 * Partition, read sysfs name of the device.
2a4c734b 952 */
eb9a65bb 953 ssize_t len;
270e66ec 954 char path[PATH_MAX], *name;
2a4c734b 955
270e66ec
MB
956 if (!sysfs_devno_path(st.st_rdev, path, sizeof(path))) {
957 warn(_("failed to compose sysfs path for %s"), devname);
958 goto leave;
959 }
2a4c734b 960
766ab9b0 961 diskname = xstrdup(buf);
cc6b1d11 962 len = readlink(path, buf, PATH_MAX);
2a4c734b
MB
963 if (len < 0) {
964 warn(_("%s: failed to read link"), path);
270e66ec 965 goto leave;
2a4c734b
MB
966 }
967 buf[len] = '\0';
968
969 /* sysfs device name */
970 name = strrchr(buf, '/') + 1;
971
09a71aa1 972 if (set_cxt(&parent, NULL, NULL, diskname))
270e66ec 973 goto leave;
09a71aa1 974 if (set_cxt(&cxt, &parent, &parent, name))
270e66ec 975 goto leave;
09a71aa1
PR
976
977 if (lsblk->inverse)
978 process_blkdev(&parent, &cxt, 1, cxt.name);
979 else
980 process_blkdev(&cxt, &parent, 1, NULL);
2a4c734b
MB
981 }
982
270e66ec
MB
983 status = EXIT_SUCCESS;
984leave:
985 free(diskname);
2a4c734b
MB
986 reset_blkdev_cxt(&cxt);
987
988 if (st.st_rdev != disk)
989 reset_blkdev_cxt(&parent);
990
270e66ec 991 return status;
2a4c734b
MB
992}
993
994static void parse_excludes(const char *str)
995{
996 nexcludes = 0;
997
998 while (str && *str) {
999 char *end = NULL;
ed34643c 1000 unsigned long n;
2a4c734b
MB
1001
1002 errno = 0;
1003 n = strtoul(str, &end, 10);
1004
1005 if (end == str || (errno != 0 && (n == ULONG_MAX || n == 0)))
1006 err(EXIT_FAILURE, _("failed to parse list '%s'"), str);
1007 excludes[nexcludes++] = n;
1008
1009 if (nexcludes == ARRAY_SIZE(excludes))
f14f02f5 1010 /* TRANSLATORS: The standard value for %d is 256. */
2a4c734b
MB
1011 errx(EXIT_FAILURE, _("the list of excluded devices is "
1012 "too large (limit is %d devices)"),
1013 (int)ARRAY_SIZE(excludes));
1014 str = end && *end ? end + 1 : NULL;
1015 }
1016}
1017
abafd686 1018static void __attribute__((__noreturn__)) help(FILE *out)
2a4c734b 1019{
507d39c2 1020 size_t i;
2a4c734b
MB
1021
1022 fprintf(out, _(
1023 "\nUsage:\n"
1024 " %s [options] [<device> ...]\n"), program_invocation_short_name);
1025
1026 fprintf(out, _(
1027 "\nOptions:\n"
1028 " -a, --all print all devices\n"
1029 " -b, --bytes print SIZE in bytes rather than in human readable format\n"
f31505fe 1030 " -d, --nodeps don't print slaves or holders\n"
2d232246 1031 " -D, --discard print discard capabilities\n"
2a4c734b
MB
1032 " -e, --exclude <list> exclude devices by major number (default: RAM disks)\n"
1033 " -f, --fs output info about filesystems\n"
1034 " -h, --help usage information (this)\n"
1035 " -i, --ascii use ascii characters only\n"
1036 " -m, --perms output info about permissions\n"
1037 " -l, --list use list format ouput\n"
1038 " -n, --noheadings don't print headings\n"
1039 " -o, --output <list> output columns\n"
6ea1bdd3
KZ
1040 " -P, --pairs use key=\"value\" output format\n"
1041 " -r, --raw use raw output format\n"
09a71aa1 1042 " -s, --inverse inverse dependencies\n"
2a4c734b
MB
1043 " -t, --topology output info about topology\n"));
1044
1045 fprintf(out, _("\nAvailable columns:\n"));
1046
507d39c2 1047 for (i = 0; i < NCOLS; i++)
d015794e 1048 fprintf(out, " %10s %s\n", infos[i].name, _(infos[i].help));
2a4c734b 1049
f77fa578 1050 fprintf(out, _("\nFor more information see lsblk(8).\n"));
2a4c734b
MB
1051
1052 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
1053}
1054
abafd686 1055static void __attribute__((__noreturn__))
2a4c734b
MB
1056errx_mutually_exclusive(const char *opts)
1057{
1058 errx(EXIT_FAILURE, "%s %s", opts, _("options are mutually exclusive"));
1059}
1060
f65b3bb6
SK
1061static void check_sysdevblock(void)
1062{
1063 if (access(_PATH_SYS_DEVBLOCK, R_OK) != 0)
1064 err(EXIT_FAILURE, _("failed to access sysfs directory: %s"),
1065 _PATH_SYS_DEVBLOCK);
1066}
1067
2a4c734b
MB
1068int main(int argc, char *argv[])
1069{
1070 struct lsblk _ls;
1071 int tt_flags = TT_FL_TREE;
1072 int i, c, status = EXIT_FAILURE;
1073
6c7d5ae9 1074 static const struct option longopts[] = {
2a4c734b 1075 { "all", 0, 0, 'a' },
f31505fe
KZ
1076 { "bytes", 0, 0, 'b' },
1077 { "nodeps", 0, 0, 'd' },
2d232246 1078 { "discard", 0, 0, 'D' },
2a4c734b
MB
1079 { "help", 0, 0, 'h' },
1080 { "output", 1, 0, 'o' },
1081 { "perms", 0, 0, 'm' },
1082 { "noheadings", 0, 0, 'n' },
1083 { "list", 0, 0, 'l' },
1084 { "ascii", 0, 0, 'i' },
1085 { "raw", 0, 0, 'r' },
09a71aa1 1086 { "inverse", 0, 0, 's' },
2a4c734b
MB
1087 { "fs", 0, 0, 'f' },
1088 { "exclude", 1, 0, 'e' },
1089 { "topology", 0, 0, 't' },
6ea1bdd3 1090 { "pairs", 0, 0, 'P' },
2a4c734b
MB
1091 { NULL, 0, 0, 0 },
1092 };
1093
1094 setlocale(LC_ALL, "");
1095 bindtextdomain(PACKAGE, LOCALEDIR);
1096 textdomain(PACKAGE);
1097
1098 lsblk = &_ls;
1099 memset(lsblk, 0, sizeof(*lsblk));
1100
09a71aa1 1101 while((c = getopt_long(argc, argv, "abdDe:fhlnmo:Pirst", longopts, NULL)) != -1) {
2a4c734b
MB
1102 switch(c) {
1103 case 'a':
1104 lsblk->all_devices = 1;
1105 break;
1106 case 'b':
1107 lsblk->bytes = 1;
1108 break;
f31505fe
KZ
1109 case 'd':
1110 lsblk->nodeps = 1;
1111 break;
2d232246
MP
1112 case 'D':
1113 columns[ncolumns++] = COL_NAME;
1114 columns[ncolumns++] = COL_DALIGN;
1115 columns[ncolumns++] = COL_DGRAN;
1116 columns[ncolumns++] = COL_DMAX;
1117 columns[ncolumns++] = COL_DZERO;
1118 break;
2a4c734b
MB
1119 case 'e':
1120 parse_excludes(optarg);
1121 break;
1122 case 'h':
1123 help(stdout);
1124 break;
1125 case 'l':
6ea1bdd3
KZ
1126 if ((tt_flags & TT_FL_RAW)|| (tt_flags & TT_FL_EXPORT))
1127 errx_mutually_exclusive("--{raw,list,export}");
2a4c734b
MB
1128
1129 tt_flags &= ~TT_FL_TREE; /* disable the default */
1130 break;
1131 case 'n':
1132 tt_flags |= TT_FL_NOHEADINGS;
1133 break;
1134 case 'o':
c87638ad 1135 ncolumns = string_to_idarray(optarg,
bdc3ed66
KZ
1136 columns, ARRAY_SIZE(columns),
1137 column_name_to_id);
1138 if (ncolumns < 0)
2a4c734b
MB
1139 return EXIT_FAILURE;
1140 break;
6ea1bdd3
KZ
1141 case 'P':
1142 tt_flags |= TT_FL_EXPORT;
1143 tt_flags &= ~TT_FL_TREE; /* disable the default */
1144 break;
2a4c734b 1145 case 'i':
449f9336 1146 tt_flags |= TT_FL_ASCII;
2a4c734b
MB
1147 break;
1148 case 'r':
1149 tt_flags &= ~TT_FL_TREE; /* disable the default */
1150 tt_flags |= TT_FL_RAW; /* enable raw */
1151 break;
09a71aa1
PR
1152 case 's':
1153 lsblk->inverse = 1;
1154 break;
2a4c734b
MB
1155 case 'f':
1156 columns[ncolumns++] = COL_NAME;
1157 columns[ncolumns++] = COL_FSTYPE;
1158 columns[ncolumns++] = COL_LABEL;
38a8d69e 1159 columns[ncolumns++] = COL_UUID;
2a4c734b
MB
1160 columns[ncolumns++] = COL_TARGET;
1161 break;
1162 case 'm':
1163 columns[ncolumns++] = COL_NAME;
1164 columns[ncolumns++] = COL_SIZE;
1165 columns[ncolumns++] = COL_OWNER;
1166 columns[ncolumns++] = COL_GROUP;
1167 columns[ncolumns++] = COL_MODE;
1168 break;
1169 case 't':
1170 columns[ncolumns++] = COL_NAME;
1171 columns[ncolumns++] = COL_ALIOFF;
1172 columns[ncolumns++] = COL_MINIO;
1173 columns[ncolumns++] = COL_OPTIO;
1174 columns[ncolumns++] = COL_PHYSEC;
1175 columns[ncolumns++] = COL_LOGSEC;
1176 columns[ncolumns++] = COL_ROTA;
1177 columns[ncolumns++] = COL_SCHED;
64c9e22a 1178 columns[ncolumns++] = COL_RQ_SIZE;
2a4c734b
MB
1179 break;
1180 default:
1181 help(stderr);
1182 }
1183 }
1184
f65b3bb6
SK
1185 check_sysdevblock();
1186
2a4c734b
MB
1187 if (!ncolumns) {
1188 columns[ncolumns++] = COL_NAME;
1189 columns[ncolumns++] = COL_MAJMIN;
627970a7 1190 columns[ncolumns++] = COL_RM;
2a4c734b
MB
1191 columns[ncolumns++] = COL_SIZE;
1192 columns[ncolumns++] = COL_RO;
18eac5ba 1193 columns[ncolumns++] = COL_TYPE;
2a4c734b
MB
1194 columns[ncolumns++] = COL_TARGET;
1195 }
1196
1197 if (nexcludes && lsblk->all_devices)
1198 errx_mutually_exclusive("--{all,exclude}");
1199 else if (!nexcludes)
1200 excludes[nexcludes++] = 1; /* default: ignore RAM disks */
1201 /*
1202 * initialize output columns
1203 */
1204 if (!(lsblk->tt = tt_new_table(tt_flags)))
1205 errx(EXIT_FAILURE, _("failed to initialize output table"));
1206
1207 for (i = 0; i < ncolumns; i++) {
1208 struct colinfo *ci = get_column_info(i);
1209 int fl = ci->flags;
1210
1211 if (!(tt_flags & TT_FL_TREE) && get_column_id(i) == COL_NAME)
1212 fl &= ~TT_FL_TREE;
1213
1214 if (!tt_define_column(lsblk->tt, ci->name, ci->whint, fl)) {
1215 warn(_("failed to initialize output column"));
1216 goto leave;
1217 }
1218 }
1219
1220 if (optind == argc)
1221 status = iterate_block_devices();
1222 else while (optind < argc)
1223 status = process_one_device(argv[optind++]);
1224
1225 tt_print_table(lsblk->tt);
1226
1227leave:
1228 tt_free_table(lsblk->tt);
1229 return status;
1230}