diff options
Diffstat (limited to 'misc-utils/lsblk.c')
-rw-r--r-- | misc-utils/lsblk.c | 1622 |
1 files changed, 876 insertions, 746 deletions
diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c index fadef65c4..ffa1f82bf 100644 --- a/misc-utils/lsblk.c +++ b/misc-utils/lsblk.c @@ -1,7 +1,7 @@ /* * lsblk(8) - list block devices * - * Copyright (C) 2010,2011,2012 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved. * Written by Milan Broz <mbroz@redhat.com> * Karel Zak <kzak@redhat.com> * @@ -19,35 +19,25 @@ * 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 <stdint.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <fcntl.h> #include <string.h> #include <sys/ioctl.h> -#include <inttypes.h> #include <stdarg.h> #include <locale.h> #include <pwd.h> #include <grp.h> #include <ctype.h> +#include <assert.h> #include <blkid.h> -#include <libmount.h> -#include <libsmartcols.h> - -#ifdef HAVE_LIBUDEV -#include <libudev.h> -#endif - -#include <assert.h> #include "c.h" #include "pathnames.h" @@ -58,26 +48,13 @@ #include "strutils.h" #include "sysfs.h" #include "closestream.h" -#include "mangle.h" #include "optutils.h" -#include "debug.h" +#include "lsblk.h" -static UL_DEBUG_DEFINE_MASK(lsblk); +UL_DEBUG_DEFINE_MASK(lsblk); UL_DEBUG_DEFINE_MASKNAMES(lsblk) = UL_DEBUG_EMPTY_MASKNAMES; -#define LSBLK_DEBUG_INIT (1 << 1) -#define LSBLK_DEBUG_FILTER (1 << 2) -#define LSBLK_DEBUG_DEV (1 << 3) -#define LSBLK_DEBUG_CXT (1 << 4) -#define LSBLK_DEBUG_ALL 0xFFFF - -#define DBG(m, x) __UL_DBG(lsblk, LSBLK_DEBUG_, m, x) -#define ON_DBG(m, x) __UL_DBG_CALL(lsblk, LSBLK_DEBUG_, m, x) - -#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(lsblk) -#include "debugobj.h" - #define LSBLK_EXIT_SOMEOK 64 #define LSBLK_EXIT_ALLFAILED 32 @@ -87,11 +64,18 @@ static int column_id_to_number(int id); enum { COL_NAME = 0, COL_KNAME, + COL_PATH, COL_MAJMIN, + COL_FSAVAIL, + COL_FSSIZE, COL_FSTYPE, + COL_FSUSED, + COL_FSUSEPERC, COL_TARGET, COL_LABEL, COL_UUID, + COL_PTUUID, + COL_PTTYPE, COL_PARTTYPE, COL_PARTLABEL, COL_PARTUUID, @@ -157,7 +141,6 @@ struct colinfo { double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* SCOLS_FL_* */ const char *help; - int type; /* COLTYPE_* */ }; @@ -165,13 +148,23 @@ struct colinfo { static struct colinfo infos[] = { [COL_NAME] = { "NAME", 0.25, SCOLS_FL_TREE | 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_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_FSTYPE] = { "FSTYPE", 0.1, SCOLS_FL_TRUNC, N_("filesystem type") }, + + [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_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 UUID") }, [COL_PARTLABEL] = { "PARTLABEL", 0.1, 0, N_("partition LABEL") }, [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") }, @@ -212,33 +205,17 @@ static struct colinfo infos[] = { [COL_ZONED] = { "ZONED", 0.3, 0, N_("zone model") }, }; -struct lsblk { - struct libscols_table *table; /* output table */ - struct libscols_column *sort_col;/* sort output by this column */ - int sort_id; - - int flags; /* LSBLK_* */ - - unsigned int all_devices:1; /* print all devices, including empty */ - unsigned int bytes:1; /* print SIZE in bytes */ - unsigned int inverse:1; /* print inverse dependencies */ - unsigned int nodeps:1; /* don't print slaves/holders */ - unsigned int scsi:1; /* print only device with HCTL (SCSI) */ - unsigned int paths:1; /* print devnames with "/dev" prefix */ - unsigned int sort_hidden:1; /* sort column not between output columns */ - unsigned int force_tree_order:1;/* sort lines by parent->tree relation */ -}; +struct lsblk *lsblk; /* global handler */ -static struct lsblk *lsblk; /* global handler */ - -/* columns[] array specifies all currently wanted output column. The columns +/* + * 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 */ + * 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)) @@ -254,60 +231,20 @@ static inline void add_uniq_column(int id) 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 struct libmnt_table *mtab, *swaps; -static struct libmnt_cache *mntcache; - -#ifdef HAVE_LIBUDEV -static struct udev *udev; -#endif - -struct blkdev_cxt { - struct blkdev_cxt *parent; - - struct libscols_line *scols_line; - struct stat st; - - char *name; /* kernel name in /sys/block */ - char *dm_name; /* DM name (dm/block) */ - - char *filename; /* path to device node */ - - struct sysfs_cxt sysfs; - - int partition; /* is partition? TRUE/FALSE */ - - int probed; /* already probed */ - char *fstype; /* detected fs, NULL or "?" if cannot detect */ - char *uuid; /* filesystem UUID (or stack uuid) */ - char *label; /* filesystem label */ - char *parttype; /* partition type UUID */ - char *partuuid; /* partition UUID */ - char *partlabel; /* partition label */ - char *partflags; /* partition flags */ - char *wwn; /* storage WWN */ - char *serial; /* disk serial number */ - - int npartitions; /* # of partitions this device has */ - int nholders; /* # of devices mapped directly to this device - * /sys/block/.../holders */ - int nslaves; /* # of devices this device maps to */ - int maj, min; /* devno */ - int discard; /* supports discard */ - - uint64_t size; /* device size */ -}; - -static void lsblk_init_debug(void) -{ - __UL_INIT_DEBUG_FROM_ENV(lsblk, LSBLK_DEBUG_, 0, LSBLK_DEBUG); -} - static int is_maj_excluded(int maj) { size_t i; @@ -344,7 +281,7 @@ static int is_maj_included(int maj) return 0; } -/* array with IDs of enabled columns */ +/* Converts column sequential number to column ID (COL_*) */ static int get_column_id(int num) { assert(num >= 0); @@ -353,11 +290,13 @@ static int get_column_id(int num) 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; @@ -372,6 +311,7 @@ static int column_name_to_id(const char *name, size_t namesz) return -1; } +/* Converts column ID (COL_*) to column sequential number */ static int column_id_to_number(int id) { size_t i; @@ -382,35 +322,13 @@ static int column_id_to_number(int id) return -1; } -static void reset_blkdev_cxt(struct blkdev_cxt *cxt) -{ - if (!cxt) - return; - - DBG(CXT, ul_debugobj(cxt, "reset")); - - free(cxt->name); - free(cxt->dm_name); - free(cxt->filename); - free(cxt->fstype); - free(cxt->uuid); - free(cxt->label); - free(cxt->parttype); - free(cxt->partuuid); - free(cxt->partlabel); - free(cxt->wwn); - free(cxt->serial); - - sysfs_deinit(&cxt->sysfs); - - memset(cxt, 0, sizeof(*cxt)); -} - +/* Checks for DM prefix in the device name */ static int is_dm(const char *name) { return strncmp(name, "dm-", 3) ? 0 : 1; } +/* This is readdir()-like function, but skips "." and ".." directory entries */ static struct dirent *xreaddir(DIR *dp) { struct dirent *d; @@ -428,225 +346,31 @@ static struct dirent *xreaddir(DIR *dp) return d; } -static char *get_device_path(struct blkdev_cxt *cxt) +/* 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(cxt); - assert(cxt->name); + assert(dev); + assert(dev->name); - if (is_dm(cxt->name)) - return canonicalize_dm_name(cxt->name); + if (is_dm(dev->name)) + return __canonicalize_dm_name(lsblk->sysroot, dev->name); - snprintf(path, sizeof(path), "/dev/%s", cxt->name); + snprintf(path, sizeof(path), "/dev/%s", dev->name); sysfs_devname_sys_to_dev(path); return xstrdup(path); } -static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)), - const char *filename, int line) -{ - if (filename) - warnx(_("%s: parse error at line %d -- ignored"), filename, line); - return 1; -} - -static int is_active_swap(const char *filename) -{ - if (!swaps) { - swaps = mnt_new_table(); - if (!swaps) - return 0; - if (!mntcache) - mntcache = mnt_new_cache(); - - mnt_table_set_parser_errcb(swaps, table_parser_errcb); - mnt_table_set_cache(swaps, mntcache); - mnt_table_parse_swaps(swaps, NULL); - } - - return mnt_table_find_srcpath(swaps, filename, MNT_ITER_BACKWARD) != NULL; -} - -static char *get_device_mountpoint(struct blkdev_cxt *cxt) -{ - struct libmnt_fs *fs; - const char *fsroot; - - assert(cxt); - assert(cxt->filename); - - if (!mtab) { - mtab = mnt_new_table(); - if (!mtab) - return NULL; - if (!mntcache) - mntcache = mnt_new_cache(); - - mnt_table_set_parser_errcb(mtab, table_parser_errcb); - mnt_table_set_cache(mtab, mntcache); - mnt_table_parse_mtab(mtab, NULL); - } - - /* Note that maj:min in /proc/self/mountinfo does not have to match with - * devno as returned by stat(), so we have to try devname too - */ - fs = mnt_table_find_devno(mtab, makedev(cxt->maj, cxt->min), MNT_ITER_BACKWARD); - if (!fs) - fs = mnt_table_find_srcpath(mtab, cxt->filename, MNT_ITER_BACKWARD); - if (!fs) - return is_active_swap(cxt->filename) ? xstrdup("[SWAP]") : NULL; - - /* found */ - fsroot = mnt_fs_get_root(fs); - if (fsroot && strcmp(fsroot, "/") != 0) { - /* hmm.. we found bind mount or btrfs subvolume, let's try to - * get real FS root mountpoint */ - struct libmnt_fs *rfs; - struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD); - - mnt_table_set_iter(mtab, itr, fs); - while (mnt_table_next_fs(mtab, itr, &rfs) == 0) { - fsroot = mnt_fs_get_root(rfs); - if ((!fsroot || strcmp(fsroot, "/") == 0) - && mnt_fs_match_source(rfs, cxt->filename, mntcache)) { - fs = rfs; - break; - } - } - mnt_free_iter(itr); - } - - DBG(DEV, ul_debugobj(cxt, "mountpoint: %s", mnt_fs_get_target(fs))); - return xstrdup(mnt_fs_get_target(fs)); -} - -#ifndef HAVE_LIBUDEV -static int get_udev_properties(struct blkdev_cxt *cxt - __attribute__((__unused__))) -{ - return -1; -} -#else -static int get_udev_properties(struct blkdev_cxt *cxt) -{ - struct udev_device *dev; - - if (cxt->probed) - return 0; /* already done */ - - if (!udev) - udev = udev_new(); - if (!udev) - return -1; - - dev = udev_device_new_from_subsystem_sysname(udev, "block", cxt->name); - if (dev) { - const char *data; - - if ((data = udev_device_get_property_value(dev, "ID_FS_LABEL_ENC"))) { - cxt->label = xstrdup(data); - unhexmangle_string(cxt->label); - } - if ((data = udev_device_get_property_value(dev, "ID_FS_UUID_ENC"))) { - cxt->uuid = xstrdup(data); - unhexmangle_string(cxt->uuid); - } - if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"))) { - cxt->partlabel = xstrdup(data); - unhexmangle_string(cxt->partlabel); - } - if ((data = udev_device_get_property_value(dev, "ID_FS_TYPE"))) - cxt->fstype = xstrdup(data); - if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_TYPE"))) - cxt->parttype = xstrdup(data); - if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_UUID"))) - cxt->partuuid = xstrdup(data); - if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_FLAGS"))) - cxt->partflags = xstrdup(data); - - data = udev_device_get_property_value(dev, "ID_WWN_WITH_EXTENSION"); - if (!data) - data = udev_device_get_property_value(dev, "ID_WWN"); - if (data) - cxt->wwn = xstrdup(data); - - if ((data = udev_device_get_property_value(dev, "ID_SERIAL_SHORT"))) - cxt->serial = xstrdup(data); - udev_device_unref(dev); - cxt->probed = 1; - DBG(DEV, ul_debugobj(cxt, "%s: found udev properties", cxt->name)); - } - - return cxt->probed == 1 ? 0 : -1; - -} -#endif /* HAVE_LIBUDEV */ - -static void probe_device(struct blkdev_cxt *cxt) -{ - blkid_probe pr = NULL; - - if (cxt->probed) - return; - - if (!cxt->size) - return; - - /* try udev DB */ - if (get_udev_properties(cxt) == 0) - return; /* success */ - - cxt->probed = 1; - - /* try libblkid (fallback) */ - if (getuid() != 0) - return; /* no permissions to read from the device */ - - pr = blkid_new_probe_from_filename(cxt->filename); - if (!pr) - return; - - blkid_probe_enable_superblocks(pr, 1); - blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_LABEL | - BLKID_SUBLKS_UUID | - BLKID_SUBLKS_TYPE); - blkid_probe_enable_partitions(pr, 1); - blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); - - if (!blkid_do_safeprobe(pr)) { - const char *data = NULL; - - if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) - cxt->fstype = xstrdup(data); - if (!blkid_probe_lookup_value(pr, "UUID", &data, NULL)) - cxt->uuid = xstrdup(data); - if (!blkid_probe_lookup_value(pr, "LABEL", &data, NULL)) - cxt->label = xstrdup(data); - if (!blkid_probe_lookup_value(pr, "PART_ENTRY_TYPE", &data, NULL)) - cxt->parttype = xstrdup(data); - if (!blkid_probe_lookup_value(pr, "PART_ENTRY_UUID", &data, NULL)) - cxt->partuuid = xstrdup(data); - if (!blkid_probe_lookup_value(pr, "PART_ENTRY_NAME", &data, NULL)) - cxt->partlabel = xstrdup(data); - if (!blkid_probe_lookup_value(pr, "PART_ENTRY_FLAGS", &data, NULL)) - cxt->partflags = xstrdup(data); - DBG(DEV, ul_debugobj(cxt, "%s: found blkid properties", cxt->name)); - } - - blkid_free_probe(pr); - return; -} - -static int is_readonly_device(struct blkdev_cxt *cxt) +static int is_readonly_device(struct lsblk_device *dev) { int fd, ro = 0; - if (sysfs_scanf(&cxt->sysfs, "ro", "%d", &ro) == 1) + if (ul_path_scanf(dev->sysfs, "ro", "%d", &ro) == 1) return ro; /* fallback if "ro" attribute does not exist */ - fd = open(cxt->filename, O_RDONLY); + fd = open(dev->filename, O_RDONLY); if (fd != -1) { if (ioctl(fd, BLKROGET, &ro) != 0) ro = 0; @@ -655,14 +379,14 @@ static int is_readonly_device(struct blkdev_cxt *cxt) return ro; } -static char *get_scheduler(struct blkdev_cxt *cxt) +static char *get_scheduler(struct lsblk_device *dev) { - char *str = sysfs_strdup(&cxt->sysfs, "queue/scheduler"); + char buf[128]; char *p, *res = NULL; - if (!str) + if (ul_path_read_buffer(dev->sysfs, buf, sizeof(buf), "queue/scheduler") == 0) return NULL; - p = strchr(str, '['); + p = strchr(buf, '['); if (p) { res = p + 1; p = strchr(res, ']'); @@ -672,20 +396,23 @@ static char *get_scheduler(struct blkdev_cxt *cxt) } else res = NULL; } - free(str); return res; } -static char *get_type(struct blkdev_cxt *cxt) +static char *get_type(struct lsblk_device *dev) { char *res = NULL, *p; - if (is_dm(cxt->name)) { - char *dm_uuid = sysfs_strdup(&cxt->sysfs, "dm/uuid"); + 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 (dm_uuid) { + if (ul_path_read_string(dev->sysfs, &dm_uuid, "dm/uuid") > 0 + && dm_uuid) { char *tmp = dm_uuid; char *dm_uuid_prefix = strsep(&tmp, "-"); @@ -703,22 +430,23 @@ static char *get_type(struct blkdev_cxt *cxt) /* No UUID or no prefix - just mark it as DM device */ res = xstrdup("dm"); - } else if (!strncmp(cxt->name, "loop", 4)) { + } else if (!strncmp(dev->name, "loop", 4)) { res = xstrdup("loop"); - } else if (!strncmp(cxt->name, "md", 2)) { - char *md_level = sysfs_strdup(&cxt->sysfs, "md/level"); + } 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 (!sysfs_read_int(&cxt->sysfs, "device/type", &x)) + if (ul_path_read_s32(dev->sysfs, &x, "device/type") == 0) type = blkdev_scsi_type_to_name(x); - if (!type) - type = cxt->partition ? "part" : "disk"; + type = "disk"; res = xstrdup(type); } @@ -728,19 +456,20 @@ static char *get_type(struct blkdev_cxt *cxt) } /* Thanks to lsscsi code for idea of detection logic used here */ -static char *get_transport(struct blkdev_cxt *cxt) +static char *get_transport(struct lsblk_device *dev) { - struct sysfs_cxt *sysfs = &cxt->sysfs; + struct path_cxt *sysfs = dev->sysfs; char *attr = NULL; const char *trans = NULL; + /* SCSI - Serial Peripheral Interface */ - if (sysfs_scsi_host_is(sysfs, "spi")) + if (sysfs_blkdev_scsi_host_is(sysfs, "spi")) trans = "spi"; /* FC/FCoE - Fibre Channel / Fibre Channel over Ethernet */ - else if (sysfs_scsi_host_is(sysfs, "fc")) { - attr = sysfs_scsi_host_strdup_attribute(sysfs, "fc", "symbolic_name"); + 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"; @@ -748,26 +477,26 @@ static char *get_transport(struct blkdev_cxt *cxt) } /* SAS - Serial Attached SCSI */ - else if (sysfs_scsi_host_is(sysfs, "sas") || - sysfs_scsi_has_attribute(sysfs, "sas_device")) + 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_scsi_has_attribute(sysfs, "ieee1394_id")) + else if (sysfs_blkdev_scsi_has_attribute(sysfs, "ieee1394_id")) trans = "sbp"; /* iSCSI */ - else if (sysfs_scsi_host_is(sysfs, "iscsi")) + else if (sysfs_blkdev_scsi_host_is(sysfs, "iscsi")) trans ="iscsi"; /* USB - Universal Serial Bus */ - else if (sysfs_scsi_path_contains(sysfs, "usb")) + else if (sysfs_blkdev_scsi_path_contains(sysfs, "usb")) trans = "usb"; /* ATA, SATA */ - else if (sysfs_scsi_host_is(sysfs, "scsi")) { - attr = sysfs_scsi_host_strdup_attribute(sysfs, "scsi", "proc_name"); + 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)) @@ -776,23 +505,23 @@ static char *get_transport(struct blkdev_cxt *cxt) trans = "ata"; free(attr); - } else if (strncmp(cxt->name, "nvme", 4) == 0) + } else if (strncmp(dev->name, "nvme", 4) == 0) trans = "nvme"; return trans ? xstrdup(trans) : NULL; } -static char *get_subsystems(struct blkdev_cxt *cxt) +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_get_devchain(&cxt->sysfs, path, sizeof(path)); + chain = sysfs_blkdev_get_devchain(dev->sysfs, path, sizeof(path)); if (!chain) return NULL; - while (sysfs_next_subsystem(&cxt->sysfs, chain, &sub) == 0) { + while (sysfs_blkdev_next_subsystem(dev->sysfs, chain, &sub) == 0) { size_t sz; /* don't create "block:scsi:scsi", but "block:scsi" */ @@ -861,14 +590,20 @@ static void set_sortdata_u64(struct libscols_line *ln, int col, uint64_t x) scols_cell_set_userdata(ce, data); } -static void set_sortdata_u64_from_string(struct libscols_line *ln, int col, const char *str) +/* do not modify *data on any error */ +static void str2u64(const char *str, uint64_t *data) { - uint64_t x; + uintmax_t num; + char *end = NULL; - if (!str || sscanf(str, "%"SCNu64, &x) != 1) + errno = 0; + if (str == NULL || *str == '\0') return; + num = strtoumax(str, &end, 10); - set_sortdata_u64(ln, col, x); + if (errno || str == end || (end && *end)) + return; + *data = num; } static void unref_sortdata(struct libscols_table *tb) @@ -891,671 +626,973 @@ static void unref_sortdata(struct libscols_table *tb) scols_free_iter(itr); } -static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libscols_line *ln) +static char *get_vfs_attribute(struct lsblk_device *dev, int id) { - int sort = 0, st_rc = 0; - char *str = NULL; + 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); + + return &dev->st; +} - if (!cxt->st.st_rdev && (id == COL_OWNER || id == COL_GROUP || - id == COL_MODE)) - st_rc = stat(cxt->filename, &cxt->st); +static int is_removable_device(struct lsblk_device *dev, struct lsblk_device *parent) +{ + struct path_cxt *pc; - if (lsblk->sort_id == id) - sort = 1; + 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; + + if (pc == parent->sysfs) + /* dev is partition and parent is whole-disk */ + dev->removable = is_removable_device(parent, NULL); + else + /* parent is something else, use sysfs parent */ + ul_path_scanf(pc, "removable", "%d", &dev->removable); + } +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; + char *str = NULL; switch(id) { case COL_NAME: - str = cxt->dm_name ? mk_dm_name(cxt->dm_name) : mk_name(cxt->name); + str = dev->dm_name ? mk_dm_name(dev->dm_name) : mk_name(dev->name); break; case COL_KNAME: - str = mk_name(cxt->name); + str = mk_name(dev->name); break; case COL_PKNAME: - if (cxt->parent) - str = mk_name(cxt->parent->name); + if (parent) + str = mk_name(parent->name); + break; + case COL_PATH: + if (dev->filename) + str = xstrdup(dev->filename); break; case COL_OWNER: { - struct passwd *pw = st_rc ? NULL : getpwuid(cxt->st.st_uid); + 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: { - struct group *gr = st_rc ? NULL : getgrgid(cxt->st.st_gid); + 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: { - char md[11]; + struct stat *st = device_get_stat(dev); + char md[11] = { '\0' }; - if (!st_rc) { - xstrmode(cxt->st.st_mode, md); - str = xstrdup(md); - } + if (st) + str = xstrdup(xstrmode(st->st_mode, md)); break; } case COL_MAJMIN: if (is_parsable(lsblk)) - xasprintf(&str, "%u:%u", cxt->maj, cxt->min); + xasprintf(&str, "%u:%u", dev->maj, dev->min); else - xasprintf(&str, "%3u:%-3u", cxt->maj, cxt->min); - if (sort) - set_sortdata_u64(ln, col, makedev(cxt->maj, cxt->min)); + xasprintf(&str, "%3u:%-3u", dev->maj, dev->min); + if (sortdata) + *sortdata = makedev(dev->maj, dev->min); break; case COL_FSTYPE: - probe_device(cxt); - if (cxt->fstype) - str = xstrdup(cxt->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_TARGET: - str = get_device_mountpoint(cxt); + str = xstrdup(lsblk_device_get_mountpoint(dev)); break; case COL_LABEL: - probe_device(cxt); - if (cxt->label) - str = xstrdup(cxt->label); + prop = lsblk_device_get_properties(dev); + if (prop && prop->label) + str = xstrdup(prop->label); break; case COL_UUID: - probe_device(cxt); - if (cxt->uuid) - str = xstrdup(cxt->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: - probe_device(cxt); - if (cxt->parttype) - str = xstrdup(cxt->parttype); + prop = lsblk_device_get_properties(dev); + if (prop && prop->parttype) + str = xstrdup(prop->parttype); break; case COL_PARTLABEL: - probe_device(cxt); - if (cxt->partlabel) - str = xstrdup(cxt->partlabel); + prop = lsblk_device_get_properties(dev); + if (prop && prop->partlabel) + str = xstrdup(prop->partlabel); break; case COL_PARTUUID: - probe_device(cxt); - if (cxt->partuuid) - str = xstrdup(cxt->partuuid); + prop = lsblk_device_get_properties(dev); + if (prop && prop->partuuid) + str = xstrdup(prop->partuuid); break; case COL_PARTFLAGS: - probe_device(cxt); - if (cxt->partflags) - str = xstrdup(cxt->partflags); + prop = lsblk_device_get_properties(dev); + if (prop && prop->partflags) + str = xstrdup(prop->partflags); break; case COL_WWN: - get_udev_properties(cxt); - if (cxt->wwn) - str = xstrdup(cxt->wwn); + prop = lsblk_device_get_properties(dev); + if (prop && prop->wwn) + str = xstrdup(prop->wwn); break; case COL_RA: - str = sysfs_strdup(&cxt->sysfs, "queue/read_ahead_kb"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + 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(cxt) ? "1" : "0"); + str = xstrdup(is_readonly_device(dev) ? "1" : "0"); break; case COL_RM: - str = sysfs_strdup(&cxt->sysfs, "removable"); - if (!str && cxt->sysfs.parent) - str = sysfs_strdup(cxt->sysfs.parent, "removable"); + str = xstrdup(is_removable_device(dev, parent) ? "1" : "0"); break; case COL_HOTPLUG: - str = sysfs_is_hotpluggable(&cxt->sysfs) ? xstrdup("1") : xstrdup("0"); + str = sysfs_blkdev_is_hotpluggable(dev->sysfs) ? xstrdup("1") : xstrdup("0"); break; case COL_ROTA: - str = sysfs_strdup(&cxt->sysfs, "queue/rotational"); + ul_path_read_string(dev->sysfs, &str, "queue/rotational"); break; case COL_RAND: - str = sysfs_strdup(&cxt->sysfs, "queue/add_random"); + ul_path_read_string(dev->sysfs, &str, "queue/add_random"); break; case COL_MODEL: - if (!cxt->partition && cxt->nslaves == 0) - str = sysfs_strdup(&cxt->sysfs, "device/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 (!cxt->partition && cxt->nslaves == 0) { - get_udev_properties(cxt); - if (cxt->serial) - str = xstrdup(cxt->serial); + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); + if (prop && prop->serial) + str = xstrdup(prop->serial); else - str = sysfs_strdup(&cxt->sysfs, "device/serial"); + ul_path_read_string(dev->sysfs, &str, "device/serial"); } break; case COL_REV: - if (!cxt->partition && cxt->nslaves == 0) - str = sysfs_strdup(&cxt->sysfs, "device/rev"); + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/rev"); break; case COL_VENDOR: - if (!cxt->partition && cxt->nslaves == 0) - str = sysfs_strdup(&cxt->sysfs, "device/vendor"); + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/vendor"); break; case COL_SIZE: - if (!cxt->size) + if (!dev->size) break; if (lsblk->bytes) - xasprintf(&str, "%ju", cxt->size); + xasprintf(&str, "%ju", dev->size); else - str = size_to_human_string(SIZE_SUFFIX_1LETTER, cxt->size); - if (sort) - set_sortdata_u64(ln, col, cxt->size); + str = size_to_human_string(SIZE_SUFFIX_1LETTER, dev->size); + if (sortdata) + *sortdata = dev->size; break; case COL_STATE: - if (!cxt->partition && !cxt->dm_name) - str = sysfs_strdup(&cxt->sysfs, "device/state"); - else if (cxt->dm_name) { + 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 (sysfs_read_int(&cxt->sysfs, "dm/suspended", &x) == 0) + if (ul_path_read_s32(dev->sysfs, &x, "dm/suspended") == 0) str = xstrdup(x ? "suspended" : "running"); } break; case COL_ALIOFF: - str = sysfs_strdup(&cxt->sysfs, "alignment_offset"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "alignment_offset"); + if (sortdata) + str2u64(str, sortdata); break; case COL_MINIO: - str = sysfs_strdup(&cxt->sysfs, "queue/minimum_io_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/minimum_io_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_OPTIO: - str = sysfs_strdup(&cxt->sysfs, "queue/optimal_io_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/optimal_io_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_PHYSEC: - str = sysfs_strdup(&cxt->sysfs, "queue/physical_block_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/physical_block_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_LOGSEC: - str = sysfs_strdup(&cxt->sysfs, "queue/logical_block_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/logical_block_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_SCHED: - str = get_scheduler(cxt); + str = get_scheduler(dev); break; case COL_RQ_SIZE: - str = sysfs_strdup(&cxt->sysfs, "queue/nr_requests"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/nr_requests"); + if (sortdata) + str2u64(str, sortdata); break; case COL_TYPE: - str = get_type(cxt); + str = get_type(dev); break; case COL_HCTL: { int h, c, t, l; - if (sysfs_scsi_get_hctl(&cxt->sysfs, &h, &c, &t, &l) == 0) + 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(cxt); + str = get_transport(dev); break; case COL_SUBSYS: - str = get_subsystems(cxt); + str = get_subsystems(dev); break; case COL_DALIGN: - if (cxt->discard) - str = sysfs_strdup(&cxt->sysfs, "discard_alignment"); + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "discard_alignment"); if (!str) str = xstrdup("0"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + if (sortdata) + str2u64(str, sortdata); break; case COL_DGRAN: if (lsblk->bytes) { - str = sysfs_strdup(&cxt->sysfs, "queue/discard_granularity"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/discard_granularity"); + if (sortdata) + str2u64(str, sortdata); } else { - uint64_t x; - if (sysfs_read_u64(&cxt->sysfs, - "queue/discard_granularity", &x) == 0) { - str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sort) - set_sortdata_u64(ln, col, x); - } + 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) { - str = sysfs_strdup(&cxt->sysfs, "queue/discard_max_bytes"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/discard_max_bytes"); + if (sortdata) + str2u64(str, sortdata); } else { uint64_t x; - if (sysfs_read_u64(&cxt->sysfs, - "queue/discard_max_bytes", &x) == 0) { + if (ul_path_read_u64(dev->sysfs, &x, "queue/discard_max_bytes") == 0) { str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sort) - set_sortdata_u64(ln, col, x); + if (sortdata) + *sortdata = x; } } break; case COL_DZERO: - if (cxt->discard) - str = sysfs_strdup(&cxt->sysfs, "queue/discard_zeroes_data"); + 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) { - str = sysfs_strdup(&cxt->sysfs, "queue/write_same_max_bytes"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/write_same_max_bytes"); + if (sortdata) + str2u64(str, sortdata); } else { uint64_t x; - if (sysfs_read_u64(&cxt->sysfs, - "queue/write_same_max_bytes", &x) == 0) { + if (ul_path_read_u64(dev->sysfs, &x, "queue/write_same_max_bytes") == 0) { str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sort) - set_sortdata_u64(ln, col, x); + if (sortdata) + *sortdata = x; } } if (!str) str = xstrdup("0"); break; case COL_ZONED: - str = sysfs_strdup(&cxt->sysfs, "queue/zoned"); + ul_path_read_string(dev->sysfs, &str, "queue/zoned"); break; }; - if (str && scols_line_refer_data(ln, col, str)) - err(EXIT_FAILURE, _("failed to add output data")); + return str; } -static void fill_table_line(struct blkdev_cxt *cxt, struct libscols_line *scols_parent) +/* + * 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; - cxt->scols_line = scols_table_new_line(lsblk->table, scols_parent); - if (!cxt->scols_line) + ON_DBG(DEV, if (ul_path_isopen_dirfd(dev->sysfs)) ul_debugobj(dev, "%s ---> is open!", dev->name)); + + /* Do not print device more than one in --list mode */ + if (!(lsblk->flags & LSBLK_TREE) && 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")); - for (i = 0; i < ncolumns; i++) - set_scols_data(cxt, i, get_column_id(i), cxt->scols_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 */ + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_parent(dev, &itr, &p) == 0) { + if (!p->scols_line) + continue; + scols_table_group_lines(tab, gr, p->scols_line, 0); + } + + /* Link the group -- this makes group->child connection */ + 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); + } + 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) + device_to_scols(child, dev, tab, ln); + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); } -static int set_cxt(struct blkdev_cxt *cxt, - struct blkdev_cxt *parent, - struct blkdev_cxt *wholedisk, +/* + * 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(CXT, ul_debugobj(cxt, "setting context for %s [parent=%p, wholedisk=%p]", - name, parent, wholedisk)); + DBG(DEV, ul_debugobj(dev, "initialize %s [wholedisk=%p %s]", + name, wholedisk, wholedisk ? wholedisk->name : "")); - cxt->parent = parent; - cxt->name = xstrdup(name); - cxt->partition = wholedisk != NULL; + dev->name = xstrdup(name); - cxt->filename = get_device_path(cxt); - if (!cxt->filename) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to get device path", cxt->name)); - return -1; + if (wholedisk) { + dev->wholedisk = wholedisk; + lsblk_ref_device(wholedisk); } - DBG(CXT, ul_debugobj(cxt, "%s: filename=%s", cxt->name, cxt->filename)); - devno = sysfs_devname_to_devno(cxt->name, wholedisk ? wholedisk->name : NULL); + 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(CXT, ul_debugobj(cxt, "%s: unknown device name", cxt->name)); + DBG(DEV, ul_debugobj(dev, "%s: unknown device name", dev->name)); return -1; } - if (lsblk->inverse) { - if (sysfs_init(&cxt->sysfs, devno, wholedisk ? &wholedisk->sysfs : NULL)) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to initialize sysfs handler", cxt->name)); - return -1; - } - if (parent) - parent->sysfs.parent = &cxt->sysfs; - } else { - if (sysfs_init(&cxt->sysfs, devno, parent ? &parent->sysfs : NULL)) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to initialize sysfs handler", cxt->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; } - cxt->maj = major(devno); - cxt->min = minor(devno); - cxt->size = 0; - - if (sysfs_read_u64(&cxt->sysfs, "size", &cxt->size) == 0) /* in sectors */ - cxt->size <<= 9; /* in bytes */ + dev->maj = major(devno); + dev->min = minor(devno); + dev->size = 0; - if (sysfs_read_int(&cxt->sysfs, - "queue/discard_granularity", &cxt->discard) != 0) - cxt->discard = 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 && cxt->size == 0) { - DBG(CXT, ul_debugobj(cxt, "zero size device -- ignore")); + if (!lsblk->all_devices && dev->size == 0) { + DBG(DEV, ul_debugobj(dev, "zero size device -- ignore")); return -1; } - if (is_dm(cxt->name)) { - cxt->dm_name = sysfs_strdup(&cxt->sysfs, "dm/name"); - if (!cxt->dm_name) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to get dm name", cxt->name)); + 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; } } - cxt->npartitions = sysfs_count_partitions(&cxt->sysfs, cxt->name); - cxt->nholders = sysfs_count_dirents(&cxt->sysfs, "holders"); - cxt->nslaves = sysfs_count_dirents(&cxt->sysfs, "slaves"); + 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(CXT, ul_debugobj(cxt, "%s: npartitions=%d, nholders=%d, nslaves=%d", - cxt->name, cxt->npartitions, cxt->nholders, cxt->nslaves)); + 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_scsi_get_hctl(&cxt->sysfs, NULL, NULL, NULL, NULL)) { - DBG(CXT, ul_debugobj(cxt, "non-scsi device -- ignore")); + 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(CXT, ul_debugobj(cxt, "%s: context successfully initialized", cxt->name)); + DBG(DEV, ul_debugobj(dev, "%s: context successfully initialized", dev->name)); return 0; } -static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent, - int do_partitions, const char *part_name); +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); /* - * List device partitions if any. + * Read devices from whole-disk device into tree */ -static int list_partitions(struct blkdev_cxt *wholedisk_cxt, struct blkdev_cxt *parent_cxt, - const char *part_name) +static int process_partitions(struct lsblk_devtree *tr, struct lsblk_device *disk) { DIR *dir; struct dirent *d; - struct blkdev_cxt part_cxt = { NULL }; - int r = -1; - assert(wholedisk_cxt); + assert(disk); /* * Do not process further if there are no partitions for * this device or the device itself is a partition. */ - if (!wholedisk_cxt->npartitions || wholedisk_cxt->partition) - return -1; + if (!disk->npartitions || device_is_partition(disk)) + return -EINVAL; - DBG(CXT, ul_debugobj(wholedisk_cxt, "probe whole-disk for partitions")); + DBG(DEV, ul_debugobj(disk, "%s: probe whole-disk for partitions", disk->name)); - dir = sysfs_opendir(&wholedisk_cxt->sysfs, NULL); + dir = ul_path_opendir(disk->sysfs, NULL); if (!dir) err(EXIT_FAILURE, _("failed to open device directory in sysfs")); while ((d = xreaddir(dir))) { - /* Process particular partition only? */ - if (part_name && strcmp(part_name, d->d_name)) - continue; + struct lsblk_device *part; - if (!(sysfs_is_partition_dirent(dir, d, wholedisk_cxt->name))) + if (!(sysfs_blkdev_is_partition_dirent(dir, d, disk->name))) continue; - DBG(CXT, ul_debugobj(wholedisk_cxt, " checking %s", d->d_name)); + DBG(DEV, ul_debugobj(disk, " checking %s", d->d_name)); - if (lsblk->inverse) { - /* - * <parent_cxt> - * `-<part_cxt> - * `-<wholedisk_cxt> - * `-... - */ - if (set_cxt(&part_cxt, parent_cxt, wholedisk_cxt, d->d_name)) - goto next; + part = devtree_get_device_or_new(tr, disk, d->d_name); + if (!part) + continue; - if (!parent_cxt && part_cxt.nholders) - goto next; + if (lsblk_device_new_dependence(disk, part) == 0) + process_dependencies(tr, part, 0); - wholedisk_cxt->parent = &part_cxt; - fill_table_line(&part_cxt, parent_cxt ? parent_cxt->scols_line : NULL); - if (!lsblk->nodeps) - process_blkdev(wholedisk_cxt, &part_cxt, 0, NULL); - } else { - /* - * <parent_cxt> - * `-<wholedisk_cxt> - * `-<part_cxt> - * `-... - */ - int ps = set_cxt(&part_cxt, wholedisk_cxt, wholedisk_cxt, d->d_name); - - /* Print whole disk only once */ - if (r) - fill_table_line(wholedisk_cxt, parent_cxt ? parent_cxt->scols_line : NULL); - if (ps == 0 && !lsblk->nodeps) - process_blkdev(&part_cxt, wholedisk_cxt, 0, NULL); - } - next: - reset_blkdev_cxt(&part_cxt); - r = 0; + ul_path_close_dirfd(part->sysfs); } - DBG(CXT, ul_debugobj(wholedisk_cxt, "probe whole-disk for partitions -- done")); + /* 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 r; + return 0; } -static int get_wholedisk_from_partition_dirent(DIR *dir, - struct dirent *d, struct blkdev_cxt *cxt) +static char *get_wholedisk_from_partition_dirent(DIR *dir, struct dirent *d, char *buf, size_t bufsz) { - char path[PATH_MAX]; char *p; int len; - if ((len = readlinkat(dirfd(dir), d->d_name, path, sizeof(path) - 1)) < 0) + if ((len = readlinkat(dirfd(dir), d->d_name, buf, bufsz - 1)) < 0) return 0; - path[len] = '\0'; + buf[len] = '\0'; /* The path ends with ".../<device>/<partition>" */ - p = strrchr(path, '/'); + p = strrchr(buf, '/'); if (!p) - return 0; + return NULL; *p = '\0'; - p = strrchr(path, '/'); + p = strrchr(buf, '/'); if (!p) - return 0; + return NULL; p++; - return set_cxt(cxt, NULL, NULL, p); + return p; } /* - * List device dependencies: partitions, holders (inverse = 0) or slaves (inverse = 1). + * Reads slaves/holders and partitions for specified device into device tree */ -static int list_deps(struct blkdev_cxt *cxt) +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions) { DIR *dir; struct dirent *d; - struct blkdev_cxt dep = { NULL }; const char *depname; - assert(cxt); + assert(dev); if (lsblk->nodeps) return 0; - DBG(CXT, ul_debugobj(cxt, "%s: list dependencies", cxt->name)); + /* 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 ? cxt->nslaves : cxt->nholders)) + if (!(lsblk->inverse ? dev->nslaves : dev->nholders)) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders)")); return 0; + } depname = lsblk->inverse ? "slaves" : "holders"; - dir = sysfs_opendir(&cxt->sysfs, depname); - if (!dir) + dir = ul_path_opendir(dev->sysfs, depname); + if (!dir) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders directory)")); return 0; - - DBG(CXT, ul_debugobj(cxt, "%s: checking for '%s' dependence", cxt->name, depname)); - - while ((d = xreaddir(dir))) { - /* Is the dependency a partition? */ - if (sysfs_is_partition_dirent(dir, d, NULL)) { - if (!get_wholedisk_from_partition_dirent(dir, d, &dep)) { - DBG(CXT, ul_debugobj(cxt, "%s: %s: dependence is partition", - cxt->name, d->d_name)); - process_blkdev(&dep, cxt, 1, d->d_name); - } - } - /* The dependency is a whole device. */ - else if (!set_cxt(&dep, cxt, NULL, d->d_name)) { - DBG(CXT, ul_debugobj(cxt, "%s: %s: dependence is whole-disk", - cxt->name, d->d_name)); - /* For inverse tree we don't want to show partitions - * if the dependence is on whole-disk */ - process_blkdev(&dep, cxt, lsblk->inverse ? 0 : 1, NULL); - } - reset_blkdev_cxt(&dep); } - closedir(dir); + ul_path_close_dirfd(dev->sysfs); - DBG(CXT, ul_debugobj(cxt, "%s: checking for '%s' -- done", cxt->name, depname)); - return 0; -} + DBG(DEV, ul_debugobj(dev, " %s: checking for '%s' dependence", dev->name, depname)); -static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent, - int do_partitions, const char *part_name) -{ - if (do_partitions && cxt->npartitions) - list_partitions(cxt, parent, part_name); /* partitions + whole-disk */ - else - fill_table_line(cxt, parent ? parent->scols_line : NULL); /* whole-disk only */ - - return list_deps(cxt); -} + while ((d = xreaddir(dir))) { + struct lsblk_device *dep = NULL; + struct lsblk_device *disk = NULL; -/* Iterate devices in sysfs */ -static int iterate_block_devices(void) -{ - DIR *dir; - struct dirent *d; - struct blkdev_cxt cxt = { NULL }; + /* Is the dependency a partition? */ + if (sysfs_blkdev_is_partition_dirent(dir, d, NULL)) { - if (!(dir = opendir(_PATH_SYS_BLOCK))) - return -errno; + char buf[PATH_MAX]; + char *diskname; - DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK)); + DBG(DEV, ul_debugobj(dev, " %s: dependence is partition", d->d_name)); - while ((d = xreaddir(dir))) { + 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; + } - DBG(DEV, ul_debug(" %s dentry", d->d_name)); + dep = devtree_get_device_or_new(tr, disk, d->d_name); + if (!dep) + goto next; - if (set_cxt(&cxt, NULL, NULL, d->d_name)) - goto next; + if (lsblk_device_new_dependence(dev, dep) == 0) + process_dependencies(tr, dep, 1); - if (is_maj_excluded(cxt.maj) || !is_maj_included(cxt.maj)) - goto next; + 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)); - /* Skip devices in the middle of dependency tree. */ - if ((lsblk->inverse ? cxt.nholders : cxt.nslaves) > 0) - goto next; + dep = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dep) + goto next; - process_blkdev(&cxt, NULL, 1, NULL); - next: - reset_blkdev_cxt(&cxt); + 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_debug("iterate on " _PATH_SYS_BLOCK " -- done")); + DBG(DEV, ul_debugobj(dev, "%s: checking for '%s' -- done", dev->name, depname)); return 0; } -static char *devno_to_sysfs_name(dev_t devno, char *devname, char *buf, size_t buf_size) -{ - char path[PATH_MAX]; - ssize_t len; - - if (!sysfs_devno_path(devno, path, sizeof(path))) { - warn(_("%s: failed to compose sysfs path"), devname); - return NULL; - } - - len = readlink(path, buf, buf_size - 1); - if (len < 0) { - warn(_("%s: failed to read link"), path); - return NULL; - } - buf[len] = '\0'; - - return xstrdup(strrchr(buf, '/') + 1); -} - -static int process_one_device(char *devname) +/* + * 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 blkdev_cxt parent = { NULL }, cxt = { NULL }; - struct stat st; + struct lsblk_device *dev = NULL; + struct lsblk_device *disk = NULL; char buf[PATH_MAX + 1], *name = NULL, *diskname = NULL; - dev_t disk = 0; int real_part = 0, rc = -EINVAL; - if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { - warnx(_("%s: not a block device"), devname); - goto leave; - } + if (devno == 0) { + struct stat st; - if (!(name = devno_to_sysfs_name(st.st_rdev, devname, buf, PATH_MAX))) { - warn(_("%s: failed to get sysfs name"), devname); + 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 + DBG(DEV, ul_debug("%d:%d: reading alone device", major(devno), minor(devno))); + + /* 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 { - if (blkid_devno_to_wholedisk(st.st_rdev, buf, sizeof(buf), &disk)) { - warn(_("%s: failed to get whole-disk device number"), devname); + 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 = st.st_rdev != disk; + real_part = devno != diskno; } if (!real_part) { /* * Device is not a partition. */ - if (set_cxt(&cxt, NULL, NULL, name)) + DBG(DEV, ul_debug(" non-partition")); + + dev = devtree_get_device_or_new(tr, NULL, name); + if (!dev) goto leave; - process_blkdev(&cxt, NULL, !lsblk->inverse, NULL); + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, !lsblk->inverse); } else { /* - * Partition, read sysfs name of the device. + * Partition, read sysfs name of the disk device */ - if (set_cxt(&parent, NULL, NULL, diskname)) + DBG(DEV, ul_debug(" partition")); + + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) goto leave; - if (set_cxt(&cxt, &parent, &parent, name)) + + dev = devtree_get_device_or_new(tr, disk, name); + if (!dev) goto leave; - if (lsblk->inverse) - process_blkdev(&parent, &cxt, 1, cxt.name); + 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 - process_blkdev(&cxt, &parent, 1, NULL); + 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); - reset_blkdev_cxt(&cxt); + return rc; +} - if (real_part) - reset_blkdev_cxt(&parent); +static int process_one_device(struct lsblk_devtree *tr, char *devname) +{ + return __process_one_device(tr, devname, 0); +} - return rc; +/* + * 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); + 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; @@ -1583,6 +1620,10 @@ static void parse_excludes(const char *str0) } } +/* + * 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; @@ -1628,6 +1669,43 @@ static int cmp_u64_cells(struct libscols_cell *a, 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; @@ -1640,29 +1718,32 @@ static void __attribute__((__noreturn__)) usage(void) 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 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(_(" -D, --discard print discard capabilities\n"), out); - fputs(_(" -z, --zoned print zone model\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(_(" -I, --include <list> show only devices with specified major numbers\n"), out); - fputs(_(" -J, --json use JSON output format\n"), out); fputs(_(" -l, --list use list format output\n"), out); - fputs(_(" -T, --tree use tree 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(_(" -O, --output-all output all columns\n"), out); fputs(_(" -p, --paths print complete device path\n"), out); - fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); fputs(_(" -r, --raw use raw output format\n"), out); fputs(_(" -s, --inverse inverse dependencies\n"), out); - fputs(_(" -S, --scsi output info about SCSI devices\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)); @@ -1685,22 +1766,33 @@ static void check_sysdevblock(void) int main(int argc, char *argv[]) { - struct lsblk _ls = { .sort_id = -1, .flags = LSBLK_TREE }; + struct lsblk _ls = { + .sort_id = -1, + .dedup_id = -1, + .flags = LSBLK_TREE + }; + struct lsblk_devtree *tr = NULL; int c, status = EXIT_FAILURE; char *outarg = NULL; size_t i; int force_tree = 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' }, @@ -1715,6 +1807,7 @@ int main(int argc, char *argv[]) { "pairs", no_argument, NULL, 'P' }, { "scsi", no_argument, NULL, 'S' }, { "sort", required_argument, NULL, 'x' }, + { "sysroot", required_argument, NULL, OPT_SYSROOT }, { "tree", no_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 }, @@ -1743,7 +1836,7 @@ int main(int argc, char *argv[]) lsblk_init_debug(); while((c = getopt_long(argc, argv, - "abdDze:fhJlnmo:OpPiI:rstVSTx:", longopts, NULL)) != -1) { + "abdDzE:e:fhJlnMmo:OpPiI:rstVSTx:", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -1780,6 +1873,9 @@ int main(int argc, char *argv[]) case 'l': lsblk->flags &= ~LSBLK_TREE; /* disable the default */ break; + case 'M': + lsblk->merge = 1; + break; case 'n': lsblk->flags |= LSBLK_NOHEADINGS; break; @@ -1815,6 +1911,8 @@ int main(int argc, char *argv[]) add_uniq_column(COL_FSTYPE); 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': @@ -1851,9 +1949,19 @@ int main(int argc, char *argv[]) case 'T': force_tree = 1; break; + + case OPT_SYSROOT: + lsblk->sysroot = optarg; + break; case 'V': printf(UTIL_LINUX_VERSION); return EXIT_SUCCESS; + 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)); @@ -1902,8 +2010,15 @@ int main(int argc, char *argv[]) lsblk->sort_hidden = 1; } - mnt_init_debug(0); + 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 @@ -1928,6 +2043,8 @@ int main(int argc, char *argv[]) fl &= ~SCOLS_FL_TREE; if (lsblk->sort_hidden && lsblk->sort_id == id) fl |= SCOLS_FL_HIDDEN; + if (lsblk->dedup_hidden && lsblk->dedup_id == id) + fl |= SCOLS_FL_HIDDEN; cl = scols_table_new_column(lsblk->table, ci->name, ci->whint, fl); if (!cl) { @@ -1961,13 +2078,21 @@ int main(int argc, char *argv[]) } } - if (optind == argc) - status = iterate_block_devices() == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - else { + 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(argv[optind++]) != 0) + if (process_one_device(tr, argv[optind++]) != 0) cnt_err++; cnt++; } @@ -1977,6 +2102,13 @@ int main(int argc, char *argv[]) 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) @@ -1990,11 +2122,9 @@ leave: scols_unref_table(lsblk->table); - mnt_unref_table(mtab); - mnt_unref_table(swaps); - mnt_unref_cache(mntcache); -#ifdef HAVE_LIBUDEV - udev_unref(udev); -#endif + lsblk_mnt_deinit(); + lsblk_properties_deinit(); + lsblk_unref_devtree(tr); + return status; } |