diff options
Diffstat (limited to 'misc-utils/lsblk.c')
-rw-r--r-- | misc-utils/lsblk.c | 2202 |
1 files changed, 2202 insertions, 0 deletions
diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c new file mode 100644 index 0000000..49c15ab --- /dev/null +++ b/misc-utils/lsblk.c @@ -0,0 +1,2202 @@ +/* + * lsblk(8) - list block devices + * + * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved. + * Written by Milan Broz <mbroz@redhat.com> + * Karel Zak <kzak@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <stdio.h> +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <stdarg.h> +#include <locale.h> +#include <pwd.h> +#include <grp.h> +#include <ctype.h> +#include <assert.h> + +#include <blkid.h> + +#include "c.h" +#include "pathnames.h" +#include "blkdev.h" +#include "canonicalize.h" +#include "nls.h" +#include "xalloc.h" +#include "strutils.h" +#include "sysfs.h" +#include "closestream.h" +#include "optutils.h" +#include "fileutils.h" + +#include "lsblk.h" + +UL_DEBUG_DEFINE_MASK(lsblk); +UL_DEBUG_DEFINE_MASKNAMES(lsblk) = UL_DEBUG_EMPTY_MASKNAMES; + +#define LSBLK_EXIT_SOMEOK 64 +#define LSBLK_EXIT_ALLFAILED 32 + +static int column_id_to_number(int id); + +/* column IDs */ +enum { + COL_NAME = 0, + COL_KNAME, + COL_PATH, + COL_MAJMIN, + COL_FSAVAIL, + COL_FSSIZE, + COL_FSTYPE, + COL_FSUSED, + COL_FSUSEPERC, + COL_FSVERSION, + COL_TARGET, + COL_LABEL, + COL_UUID, + COL_PTUUID, + COL_PTTYPE, + COL_PARTTYPE, + COL_PARTTYPENAME, + COL_PARTLABEL, + COL_PARTUUID, + COL_PARTFLAGS, + COL_RA, + COL_RO, + COL_RM, + COL_HOTPLUG, + COL_MODEL, + COL_SERIAL, + COL_SIZE, + COL_STATE, + COL_OWNER, + COL_GROUP, + COL_MODE, + COL_ALIOFF, + COL_MINIO, + COL_OPTIO, + COL_PHYSEC, + COL_LOGSEC, + COL_ROTA, + COL_SCHED, + COL_RQ_SIZE, + COL_TYPE, + COL_DALIGN, + COL_DGRAN, + COL_DMAX, + COL_DZERO, + COL_WSAME, + COL_WWN, + COL_RAND, + COL_PKNAME, + COL_HCTL, + COL_TRANSPORT, + COL_SUBSYS, + COL_REV, + COL_VENDOR, + COL_ZONED, + COL_DAX +}; + +/* basic table settings */ +enum { + LSBLK_ASCII = (1 << 0), + LSBLK_RAW = (1 << 1), + LSBLK_NOHEADINGS = (1 << 2), + LSBLK_EXPORT = (1 << 3), + LSBLK_TREE = (1 << 4), + LSBLK_JSON = (1 << 5), +}; + +/* Types used for qsort() and JSON */ +enum { + COLTYPE_STR = 0, /* default */ + COLTYPE_NUM = 1, /* always u64 number */ + COLTYPE_SORTNUM = 2, /* string on output, u64 for qsort() */ + COLTYPE_SIZE = 3, /* srring by default, number when --bytes */ + COLTYPE_BOOL = 4 /* 0 or 1 */ +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* SCOLS_FL_* */ + const char *help; + int type; /* COLTYPE_* */ +}; + +/* columns descriptions */ +static struct colinfo infos[] = { + [COL_NAME] = { "NAME", 0.25, SCOLS_FL_NOEXTREMES, N_("device name") }, + [COL_KNAME] = { "KNAME", 0.3, 0, N_("internal kernel device name") }, + [COL_PKNAME] = { "PKNAME", 0.3, 0, N_("internal parent kernel device name") }, + [COL_PATH] = { "PATH", 0.3, 0, N_("path to the device node") }, + [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number"), COLTYPE_SORTNUM }, + + [COL_FSAVAIL] = { "FSAVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available") }, + [COL_FSSIZE] = { "FSSIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size") }, + [COL_FSTYPE] = { "FSTYPE", 0.1, SCOLS_FL_TRUNC, N_("filesystem type") }, + [COL_FSUSED] = { "FSUSED", 5, SCOLS_FL_RIGHT, N_("filesystem size used") }, + [COL_FSUSEPERC] = { "FSUSE%", 3, SCOLS_FL_RIGHT, N_("filesystem use percentage") }, + [COL_FSVERSION] = { "FSVER", 0.1, SCOLS_FL_TRUNC, N_("filesystem version") }, + + [COL_TARGET] = { "MOUNTPOINT", 0.10, SCOLS_FL_TRUNC, N_("where the device is mounted") }, + [COL_LABEL] = { "LABEL", 0.1, 0, N_("filesystem LABEL") }, + [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") }, + + [COL_PTUUID] = { "PTUUID", 36, 0, N_("partition table identifier (usually UUID)") }, + [COL_PTTYPE] = { "PTTYPE", 0.1, 0, N_("partition table type") }, + + [COL_PARTTYPE] = { "PARTTYPE", 36, 0, N_("partition type code or UUID") }, + [COL_PARTTYPENAME] = { "PARTTYPENAME", 0.1, 0, N_("partition type name") }, + [COL_PARTLABEL] = { "PARTLABEL", 0.1, 0, N_("partition LABEL") }, + [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") }, + [COL_PARTFLAGS] = { "PARTFLAGS", 36, 0, N_("partition flags") }, + + [COL_RA] = { "RA", 3, SCOLS_FL_RIGHT, N_("read-ahead of the device"), COLTYPE_NUM }, + [COL_RO] = { "RO", 1, SCOLS_FL_RIGHT, N_("read-only device"), COLTYPE_BOOL }, + [COL_RM] = { "RM", 1, SCOLS_FL_RIGHT, N_("removable device"), COLTYPE_BOOL }, + [COL_HOTPLUG]= { "HOTPLUG", 1, SCOLS_FL_RIGHT, N_("removable or hotplug device (usb, pcmcia, ...)"), COLTYPE_BOOL }, + [COL_ROTA] = { "ROTA", 1, SCOLS_FL_RIGHT, N_("rotational device"), COLTYPE_BOOL }, + [COL_RAND] = { "RAND", 1, SCOLS_FL_RIGHT, N_("adds randomness"), COLTYPE_BOOL }, + [COL_MODEL] = { "MODEL", 0.1, SCOLS_FL_TRUNC, N_("device identifier") }, + [COL_SERIAL] = { "SERIAL", 0.1, SCOLS_FL_TRUNC, N_("disk serial number") }, + [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the device"), COLTYPE_SIZE }, + [COL_STATE] = { "STATE", 7, SCOLS_FL_TRUNC, N_("state of the device") }, + [COL_OWNER] = { "OWNER", 0.1, SCOLS_FL_TRUNC, N_("user name"), }, + [COL_GROUP] = { "GROUP", 0.1, SCOLS_FL_TRUNC, N_("group name") }, + [COL_MODE] = { "MODE", 10, 0, N_("device node permissions") }, + [COL_ALIOFF] = { "ALIGNMENT", 6, SCOLS_FL_RIGHT, N_("alignment offset"), COLTYPE_NUM }, + [COL_MINIO] = { "MIN-IO", 6, SCOLS_FL_RIGHT, N_("minimum I/O size"), COLTYPE_NUM }, + [COL_OPTIO] = { "OPT-IO", 6, SCOLS_FL_RIGHT, N_("optimal I/O size"), COLTYPE_NUM }, + [COL_PHYSEC] = { "PHY-SEC", 7, SCOLS_FL_RIGHT, N_("physical sector size"), COLTYPE_NUM }, + [COL_LOGSEC] = { "LOG-SEC", 7, SCOLS_FL_RIGHT, N_("logical sector size"), COLTYPE_NUM }, + [COL_SCHED] = { "SCHED", 0.1, 0, N_("I/O scheduler name") }, + [COL_RQ_SIZE]= { "RQ-SIZE", 5, SCOLS_FL_RIGHT, N_("request queue size"), COLTYPE_NUM }, + [COL_TYPE] = { "TYPE", 4, 0, N_("device type") }, + [COL_DALIGN] = { "DISC-ALN", 6, SCOLS_FL_RIGHT, N_("discard alignment offset"), COLTYPE_NUM }, + [COL_DGRAN] = { "DISC-GRAN", 6, SCOLS_FL_RIGHT, N_("discard granularity"), COLTYPE_SIZE }, + [COL_DMAX] = { "DISC-MAX", 6, SCOLS_FL_RIGHT, N_("discard max bytes"), COLTYPE_SIZE }, + [COL_DZERO] = { "DISC-ZERO", 1, SCOLS_FL_RIGHT, N_("discard zeroes data"), COLTYPE_BOOL }, + [COL_WSAME] = { "WSAME", 6, SCOLS_FL_RIGHT, N_("write same max bytes"), COLTYPE_SIZE }, + [COL_WWN] = { "WWN", 18, 0, N_("unique storage identifier") }, + [COL_HCTL] = { "HCTL", 10, 0, N_("Host:Channel:Target:Lun for SCSI") }, + [COL_TRANSPORT] = { "TRAN", 6, 0, N_("device transport type") }, + [COL_SUBSYS] = { "SUBSYSTEMS", 0.1, SCOLS_FL_NOEXTREMES, N_("de-duplicated chain of subsystems") }, + [COL_REV] = { "REV", 4, SCOLS_FL_RIGHT, N_("device revision") }, + [COL_VENDOR] = { "VENDOR", 0.1, SCOLS_FL_TRUNC, N_("device vendor") }, + [COL_ZONED] = { "ZONED", 0.3, 0, N_("zone model") }, + [COL_DAX] = { "DAX", 1, SCOLS_FL_RIGHT, N_("dax-capable device"), COLTYPE_BOOL }, +}; + +struct lsblk *lsblk; /* global handler */ + +/* + * columns[] array specifies all currently wanted output column. The columns + * are defined by infos[] array and you can specify (on command line) each + * column twice. That's enough, dynamically allocated array of the columns is + * unnecessary overkill and over-engineering in this case + */ +static int columns[ARRAY_SIZE(infos) * 2]; +static size_t ncolumns; + +static inline void add_column(int id) +{ + if (ncolumns >= ARRAY_SIZE(columns)) + errx(EXIT_FAILURE, _("too many columns specified, " + "the limit is %zu columns"), + ARRAY_SIZE(columns) - 1); + columns[ ncolumns++ ] = id; +} + +static inline void add_uniq_column(int id) +{ + if (column_id_to_number(id) < 0) + add_column(id); +} + +static void lsblk_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(lsblk, LSBLK_DEBUG_, 0, LSBLK_DEBUG); +} + +/* + * exclude/include devices filter based on major device numbers + */ +static int excludes[256]; +static size_t nexcludes; + +static int includes[256]; +static size_t nincludes; + +static int is_maj_excluded(int maj) +{ + size_t i; + + assert(ARRAY_SIZE(excludes) > nexcludes); + + if (!nexcludes) + return 0; /* filter not enabled, device not excluded */ + + for (i = 0; i < nexcludes; i++) { + if (excludes[i] == maj) { + DBG(FILTER, ul_debug("exclude: maj=%d", maj)); + return 1; + } + } + return 0; +} + +static int is_maj_included(int maj) +{ + size_t i; + + assert(ARRAY_SIZE(includes) > nincludes); + + if (!nincludes) + return 1; /* filter not enabled, device is included */ + + for (i = 0; i < nincludes; i++) { + if (includes[i] == maj) { + DBG(FILTER, ul_debug("include: maj=%d", maj)); + return 1; + } + } + return 0; +} + +/* Converts column sequential number to column ID (COL_*) */ +static int get_column_id(int num) +{ + assert(num >= 0); + assert((size_t) num < ncolumns); + assert(columns[num] < (int) ARRAY_SIZE(infos)); + return columns[num]; +} + +/* Returns column description for the column sequential number */ +static struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +/* Converts column name (as defined in the infos[] to the column ID */ +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + const char *cn = infos[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + +/* Converts column ID (COL_*) to column sequential number */ +static int column_id_to_number(int id) +{ + size_t i; + + for (i = 0; i < ncolumns; i++) + if (columns[i] == id) + return i; + return -1; +} + +/* Checks for DM prefix in the device name */ +static int is_dm(const char *name) +{ + return strncmp(name, "dm-", 3) ? 0 : 1; +} + +/* Returns full pat to the device node (TODO: what about sysfs_blkdev_get_path()) */ +static char *get_device_path(struct lsblk_device *dev) +{ + char path[PATH_MAX]; + + assert(dev); + assert(dev->name); + + if (is_dm(dev->name)) + return __canonicalize_dm_name(lsblk->sysroot, dev->name); + + snprintf(path, sizeof(path), "/dev/%s", dev->name); + sysfs_devname_sys_to_dev(path); + return xstrdup(path); +} + +static int is_readonly_device(struct lsblk_device *dev) +{ + int fd, ro = 0; + + if (ul_path_scanf(dev->sysfs, "ro", "%d", &ro) == 1) + return ro; + + /* fallback if "ro" attribute does not exist */ + fd = open(dev->filename, O_RDONLY); + if (fd != -1) { + if (ioctl(fd, BLKROGET, &ro) != 0) + ro = 0; + close(fd); + } + return ro; +} + +static char *get_scheduler(struct lsblk_device *dev) +{ + char buf[128]; + char *p, *res = NULL; + + if (ul_path_read_buffer(dev->sysfs, buf, sizeof(buf), "queue/scheduler") == 0) + return NULL; + p = strchr(buf, '['); + if (p) { + res = p + 1; + p = strchr(res, ']'); + if (p) { + *p = '\0'; + res = xstrdup(res); + } else + res = NULL; + } + return res; +} + +static char *get_type(struct lsblk_device *dev) +{ + char *res = NULL, *p; + + if (device_is_partition(dev)) + return xstrdup("part"); + + if (is_dm(dev->name)) { + char *dm_uuid = NULL; + + /* The DM_UUID prefix should be set to subsystem owning + * the device - LVM, CRYPT, DMRAID, MPATH, PART */ + if (ul_path_read_string(dev->sysfs, &dm_uuid, "dm/uuid") > 0 + && dm_uuid) { + char *tmp = dm_uuid; + char *dm_uuid_prefix = strsep(&tmp, "-"); + + if (dm_uuid_prefix) { + /* kpartx hack to remove partition number */ + if (strncasecmp(dm_uuid_prefix, "part", 4) == 0) + dm_uuid_prefix[4] = '\0'; + + res = xstrdup(dm_uuid_prefix); + } + } + + free(dm_uuid); + if (!res) + /* No UUID or no prefix - just mark it as DM device */ + res = xstrdup("dm"); + + } else if (!strncmp(dev->name, "loop", 4)) { + res = xstrdup("loop"); + + } else if (!strncmp(dev->name, "md", 2)) { + char *md_level = NULL; + + ul_path_read_string(dev->sysfs, &md_level, "md/level"); + res = md_level ? md_level : xstrdup("md"); + + } else { + const char *type = NULL; + int x = 0; + + if (ul_path_read_s32(dev->sysfs, &x, "device/type") == 0) + type = blkdev_scsi_type_to_name(x); + if (!type) + type = "disk"; + res = xstrdup(type); + } + + for (p = res; p && *p; p++) + *p = tolower((unsigned char) *p); + return res; +} + +/* Thanks to lsscsi code for idea of detection logic used here */ +static char *get_transport(struct lsblk_device *dev) +{ + struct path_cxt *sysfs = dev->sysfs; + char *attr = NULL; + const char *trans = NULL; + + + /* SCSI - Serial Peripheral Interface */ + if (sysfs_blkdev_scsi_host_is(sysfs, "spi")) + trans = "spi"; + + /* FC/FCoE - Fibre Channel / Fibre Channel over Ethernet */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "fc")) { + attr = sysfs_blkdev_scsi_host_strdup_attribute(sysfs, "fc", "symbolic_name"); + if (!attr) + return NULL; + trans = strstr(attr, " over ") ? "fcoe" : "fc"; + free(attr); + } + + /* SAS - Serial Attached SCSI */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "sas") || + sysfs_blkdev_scsi_has_attribute(sysfs, "sas_device")) + trans = "sas"; + + + /* SBP - Serial Bus Protocol (FireWire) */ + else if (sysfs_blkdev_scsi_has_attribute(sysfs, "ieee1394_id")) + trans = "sbp"; + + /* iSCSI */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "iscsi")) + trans ="iscsi"; + + /* USB - Universal Serial Bus */ + else if (sysfs_blkdev_scsi_path_contains(sysfs, "usb")) + trans = "usb"; + + /* ATA, SATA */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "scsi")) { + attr = sysfs_blkdev_scsi_host_strdup_attribute(sysfs, "scsi", "proc_name"); + if (!attr) + return NULL; + if (!strncmp(attr, "ahci", 4) || !strncmp(attr, "sata", 4)) + trans = "sata"; + else if (strstr(attr, "ata")) + trans = "ata"; + free(attr); + + } else if (strncmp(dev->name, "nvme", 4) == 0) + trans = "nvme"; + + return trans ? xstrdup(trans) : NULL; +} + +static char *get_subsystems(struct lsblk_device *dev) +{ + char path[PATH_MAX]; + char *sub, *chain, *res = NULL; + size_t len = 0, last = 0; + + chain = sysfs_blkdev_get_devchain(dev->sysfs, path, sizeof(path)); + if (!chain) + return NULL; + + while (sysfs_blkdev_next_subsystem(dev->sysfs, chain, &sub) == 0) { + size_t sz; + + /* don't create "block:scsi:scsi", but "block:scsi" */ + if (len && strcmp(res + last, sub) == 0) { + free(sub); + continue; + } + + sz = strlen(sub); + res = xrealloc(res, len + sz + 2); + if (len) + res[len++] = ':'; + + memcpy(res + len, sub, sz + 1); + last = len; + len += sz; + free(sub); + } + + return res; +} + + +#define is_parsable(_l) (scols_table_is_raw((_l)->table) || \ + scols_table_is_export((_l)->table) || \ + scols_table_is_json((_l)->table)) + +static char *mk_name(const char *name) +{ + char *p; + if (!name) + return NULL; + if (lsblk->paths) + xasprintf(&p, "/dev/%s", name); + else + p = xstrdup(name); + if (p) + sysfs_devname_sys_to_dev(p); + return p; +} + +static char *mk_dm_name(const char *name) +{ + char *p; + if (!name) + return NULL; + if (lsblk->paths) + xasprintf(&p, "/dev/mapper/%s", name); + else + p = xstrdup(name); + return p; +} + +/* stores data to scols cell userdata (invisible and independent on output) + * to make the original values accessible for sort functions + */ +static void set_sortdata_u64(struct libscols_line *ln, int col, uint64_t x) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, col); + uint64_t *data; + + if (!ce) + return; + data = xmalloc(sizeof(uint64_t)); + *data = x; + scols_cell_set_userdata(ce, data); +} + +/* do not modify *data on any error */ +static void str2u64(const char *str, uint64_t *data) +{ + uintmax_t num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + return; + num = strtoumax(str, &end, 10); + + if (errno || str == end || (end && *end)) + return; + *data = num; +} + +static void unref_sortdata(struct libscols_table *tb) +{ + struct libscols_iter *itr; + struct libscols_line *ln; + + if (!tb || !lsblk->sort_col) + return; + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + return; + while (scols_table_next_line(tb, itr, &ln) == 0) { + struct libscols_cell *ce = scols_line_get_column_cell(ln, + lsblk->sort_col); + void *data = scols_cell_get_userdata(ce); + free(data); + } + + scols_free_iter(itr); +} + +static char *get_vfs_attribute(struct lsblk_device *dev, int id) +{ + char *sizestr; + uint64_t vfs_attr = 0; + char *mnt; + + if (!dev->fsstat.f_blocks) { + mnt = lsblk_device_get_mountpoint(dev); + if (!mnt || dev->is_swap) + return NULL; + if (statvfs(mnt, &dev->fsstat) != 0) + return NULL; + } + + switch(id) { + case COL_FSSIZE: + vfs_attr = dev->fsstat.f_frsize * dev->fsstat.f_blocks; + break; + case COL_FSAVAIL: + vfs_attr = dev->fsstat.f_frsize * dev->fsstat.f_bavail; + break; + case COL_FSUSED: + vfs_attr = dev->fsstat.f_frsize * (dev->fsstat.f_blocks - dev->fsstat.f_bfree); + break; + case COL_FSUSEPERC: + if (dev->fsstat.f_blocks == 0) + return xstrdup("-"); + + xasprintf(&sizestr, "%.0f%%", + (double)(dev->fsstat.f_blocks - dev->fsstat.f_bfree) / + dev->fsstat.f_blocks * 100); + return sizestr; + } + + if (!vfs_attr) + sizestr = xstrdup("0"); + else if (lsblk->bytes) + xasprintf(&sizestr, "%ju", vfs_attr); + else + sizestr = size_to_human_string(SIZE_SUFFIX_1LETTER, vfs_attr); + + return sizestr; +} + +static struct stat *device_get_stat(struct lsblk_device *dev) +{ + if (!dev->st.st_rdev + && stat(dev->filename, &dev->st) != 0) + return NULL; + + return &dev->st; +} + +static int is_removable_device(struct lsblk_device *dev, struct lsblk_device *parent) +{ + struct path_cxt *pc; + + if (dev->removable != -1) + goto done; + if (ul_path_scanf(dev->sysfs, "removable", "%d", &dev->removable) == 1) + goto done; + + if (parent) { + pc = sysfs_blkdev_get_parent(dev->sysfs); + if (!pc) + goto done; + + /* dev is partition and parent is whole-disk */ + if (pc == parent->sysfs) + dev->removable = is_removable_device(parent, NULL); + + /* parent is something else, use sysfs parent */ + else if (ul_path_scanf(pc, "removable", "%d", &dev->removable) != 1) + dev->removable = 0; + } +done: + if (dev->removable == -1) + dev->removable = 0; + return dev->removable; +} + +static uint64_t device_get_discard_granularity(struct lsblk_device *dev) +{ + if (dev->discard_granularity == (uint64_t) -1 + && ul_path_read_u64(dev->sysfs, &dev->discard_granularity, + "queue/discard_granularity") != 0) + dev->discard_granularity = 0; + + return dev->discard_granularity; +} + +/* + * Generates data (string) for column specified by column ID for specified device. If sortdata + * is not NULL then returns number usable to sort the column if the data are available for the + * column. + */ +static char *device_get_data( + struct lsblk_device *dev, /* device */ + struct lsblk_device *parent, /* device parent as defined in the tree */ + int id, /* column ID (COL_*) */ + uint64_t *sortdata) /* returns sort data as number */ +{ + struct lsblk_devprop *prop = NULL; + char *str = NULL; + + switch(id) { + case COL_NAME: + str = dev->dm_name ? mk_dm_name(dev->dm_name) : mk_name(dev->name); + break; + case COL_KNAME: + str = mk_name(dev->name); + break; + case COL_PKNAME: + if (parent) + str = mk_name(parent->name); + break; + case COL_PATH: + if (dev->filename) + str = xstrdup(dev->filename); + break; + case COL_OWNER: + if (lsblk->sysroot) + prop = lsblk_device_get_properties(dev); + if (prop && prop->owner) { + str = xstrdup(prop->owner); + } else { + struct stat *st = device_get_stat(dev); + struct passwd *pw = st ? getpwuid(st->st_uid) : NULL; + if (pw) + str = xstrdup(pw->pw_name); + } + break; + case COL_GROUP: + if (lsblk->sysroot) + prop = lsblk_device_get_properties(dev); + if (prop && prop->group) { + str = xstrdup(prop->group); + } else { + struct stat *st = device_get_stat(dev); + struct group *gr = st ? getgrgid(st->st_gid) : NULL; + if (gr) + str = xstrdup(gr->gr_name); + } + break; + case COL_MODE: + if (lsblk->sysroot) + prop = lsblk_device_get_properties(dev); + if (prop && prop->mode) { + str = xstrdup(prop->mode); + } else { + struct stat *st = device_get_stat(dev); + char md[11] = { '\0' }; + + if (st) + str = xstrdup(xstrmode(st->st_mode, md)); + } + break; + case COL_MAJMIN: + if (is_parsable(lsblk)) + xasprintf(&str, "%u:%u", dev->maj, dev->min); + else + xasprintf(&str, "%3u:%-3u", dev->maj, dev->min); + if (sortdata) + *sortdata = makedev(dev->maj, dev->min); + break; + case COL_FSTYPE: + prop = lsblk_device_get_properties(dev); + if (prop && prop->fstype) + str = xstrdup(prop->fstype); + break; + case COL_FSSIZE: + case COL_FSAVAIL: + case COL_FSUSED: + case COL_FSUSEPERC: + str = get_vfs_attribute(dev, id); + break; + case COL_FSVERSION: + prop = lsblk_device_get_properties(dev); + if (prop && prop->fsversion) + str = xstrdup(prop->fsversion); + break; + case COL_TARGET: + { + char *s = lsblk_device_get_mountpoint(dev); + if (s) + str = xstrdup(s); + else + str = NULL; + break; + } + case COL_LABEL: + prop = lsblk_device_get_properties(dev); + if (prop && prop->label) + str = xstrdup(prop->label); + break; + case COL_UUID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->uuid) + str = xstrdup(prop->uuid); + break; + case COL_PTUUID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->ptuuid) + str = xstrdup(prop->ptuuid); + break; + case COL_PTTYPE: + prop = lsblk_device_get_properties(dev); + if (prop && prop->pttype) + str = xstrdup(prop->pttype); + break; + case COL_PARTTYPE: + prop = lsblk_device_get_properties(dev); + if (prop && prop->parttype) + str = xstrdup(prop->parttype); + break; + case COL_PARTTYPENAME: + prop = lsblk_device_get_properties(dev); + if (prop && prop->parttype && prop->pttype) { + const char *x = lsblk_parttype_code_to_string( + prop->parttype, prop->pttype); + if (x) + str = xstrdup(x); + } + break; + case COL_PARTLABEL: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partlabel) + str = xstrdup(prop->partlabel); + break; + case COL_PARTUUID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partuuid) + str = xstrdup(prop->partuuid); + break; + case COL_PARTFLAGS: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partflags) + str = xstrdup(prop->partflags); + break; + case COL_WWN: + prop = lsblk_device_get_properties(dev); + if (prop && prop->wwn) + str = xstrdup(prop->wwn); + break; + case COL_RA: + ul_path_read_string(dev->sysfs, &str, "queue/read_ahead_kb"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_RO: + str = xstrdup(is_readonly_device(dev) ? "1" : "0"); + break; + case COL_RM: + str = xstrdup(is_removable_device(dev, parent) ? "1" : "0"); + break; + case COL_HOTPLUG: + str = sysfs_blkdev_is_hotpluggable(dev->sysfs) ? xstrdup("1") : xstrdup("0"); + break; + case COL_ROTA: + ul_path_read_string(dev->sysfs, &str, "queue/rotational"); + break; + case COL_RAND: + ul_path_read_string(dev->sysfs, &str, "queue/add_random"); + break; + case COL_MODEL: + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); + if (prop && prop->model) + str = xstrdup(prop->model); + else + ul_path_read_string(dev->sysfs, &str, "device/model"); + } + break; + case COL_SERIAL: + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); + if (prop && prop->serial) + str = xstrdup(prop->serial); + else + ul_path_read_string(dev->sysfs, &str, "device/serial"); + } + break; + case COL_REV: + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/rev"); + break; + case COL_VENDOR: + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/vendor"); + break; + case COL_SIZE: + if (lsblk->bytes) + xasprintf(&str, "%ju", dev->size); + else + str = size_to_human_string(SIZE_SUFFIX_1LETTER, dev->size); + if (sortdata) + *sortdata = dev->size; + break; + case COL_STATE: + if (!device_is_partition(dev) && !dev->dm_name) + ul_path_read_string(dev->sysfs, &str, "device/state"); + else if (dev->dm_name) { + int x = 0; + if (ul_path_read_s32(dev->sysfs, &x, "dm/suspended") == 0) + str = xstrdup(x ? "suspended" : "running"); + } + break; + case COL_ALIOFF: + ul_path_read_string(dev->sysfs, &str, "alignment_offset"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_MINIO: + ul_path_read_string(dev->sysfs, &str, "queue/minimum_io_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_OPTIO: + ul_path_read_string(dev->sysfs, &str, "queue/optimal_io_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_PHYSEC: + ul_path_read_string(dev->sysfs, &str, "queue/physical_block_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_LOGSEC: + ul_path_read_string(dev->sysfs, &str, "queue/logical_block_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_SCHED: + str = get_scheduler(dev); + break; + case COL_RQ_SIZE: + ul_path_read_string(dev->sysfs, &str, "queue/nr_requests"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_TYPE: + str = get_type(dev); + break; + case COL_HCTL: + { + int h, c, t, l; + if (sysfs_blkdev_scsi_get_hctl(dev->sysfs, &h, &c, &t, &l) == 0) + xasprintf(&str, "%d:%d:%d:%d", h, c, t, l); + break; + } + case COL_TRANSPORT: + str = get_transport(dev); + break; + case COL_SUBSYS: + str = get_subsystems(dev); + break; + case COL_DALIGN: + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "discard_alignment"); + if (!str) + str = xstrdup("0"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_DGRAN: + if (lsblk->bytes) { + ul_path_read_string(dev->sysfs, &str, "queue/discard_granularity"); + if (sortdata) + str2u64(str, sortdata); + } else { + uint64_t x = device_get_discard_granularity(dev); + str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; + } + break; + case COL_DMAX: + if (lsblk->bytes) { + ul_path_read_string(dev->sysfs, &str, "queue/discard_max_bytes"); + if (sortdata) + str2u64(str, sortdata); + } else { + uint64_t x; + if (ul_path_read_u64(dev->sysfs, &x, "queue/discard_max_bytes") == 0) { + str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; + } + } + break; + case COL_DZERO: + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "queue/discard_zeroes_data"); + if (!str) + str = xstrdup("0"); + break; + case COL_WSAME: + if (lsblk->bytes) { + ul_path_read_string(dev->sysfs, &str, "queue/write_same_max_bytes"); + if (sortdata) + str2u64(str, sortdata); + } else { + uint64_t x; + + if (ul_path_read_u64(dev->sysfs, &x, "queue/write_same_max_bytes") == 0) { + str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; + } + } + if (!str) + str = xstrdup("0"); + break; + case COL_ZONED: + ul_path_read_string(dev->sysfs, &str, "queue/zoned"); + break; + case COL_DAX: + ul_path_read_string(dev->sysfs, &str, "queue/dax"); + break; + }; + + return str; +} + +/* + * Adds data for all wanted columns about the device to the smartcols table + */ +static void device_to_scols( + struct lsblk_device *dev, + struct lsblk_device *parent, + struct libscols_table *tab, + struct libscols_line *parent_line) +{ + size_t i; + struct libscols_line *ln; + struct lsblk_iter itr; + struct lsblk_device *child = NULL; + int link_group = 0; + + + DBG(DEV, ul_debugobj(dev, "add '%s' to scols", dev->name)); + ON_DBG(DEV, if (ul_path_isopen_dirfd(dev->sysfs)) ul_debugobj(dev, " %s ---> is open!", dev->name)); + + if (!parent && dev->wholedisk) + parent = dev->wholedisk; + + /* Do not print device more than once on --list if tree order is not requested */ + if (!(lsblk->flags & LSBLK_TREE) && !lsblk->force_tree_order && dev->is_printed) + return; + + if (lsblk->merge && list_count_entries(&dev->parents) > 1) { + if (!lsblk_device_is_last_parent(dev, parent)) + return; + link_group = 1; + } + + ln = scols_table_new_line(tab, link_group ? NULL : parent_line); + if (!ln) + err(EXIT_FAILURE, _("failed to allocate output line")); + + dev->is_printed = 1; + + if (link_group) { + struct lsblk_device *p; + struct libscols_line *gr = parent_line; + + /* Merge all my parents to the one group */ + DBG(DEV, ul_debugobj(dev, " grouping parents [--merge]")); + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_parent(dev, &itr, &p) == 0) { + if (!p->scols_line) { + DBG(DEV, ul_debugobj(dev, " *** ignore '%s' no scols line yet", p->name)); + continue; + } + DBG(DEV, ul_debugobj(dev, " group '%s'", p->name)); + scols_table_group_lines(tab, p->scols_line, gr, 0); + } + + /* Link the group -- this makes group->child connection */ + DBG(DEV, ul_debugobj(dev, " linking the group [--merge]")); + scols_line_link_group(ln, gr, 0); + } + + /* read column specific data and set it to smartcols table line */ + for (i = 0; i < ncolumns; i++) { + char *data; + int id = get_column_id(i); + + if (lsblk->sort_id != id) + data = device_get_data(dev, parent, id, NULL); + else { + uint64_t sortdata = (uint64_t) -1; + + data = device_get_data(dev, parent, id, &sortdata); + if (data && sortdata != (uint64_t) -1) + set_sortdata_u64(ln, i, sortdata); + } + DBG(DEV, ul_debugobj(dev, " refer data[%zu]=\"%s\"", i, data)); + if (data && scols_line_refer_data(ln, i, data)) + err(EXIT_FAILURE, _("failed to add output data")); + } + + dev->scols_line = ln; + + if (dev->npartitions == 0) + /* For partitions we often read from parental whole-disk sysfs, + * otherwise we can close */ + ul_path_close_dirfd(dev->sysfs); + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_child(dev, &itr, &child) == 0) { + DBG(DEV, ul_debugobj(dev, "%s -> continue to child", dev->name)); + device_to_scols(child, dev, tab, ln); + DBG(DEV, ul_debugobj(dev, "%s <- child done", dev->name)); + } + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); +} + +/* + * Walks on tree and adds one line for each device to the smartcols table + */ +static void devtree_to_scols(struct lsblk_devtree *tr, struct libscols_table *tab) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) + device_to_scols(dev, NULL, tab, NULL); +} + +/* + * Reads very basic information about the device from sysfs into the device struct + */ +static int initialize_device(struct lsblk_device *dev, + struct lsblk_device *wholedisk, + const char *name) +{ + dev_t devno; + + DBG(DEV, ul_debugobj(dev, "initialize %s [wholedisk=%p %s]", + name, wholedisk, wholedisk ? wholedisk->name : "")); + + if (sysfs_devname_is_hidden(lsblk->sysroot, name)) { + DBG(DEV, ul_debugobj(dev, "%s: hidden, ignore", name)); + return -1; + } + + dev->name = xstrdup(name); + + if (wholedisk) { + dev->wholedisk = wholedisk; + lsblk_ref_device(wholedisk); + } + + dev->filename = get_device_path(dev); + if (!dev->filename) { + DBG(DEV, ul_debugobj(dev, "%s: failed to get device path", dev->name)); + return -1; + } + DBG(DEV, ul_debugobj(dev, "%s: filename=%s", dev->name, dev->filename)); + + devno = __sysfs_devname_to_devno(lsblk->sysroot, dev->name, wholedisk ? wholedisk->name : NULL); + if (!devno) { + DBG(DEV, ul_debugobj(dev, "%s: unknown device name", dev->name)); + return -1; + } + + dev->sysfs = ul_new_sysfs_path(devno, wholedisk ? wholedisk->sysfs : NULL, lsblk->sysroot); + if (!dev->sysfs) { + DBG(DEV, ul_debugobj(dev, "%s: failed to initialize sysfs handler", dev->name)); + return -1; + } + + dev->maj = major(devno); + dev->min = minor(devno); + dev->size = 0; + + if (ul_path_read_u64(dev->sysfs, &dev->size, "size") == 0) /* in sectors */ + dev->size <<= 9; /* in bytes */ + + /* Ignore devices of zero size */ + if (!lsblk->all_devices && dev->size == 0) { + DBG(DEV, ul_debugobj(dev, "zero size device -- ignore")); + return -1; + } + if (is_dm(dev->name)) { + ul_path_read_string(dev->sysfs, &dev->dm_name, "dm/name"); + if (!dev->dm_name) { + DBG(DEV, ul_debugobj(dev, "%s: failed to get dm name", dev->name)); + return -1; + } + } + + dev->npartitions = sysfs_blkdev_count_partitions(dev->sysfs, dev->name); + dev->nholders = ul_path_count_dirents(dev->sysfs, "holders"); + dev->nslaves = ul_path_count_dirents(dev->sysfs, "slaves"); + + DBG(DEV, ul_debugobj(dev, "%s: npartitions=%d, nholders=%d, nslaves=%d", + dev->name, dev->npartitions, dev->nholders, dev->nslaves)); + + /* ignore non-SCSI devices */ + if (lsblk->scsi && sysfs_blkdev_scsi_get_hctl(dev->sysfs, NULL, NULL, NULL, NULL)) { + DBG(DEV, ul_debugobj(dev, "non-scsi device -- ignore")); + return -1; + } + + DBG(DEV, ul_debugobj(dev, "%s: context successfully initialized", dev->name)); + return 0; +} + +static struct lsblk_device *devtree_get_device_or_new(struct lsblk_devtree *tr, + struct lsblk_device *disk, + const char *name) +{ + struct lsblk_device *dev = lsblk_devtree_get_device(tr, name); + + if (!dev) { + dev = lsblk_new_device(); + if (!dev) + err(EXIT_FAILURE, _("failed to allocate device")); + + if (initialize_device(dev, disk, name) != 0) { + lsblk_unref_device(dev); + return NULL; + } + lsblk_devtree_add_device(tr, dev); + lsblk_unref_device(dev); /* keep it referenced by devtree only */ + } else + DBG(DEV, ul_debugobj(dev, "%s: already processed", name)); + + return dev; +} + +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions); + +/* + * Read devices from whole-disk device into tree + */ +static int process_partitions(struct lsblk_devtree *tr, struct lsblk_device *disk) +{ + DIR *dir; + struct dirent *d; + + assert(disk); + + /* + * Do not process further if there are no partitions for + * this device or the device itself is a partition. + */ + if (!disk->npartitions || device_is_partition(disk)) + return -EINVAL; + + DBG(DEV, ul_debugobj(disk, "%s: probe whole-disk for partitions", disk->name)); + + dir = ul_path_opendir(disk->sysfs, NULL); + if (!dir) + err(EXIT_FAILURE, _("failed to open device directory in sysfs")); + + while ((d = xreaddir(dir))) { + struct lsblk_device *part; + + if (!(sysfs_blkdev_is_partition_dirent(dir, d, disk->name))) + continue; + + DBG(DEV, ul_debugobj(disk, " checking %s", d->d_name)); + + part = devtree_get_device_or_new(tr, disk, d->d_name); + if (!part) + continue; + + if (lsblk_device_new_dependence(disk, part) == 0) + process_dependencies(tr, part, 0); + + ul_path_close_dirfd(part->sysfs); + } + + /* For partitions we need parental (whole-disk) sysfs directory pretty + * often, so close it now when all is done */ + ul_path_close_dirfd(disk->sysfs); + + DBG(DEV, ul_debugobj(disk, "probe whole-disk for partitions -- done")); + closedir(dir); + return 0; +} + +static char *get_wholedisk_from_partition_dirent(DIR *dir, struct dirent *d, char *buf, size_t bufsz) +{ + char *p; + int len; + + if ((len = readlinkat(dirfd(dir), d->d_name, buf, bufsz - 1)) < 0) + return 0; + + buf[len] = '\0'; + + /* The path ends with ".../<device>/<partition>" */ + p = strrchr(buf, '/'); + if (!p) + return NULL; + *p = '\0'; + + p = strrchr(buf, '/'); + if (!p) + return NULL; + p++; + + return p; +} + +/* + * Reads slaves/holders and partitions for specified device into device tree + */ +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions) +{ + DIR *dir; + struct dirent *d; + const char *depname; + + assert(dev); + + if (lsblk->nodeps) + return 0; + + /* read all or specified partition */ + if (do_partitions && dev->npartitions) + process_partitions(tr, dev); + + DBG(DEV, ul_debugobj(dev, "%s: reading dependencies", dev->name)); + + if (!(lsblk->inverse ? dev->nslaves : dev->nholders)) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders)")); + return 0; + } + + depname = lsblk->inverse ? "slaves" : "holders"; + dir = ul_path_opendir(dev->sysfs, depname); + if (!dir) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders directory)")); + return 0; + } + ul_path_close_dirfd(dev->sysfs); + + DBG(DEV, ul_debugobj(dev, " %s: checking for '%s' dependence", dev->name, depname)); + + while ((d = xreaddir(dir))) { + struct lsblk_device *dep = NULL; + struct lsblk_device *disk = NULL; + + /* Is the dependency a partition? */ + if (sysfs_blkdev_is_partition_dirent(dir, d, NULL)) { + + char buf[PATH_MAX]; + char *diskname; + + DBG(DEV, ul_debugobj(dev, " %s: dependence is partition", d->d_name)); + + diskname = get_wholedisk_from_partition_dirent(dir, d, buf, sizeof(buf)); + if (diskname) + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) { + DBG(DEV, ul_debugobj(dev, " ignore no wholedisk ???")); + goto next; + } + + dep = devtree_get_device_or_new(tr, disk, d->d_name); + if (!dep) + goto next; + + if (lsblk_device_new_dependence(dev, dep) == 0) + process_dependencies(tr, dep, 1); + + if (lsblk->inverse + && lsblk_device_new_dependence(dep, disk) == 0) + process_dependencies(tr, disk, 0); + } + /* The dependency is a whole device. */ + else { + DBG(DEV, ul_debugobj(dev, " %s: %s: dependence is whole-disk", + dev->name, d->d_name)); + + dep = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dep) + goto next; + + if (lsblk_device_new_dependence(dev, dep) == 0) + /* For inverse tree we don't want to show partitions + * if the dependence is on whole-disk */ + process_dependencies(tr, dep, lsblk->inverse ? 0 : 1); + } +next: + if (dep && dep->sysfs) + ul_path_close_dirfd(dep->sysfs); + if (disk && disk->sysfs) + ul_path_close_dirfd(disk->sysfs); + } + closedir(dir); + + DBG(DEV, ul_debugobj(dev, "%s: checking for '%s' -- done", dev->name, depname)); + return 0; +} + +/* + * Defines the device as root node in the device tree and walks on all dependencies of the device. + */ +static int __process_one_device(struct lsblk_devtree *tr, char *devname, dev_t devno) +{ + struct lsblk_device *dev = NULL; + struct lsblk_device *disk = NULL; + char buf[PATH_MAX + 1], *name = NULL, *diskname = NULL; + int real_part = 0, rc = -EINVAL; + + if (devno == 0 && devname) { + struct stat st; + + DBG(DEV, ul_debug("%s: reading alone device", devname)); + + if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { + warnx(_("%s: not a block device"), devname); + goto leave; + } + devno = st.st_rdev; + } else if (devno) { + DBG(DEV, ul_debug("%d:%d: reading alone device", major(devno), minor(devno))); + } else { + assert(devno || devname); + return -EINVAL; + } + + /* TODO: sysfs_devno_to_devname() internally initializes path_cxt, it + * would be better to use ul_new_sysfs_path() + sysfs_blkdev_get_name() + * and reuse path_cxt for initialize_device() + */ + name = sysfs_devno_to_devname(devno, buf, sizeof(buf)); + if (!name) { + if (devname) + warn(_("%s: failed to get sysfs name"), devname); + goto leave; + } + name = xstrdup(name); + + if (!strncmp(name, "dm-", 3)) { + /* dm mapping is never a real partition! */ + real_part = 0; + } else { + dev_t diskno = 0; + + if (blkid_devno_to_wholedisk(devno, buf, sizeof(buf), &diskno)) { + warn(_("%s: failed to get whole-disk device number"), name); + goto leave; + } + diskname = buf; + real_part = devno != diskno; + } + + if (!real_part) { + /* + * Device is not a partition. + */ + DBG(DEV, ul_debug(" non-partition")); + + dev = devtree_get_device_or_new(tr, NULL, name); + if (!dev) + goto leave; + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, !lsblk->inverse); + } else { + /* + * Partition, read sysfs name of the disk device + */ + DBG(DEV, ul_debug(" partition")); + + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) + goto leave; + + dev = devtree_get_device_or_new(tr, disk, name); + if (!dev) + goto leave; + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, 1); + + if (lsblk->inverse + && lsblk_device_new_dependence(dev, disk) == 0) + process_dependencies(tr, disk, 0); + else + ul_path_close_dirfd(disk->sysfs); + } + + rc = 0; +leave: + if (dev && dev->sysfs) + ul_path_close_dirfd(dev->sysfs); + if (disk && disk->sysfs) + ul_path_close_dirfd(disk->sysfs); + free(name); + return rc; +} + +static int process_one_device(struct lsblk_devtree *tr, char *devname) +{ + assert(devname); + return __process_one_device(tr, devname, 0); +} + +/* + * The /sys/block contains only root devices, and no partitions. It seems more + * simple to scan /sys/dev/block where are all devices without exceptions to get + * top-level devices for the reverse tree. + */ +static int process_all_devices_inverse(struct lsblk_devtree *tr) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc = ul_new_path(_PATH_SYS_DEVBLOCK); + + assert(lsblk->inverse); + + if (!pc) + err(EXIT_FAILURE, _("failed to allocate /sys handler")); + + ul_path_set_prefix(pc, lsblk->sysroot); + dir = ul_path_opendir(pc, NULL); + if (!dir) + goto done; + + DBG(DEV, ul_debug("iterate on " _PATH_SYS_DEVBLOCK)); + + while ((d = xreaddir(dir))) { + dev_t devno; + int maj, min; + + DBG(DEV, ul_debug(" %s dentry", d->d_name)); + + if (sscanf(d->d_name, "%d:%d", &maj, &min) != 2) + continue; + devno = makedev(maj, min); + + if (is_maj_excluded(maj) || !is_maj_included(maj)) + continue; + if (ul_path_countf_dirents(pc, "%s/holders", d->d_name) != 0) + continue; + if (sysfs_devno_count_partitions(devno) != 0) + continue; + __process_one_device(tr, NULL, devno); + } + + closedir(dir); +done: + ul_unref_path(pc); + DBG(DEV, ul_debug("iterate on " _PATH_SYS_DEVBLOCK " -- done")); + return 0; +} + +/* + * Reads root nodes (devices) from /sys/block into devices tree + */ +static int process_all_devices(struct lsblk_devtree *tr) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc; + + assert(lsblk->inverse == 0); + + pc = ul_new_path(_PATH_SYS_BLOCK); + if (!pc) + err(EXIT_FAILURE, _("failed to allocate /sys handler")); + + ul_path_set_prefix(pc, lsblk->sysroot); + dir = ul_path_opendir(pc, NULL); + if (!dir) + goto done; + + DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK)); + + while ((d = xreaddir(dir))) { + struct lsblk_device *dev = NULL; + + DBG(DEV, ul_debug(" %s dentry", d->d_name)); + dev = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dev) + goto next; + + /* remove unwanted devices */ + if (is_maj_excluded(dev->maj) || !is_maj_included(dev->maj)) { + DBG(DEV, ul_debug(" %s: ignore (by filter)", d->d_name)); + lsblk_devtree_remove_device(tr, dev); + dev = NULL; + goto next; + } + + if (dev->nslaves) { + DBG(DEV, ul_debug(" %s: ignore (in-middle)", d->d_name)); + goto next; + } + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, 1); +next: + /* Let's be careful with number of open files */ + if (dev && dev->sysfs) + ul_path_close_dirfd(dev->sysfs); + } + + closedir(dir); +done: + ul_unref_path(pc); + DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK " -- done")); + return 0; +} + +/* + * Parses major numbers as specified on lsblk command line + */ +static void parse_excludes(const char *str0) +{ + const char *str = str0; + + while (str && *str) { + char *end = NULL; + unsigned long n; + + errno = 0; + n = strtoul(str, &end, 10); + + if (end == str || (end && *end && *end != ',')) + errx(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + if (errno != 0 && (n == ULONG_MAX || n == 0)) + err(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + excludes[nexcludes++] = n; + + if (nexcludes == ARRAY_SIZE(excludes)) + /* TRANSLATORS: The standard value for %d is 256. */ + errx(EXIT_FAILURE, _("the list of excluded devices is " + "too large (limit is %d devices)"), + (int)ARRAY_SIZE(excludes)); + + str = end && *end ? end + 1 : NULL; + } +} + +/* + * Parses major numbers as specified on lsblk command line + * (TODO: what about refactor and merge parse_excludes() and parse_includes().) + */ +static void parse_includes(const char *str0) +{ + const char *str = str0; + + while (str && *str) { + char *end = NULL; + unsigned long n; + + errno = 0; + n = strtoul(str, &end, 10); + + if (end == str || (end && *end && *end != ',')) + errx(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + if (errno != 0 && (n == ULONG_MAX || n == 0)) + err(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + includes[nincludes++] = n; + + if (nincludes == ARRAY_SIZE(includes)) + /* TRANSLATORS: The standard value for %d is 256. */ + errx(EXIT_FAILURE, _("the list of included devices is " + "too large (limit is %d devices)"), + (int)ARRAY_SIZE(includes)); + str = end && *end ? end + 1 : NULL; + } +} + +/* + * see set_sortdata_u64() and columns initialization in main() + */ +static int cmp_u64_cells(struct libscols_cell *a, + struct libscols_cell *b, + __attribute__((__unused__)) void *data) +{ + uint64_t *adata = (uint64_t *) scols_cell_get_userdata(a), + *bdata = (uint64_t *) scols_cell_get_userdata(b); + + if (adata == NULL && bdata == NULL) + return 0; + if (adata == NULL) + return -1; + if (bdata == NULL) + return 1; + return *adata == *bdata ? 0 : *adata >= *bdata ? 1 : -1; +} + +static void device_set_dedupkey( + struct lsblk_device *dev, + struct lsblk_device *parent, + int id) +{ + struct lsblk_iter itr; + struct lsblk_device *child = NULL; + + dev->dedupkey = device_get_data(dev, parent, id, NULL); + if (dev->dedupkey) + DBG(DEV, ul_debugobj(dev, "%s: de-duplication key: %s", dev->name, dev->dedupkey)); + + if (dev->npartitions == 0) + /* For partitions we often read from parental whole-disk sysfs, + * otherwise we can close */ + ul_path_close_dirfd(dev->sysfs); + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_device_next_child(dev, &itr, &child) == 0) + device_set_dedupkey(child, dev, id); + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); +} + +static void devtree_set_dedupkeys(struct lsblk_devtree *tr, int id) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) + device_set_dedupkey(dev, NULL, id); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [<device> ...]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("List information about block devices.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -D, --discard print discard capabilities\n"), out); + fputs(_(" -E, --dedup <column> de-duplicate output by <column>\n"), out); + fputs(_(" -I, --include <list> show only devices with specified major numbers\n"), out); + fputs(_(" -J, --json use JSON output format\n"), out); + fputs(_(" -O, --output-all output all columns\n"), out); + fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); + fputs(_(" -S, --scsi output info about SCSI devices\n"), out); + fputs(_(" -T, --tree[=<column>] use tree format output\n"), out); + fputs(_(" -a, --all print all devices\n"), out); + fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); + fputs(_(" -d, --nodeps don't print slaves or holders\n"), out); + fputs(_(" -e, --exclude <list> exclude devices by major number (default: RAM disks)\n"), out); + fputs(_(" -f, --fs output info about filesystems\n"), out); + fputs(_(" -i, --ascii use ascii characters only\n"), out); + fputs(_(" -l, --list use list format output\n"), out); + fputs(_(" -M, --merge group parents of sub-trees (usable for RAIDs, Multi-path)\n"), out); + fputs(_(" -m, --perms output info about permissions\n"), out); + fputs(_(" -n, --noheadings don't print headings\n"), out); + fputs(_(" -o, --output <list> output columns\n"), out); + fputs(_(" -p, --paths print complete device path\n"), out); + fputs(_(" -r, --raw use raw output format\n"), out); + fputs(_(" -s, --inverse inverse dependencies\n"), out); + fputs(_(" -t, --topology output info about topology\n"), out); + fputs(_(" -z, --zoned print zone model\n"), out); + fputs(_(" -x, --sort <column> sort output by <column>\n"), out); + fputs(_(" --sysroot <dir> use specified directory as system root\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(22)); + + fprintf(out, USAGE_COLUMNS); + + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + printf(USAGE_MAN_TAIL("lsblk(8)")); + + exit(EXIT_SUCCESS); +} + +static void check_sysdevblock(void) +{ + if (access(_PATH_SYS_DEVBLOCK, R_OK) != 0) + err(EXIT_FAILURE, _("failed to access sysfs directory: %s"), + _PATH_SYS_DEVBLOCK); +} + +int main(int argc, char *argv[]) +{ + struct lsblk _ls = { + .sort_id = -1, + .dedup_id = -1, + .flags = LSBLK_TREE, + .tree_id = COL_NAME + }; + struct lsblk_devtree *tr = NULL; + int c, status = EXIT_FAILURE; + char *outarg = NULL; + size_t i; + int force_tree = 0, has_tree_col = 0; + + enum { + OPT_SYSROOT = CHAR_MAX + 1 + }; + + static const struct option longopts[] = { + { "all", no_argument, NULL, 'a' }, + { "bytes", no_argument, NULL, 'b' }, + { "nodeps", no_argument, NULL, 'd' }, + { "discard", no_argument, NULL, 'D' }, + { "dedup", required_argument, NULL, 'E' }, + { "zoned", no_argument, NULL, 'z' }, + { "help", no_argument, NULL, 'h' }, + { "json", no_argument, NULL, 'J' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, 'O' }, + { "merge", no_argument, NULL, 'M' }, + { "perms", no_argument, NULL, 'm' }, + { "noheadings", no_argument, NULL, 'n' }, + { "list", no_argument, NULL, 'l' }, + { "ascii", no_argument, NULL, 'i' }, + { "raw", no_argument, NULL, 'r' }, + { "inverse", no_argument, NULL, 's' }, + { "fs", no_argument, NULL, 'f' }, + { "exclude", required_argument, NULL, 'e' }, + { "include", required_argument, NULL, 'I' }, + { "topology", no_argument, NULL, 't' }, + { "paths", no_argument, NULL, 'p' }, + { "pairs", no_argument, NULL, 'P' }, + { "scsi", no_argument, NULL, 'S' }, + { "sort", required_argument, NULL, 'x' }, + { "sysroot", required_argument, NULL, OPT_SYSROOT }, + { "tree", optional_argument, NULL, 'T' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'D','O' }, + { 'I','e' }, + { 'J', 'P', 'r' }, + { 'O','S' }, + { 'O','f' }, + { 'O','m' }, + { 'O','o' }, + { 'O','t' }, + { 'P','T', 'l','r' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + lsblk = &_ls; + + lsblk_init_debug(); + + while((c = getopt_long(argc, argv, + "abdDzE:e:fhJlnMmo:OpPiI:rstVST::x:", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'a': + lsblk->all_devices = 1; + break; + case 'b': + lsblk->bytes = 1; + break; + case 'd': + lsblk->nodeps = 1; + break; + case 'D': + add_uniq_column(COL_NAME); + add_uniq_column(COL_DALIGN); + add_uniq_column(COL_DGRAN); + add_uniq_column(COL_DMAX); + add_uniq_column(COL_DZERO); + break; + case 'z': + add_uniq_column(COL_NAME); + add_uniq_column(COL_ZONED); + break; + case 'e': + parse_excludes(optarg); + break; + case 'J': + lsblk->flags |= LSBLK_JSON; + break; + case 'l': + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + break; + case 'M': + lsblk->merge = 1; + break; + case 'n': + lsblk->flags |= LSBLK_NOHEADINGS; + break; + case 'o': + outarg = optarg; + break; + case 'O': + for (ncolumns = 0 ; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; + case 'p': + lsblk->paths = 1; + break; + case 'P': + lsblk->flags |= LSBLK_EXPORT; + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + break; + case 'i': + lsblk->flags |= LSBLK_ASCII; + break; + case 'I': + parse_includes(optarg); + break; + case 'r': + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + lsblk->flags |= LSBLK_RAW; /* enable raw */ + break; + case 's': + lsblk->inverse = 1; + break; + case 'f': + add_uniq_column(COL_NAME); + add_uniq_column(COL_FSTYPE); + add_uniq_column(COL_FSVERSION); + add_uniq_column(COL_LABEL); + add_uniq_column(COL_UUID); + add_uniq_column(COL_FSAVAIL); + add_uniq_column(COL_FSUSEPERC); + add_uniq_column(COL_TARGET); + break; + case 'm': + add_uniq_column(COL_NAME); + add_uniq_column(COL_SIZE); + add_uniq_column(COL_OWNER); + add_uniq_column(COL_GROUP); + add_uniq_column(COL_MODE); + break; + case 't': + add_uniq_column(COL_NAME); + add_uniq_column(COL_ALIOFF); + add_uniq_column(COL_MINIO); + add_uniq_column(COL_OPTIO); + add_uniq_column(COL_PHYSEC); + add_uniq_column(COL_LOGSEC); + add_uniq_column(COL_ROTA); + add_uniq_column(COL_SCHED); + add_uniq_column(COL_RQ_SIZE); + add_uniq_column(COL_RA); + add_uniq_column(COL_WSAME); + break; + case 'S': + lsblk->nodeps = 1; + lsblk->scsi = 1; + add_uniq_column(COL_NAME); + add_uniq_column(COL_HCTL); + add_uniq_column(COL_TYPE); + add_uniq_column(COL_VENDOR); + add_uniq_column(COL_MODEL); + add_uniq_column(COL_REV); + add_uniq_column(COL_SERIAL); + add_uniq_column(COL_TRANSPORT); + break; + case 'T': + force_tree = 1; + if (optarg) { + if (*optarg == '=') + optarg++; + lsblk->tree_id = column_name_to_id(optarg, strlen(optarg)); + } + break; + case OPT_SYSROOT: + lsblk->sysroot = optarg; + break; + case 'E': + lsblk->dedup_id = column_name_to_id(optarg, strlen(optarg)); + if (lsblk->dedup_id >= 0) + break; + errtryhelp(EXIT_FAILURE); + break; + case 'x': + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + lsblk->sort_id = column_name_to_id(optarg, strlen(optarg)); + if (lsblk->sort_id >= 0) + break; + errtryhelp(EXIT_FAILURE); + break; + + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (force_tree) + lsblk->flags |= LSBLK_TREE; + + check_sysdevblock(); + + if (!ncolumns) { + add_column(COL_NAME); + add_column(COL_MAJMIN); + add_column(COL_RM); + add_column(COL_SIZE); + add_column(COL_RO); + add_column(COL_TYPE); + add_column(COL_TARGET); + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + if (lsblk->all_devices == 0 && nexcludes == 0 && nincludes == 0) + excludes[nexcludes++] = 1; /* default: ignore RAM disks */ + + if (lsblk->sort_id < 0) + /* Since Linux 4.8 we have sort devices by default, because + * /sys is no more sorted */ + lsblk->sort_id = COL_MAJMIN; + + /* For --{inverse,raw,pairs} --list we still follow parent->child relation */ + if (!(lsblk->flags & LSBLK_TREE) + && (lsblk->inverse || lsblk->flags & LSBLK_EXPORT || lsblk->flags & LSBLK_RAW)) + lsblk->force_tree_order = 1; + + if (lsblk->sort_id >= 0 && column_id_to_number(lsblk->sort_id) < 0) { + /* the sort column is not between output columns -- add as hidden */ + add_column(lsblk->sort_id); + lsblk->sort_hidden = 1; + } + + if (lsblk->dedup_id >= 0 && column_id_to_number(lsblk->dedup_id) < 0) { + /* the deduplication column is not between output columns -- add as hidden */ + add_column(lsblk->dedup_id); + lsblk->dedup_hidden = 1; + } + + lsblk_mnt_init(); + scols_init_debug(0); + ul_path_init_debug(); + + /* + * initialize output columns + */ + if (!(lsblk->table = scols_new_table())) + errx(EXIT_FAILURE, _("failed to allocate output table")); + scols_table_enable_raw(lsblk->table, !!(lsblk->flags & LSBLK_RAW)); + scols_table_enable_export(lsblk->table, !!(lsblk->flags & LSBLK_EXPORT)); + scols_table_enable_ascii(lsblk->table, !!(lsblk->flags & LSBLK_ASCII)); + scols_table_enable_json(lsblk->table, !!(lsblk->flags & LSBLK_JSON)); + scols_table_enable_noheadings(lsblk->table, !!(lsblk->flags & LSBLK_NOHEADINGS)); + + if (lsblk->flags & LSBLK_JSON) + scols_table_set_name(lsblk->table, "blockdevices"); + + for (i = 0; i < ncolumns; i++) { + struct colinfo *ci = get_column_info(i); + struct libscols_column *cl; + int id = get_column_id(i), fl = ci->flags; + + if ((lsblk->flags & LSBLK_TREE) + && has_tree_col == 0 + && id == lsblk->tree_id) { + fl |= SCOLS_FL_TREE; + fl &= ~SCOLS_FL_RIGHT; + has_tree_col = 1; + } + + if (lsblk->sort_hidden && lsblk->sort_id == id) + fl |= SCOLS_FL_HIDDEN; + if (lsblk->dedup_hidden && lsblk->dedup_id == id) + fl |= SCOLS_FL_HIDDEN; + + if (force_tree + && lsblk->flags & LSBLK_JSON + && has_tree_col == 0 + && i + 1 == ncolumns) + /* The "--tree --json" specified, but no column with + * SCOLS_FL_TREE yet; force it for the last column + */ + fl |= SCOLS_FL_TREE; + + cl = scols_table_new_column(lsblk->table, ci->name, ci->whint, fl); + if (!cl) { + warn(_("failed to allocate output column")); + goto leave; + } + if (!lsblk->sort_col && lsblk->sort_id == id) { + lsblk->sort_col = cl; + scols_column_set_cmpfunc(cl, + ci->type == COLTYPE_NUM ? cmp_u64_cells : + ci->type == COLTYPE_SIZE ? cmp_u64_cells : + ci->type == COLTYPE_SORTNUM ? cmp_u64_cells : scols_cmpstr_cells, + NULL); + } + if (lsblk->flags & LSBLK_JSON) { + switch (ci->type) { + case COLTYPE_SIZE: + if (!lsblk->bytes) + break; + /* fallthrough */ + case COLTYPE_NUM: + scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + break; + case COLTYPE_BOOL: + scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); + break; + default: + scols_column_set_json_type(cl, SCOLS_JSON_STRING); + break; + } + } + } + + tr = lsblk_new_devtree(); + if (!tr) + err(EXIT_FAILURE, _("failed to allocate device tree")); + + if (optind == argc) { + int rc = lsblk->inverse ? + process_all_devices_inverse(tr) : + process_all_devices(tr); + + status = rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + } else { + int cnt = 0, cnt_err = 0; + + while (optind < argc) { + if (process_one_device(tr, argv[optind++]) != 0) + cnt_err++; + cnt++; + } + status = cnt == 0 ? EXIT_FAILURE : /* nothing */ + cnt == cnt_err ? LSBLK_EXIT_ALLFAILED :/* all failed */ + cnt_err ? LSBLK_EXIT_SOMEOK : /* some ok */ + EXIT_SUCCESS; /* all success */ + } + + if (lsblk->dedup_id > -1) { + devtree_set_dedupkeys(tr, lsblk->dedup_id); + lsblk_devtree_deduplicate_devices(tr); + } + + devtree_to_scols(tr, lsblk->table); + + if (lsblk->sort_col) + scols_sort_table(lsblk->table, lsblk->sort_col); + if (lsblk->force_tree_order) + scols_sort_table_by_tree(lsblk->table); + + scols_print_table(lsblk->table); + +leave: + if (lsblk->sort_col) + unref_sortdata(lsblk->table); + + scols_unref_table(lsblk->table); + + lsblk_mnt_deinit(); + lsblk_properties_deinit(); + lsblk_unref_devtree(tr); + + return status; +} |