util-linux/misc-utils/lsblk.c
Milan Broz e19e522df0 lsblk: Add SERIAL column to the SCSI output mode.
If there are several identical disks, disk serial number can help
to distinguish exact drive.

This could be helpful in debugging RAID failures and similar problems.

Signed-off-by: Milan Broz <gmazyland@gmail.com>
2020-07-14 10:37:56 +02:00

2201 lines
58 KiB
C

/*
* 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 (!dev->size)
break;
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)
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;
}