From 8a347fd31aa2095be8eb683ad4f7627e525229f1 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 4 Dec 2025 22:08:46 +0100 Subject: lsblk: new applet Add a simple lsblk utility that lists information about block devices. Reads from /sys/block to enumerate devices and displays their size, type, and mount point. Features: - Lists all block devices or specific devices - Shows device size in human-readable format (B, K, M, G, T, P) - Shows device type (disk, loop, partition) - Shows mount point if device is mounted - Sorts devices alphabetically - Minimal implementation (~2.5 kb) Mostly rewritten by vda.linux function old new delta lsblk_main - 511 +511 process_SYS_BLOCK_entry - 460 +460 .rodata 106856 106960 +104 read_ull - 81 +81 packed_usage 35924 35952 +28 compare_devices - 25 +25 applet_names 2856 2862 +6 applet_main 1644 1648 +4 ------------------------------------------------------------------------------ (add/remove: 5/0 grow/shrink: 4/0 up/down: 1219/0) Total: 1219 bytes Signed-off-by: Osama Abdelkader Signed-off-by: Denys Vlasenko --- util-linux/lsblk.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 util-linux/lsblk.c (limited to 'util-linux') diff --git a/util-linux/lsblk.c b/util-linux/lsblk.c new file mode 100644 index 000000000..3063d8ae9 --- /dev/null +++ b/util-linux/lsblk.c @@ -0,0 +1,325 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini lsblk implementation for busybox + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +//config:config LSBLK +//config: bool "lsblk (2.5 kb)" +//config: default y +//config: help +//config: List information about all available or specified block devices. + +//applet:IF_LSBLK(APPLET(lsblk, BB_DIR_USR_BIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_LSBLK) += lsblk.o + +//usage:#define lsblk_trivial_usage +//usage: "[BLOCKDEV]..." +//usage:#define lsblk_full_usage "\n\n" +//usage: "List block devices" + +/* from util-linux 2.41.1 + -A, --noempty don't print empty devices + -D, --discard print discard capabilities + -E, --dedup de-duplicate output by + -I, --include show only devices with specified major numbers + -J, --json use JSON output format + -M, --merge group parents of sub-trees (RAIDs, Multi-path) + -O, --output-all output all columns + -P, --pairs use key="value" output format + -Q, --filter print only lines matching the expression + --highlight colorize lines matching the expression + --ct-filter restrict the next counter + --ct [:[:]] define a custom counter + -S, --scsi output info about SCSI devices + -N, --nvme output info about NVMe devices + -v, --virtio output info about virtio devices + -T, --tree[=] use tree format output + -a, --all print all devices + -b, --bytes print SIZE in bytes instead of a human-readable format + -d, --nodeps don't print slaves or holders + -e, --exclude exclude devices by major number (default: RAM disks) + -f, --fs output info about filesystems + -i, --ascii use ascii characters only + -l, --list use list format output + -m, --perms output info about permissions + -n, --noheadings don't print headings + -o, --output output columns (see --list-columns) + -p, --paths print complete device path + -r, --raw use raw output format + -s, --inverse inverse dependencies + -t, --topology output info about topology + -w, --width specifies output width as number of characters + -x, --sort sort output by + -y, --shell use column names that can be used as shell variables + -z, --zoned print zone related information + --sysroot use specified directory as system root + --properties-by + methods used to gather data (default: file,udev,blkid) + -H, --list-columns list the available columns +*/ +#include "libbb.h" +#include + +struct blockdev_info { + char *name; + unsigned long long size; + const char *type; + const char *majmin; + const char *mountpoint; +}; + +struct globals { + unsigned count; + struct blockdev_info *list; +}; +#define G (*ptr_to_globals) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ +} while (0) + +static struct blockdev_info *get_or_create_info(const char *devname) +{ + unsigned i; + + for (i = 0; i < G.count; i++) { + if (strcmp(G.list[i].name, devname) == 0) + return &G.list[i]; + } + G.list = xrealloc_vector(G.list, 4, G.count); + G.list[G.count].name = xstrdup(devname); + return &G.list[G.count++]; +} + +static char *get_mountpoint(const char *devname) +{ + char devpath[256]; + struct mntent *mnt; + FILE *mtab; + char *mountpoint = NULL; + + snprintf(devpath, sizeof(devpath), "/dev/%s", devname); +//TODO: use /proc/self/mountinfo instead, it has MAJ:MIN column which is unambiguous + mtab = setmntent(bb_path_mtab_file, "r"); + if (mtab) { + while ((mnt = getmntent(mtab)) != NULL) { + if (strcmp(mnt->mnt_fsname, devpath) == 0) { + mountpoint = xstrdup(mnt->mnt_dir); + break; + } + } + endmntent(mtab); + } + + return mountpoint; +} + +static char *get_majmin_from_stat(const char *filename) +{ + struct stat st; + if (stat(filename, &st) == 0 && S_ISBLK(st.st_mode)) { + return xasprintf("%u:%u", + (unsigned)major(st.st_rdev), + (unsigned)minor(st.st_rdev) + ); + } + return NULL; +} + +static unsigned long long read_ull(const char *path, const char *name) +{ + char *filename; + ssize_t len; + unsigned long long size = ~0ULL; /* error return is all-ones */ + char buf[sizeof(size)*3]; + + filename = concat_path_file(path, name); + len = open_read_close(filename, buf, sizeof(buf) - 1); +#if 0 +bb_error_msg("open_read_close('%s'):'%.*s'", + filename, + len < 0 ? 5 : (int)len, + len < 0 ? "ERROR" : buf +); +#endif + free(filename); + if (len > 0 && len < sizeof(buf) - 1) { + buf[len] = '\0'; + size = bb_strtoull(buf, NULL, 10); + } + return size; +} + +/* For reading one-line /sys files. Truncates at \n. NULL on error */ +static char *read_str(const char *path, const char *name) +{ + char *filename; + char *res; + + filename = concat_path_file(path, name); + res = xmalloc_open_read_close(filename, NULL); +#if 0 +bb_error_msg("open_read_close('%s'):'%s'", filename, res); +#endif + free(filename); + + if (res) + strchrnul(res, '\n')[0] = '\0'; + return res; +} + +/* To see what util-linux does: + * strace -eopen,openat,getdents,getdents64,read,fcntl -s99 -v lsblk 2>&1 | less + * What I observed: + * open("/sys/block")+getdents() + * For each found entry DEV: + * open("/sys/block/DEV/hidden")+read, if 1, skip (probably) + * MAJMIN=open("/sys/block/DEV/dev")+read + * open("/sys/dev/block/MAJMIN/size")+read + * open("/sys/dev/block/MAJMIN")+getdents() + * For each found entry PART: + * open("/sys/block/PART/hidden") - if found then what? + * else + * MAJMIN1=open("/sys/block/DEV/PART/dev")+read + * recurse into handling MAJMIN1 for this partition + */ +static void process_SYS_BLOCK_entry(const char *path, const char *devname); + +/* Note: consumes malloc'ed majmin */ +static void process_SYS_DEV_BLOCK_entry(const char *path, char *majmin, const char *devname) +{ + DIR *dir; + struct dirent *entry; + struct blockdev_info *info; + + info = get_or_create_info(devname); + if (info->type) /* we already saw this one! */ + return; + + info->size = read_ull(path, "size"); +//see util-linux's lsblk.c::static char *get_type() + info->type = ( + (long long)read_ull(path, "partition") >= 0 ? "part" /* PATH/partition exists and has a number */ + : is_prefixed_with(devname, "loop") ? "loop" + : "disk" + ); +//TODO: ^^^^ read from /sys/XYZ/uevent, DEVTYPE=xyz line? Not what util-linux does, though... + info->majmin = majmin; + //info->rm = ...; + //info->ro = ...; + info->mountpoint = get_mountpoint(devname); + + /* Scan for partititons */ + dir = xopendir(path); + while ((entry = readdir(dir)) != NULL) { + if (is_prefixed_with(entry->d_name, devname)) { + char *part = xasprintf("/sys/block/%s/%s", devname, entry->d_name); + process_SYS_BLOCK_entry(part, entry->d_name); + free(part); + } + } + closedir(dir); +} +static void process_SYS_BLOCK_entry(const char *path, const char *devname) +{ + char *majmin = read_str(path, "dev"); + if (majmin /*&& majmin[0]*/) { + char *sys_dev_block_MAJMIN = concat_path_file("/sys/dev/block", majmin); + process_SYS_DEV_BLOCK_entry(sys_dev_block_MAJMIN, majmin, devname); + /* ^^^ consumes malloc'ed majmin */ + free(sys_dev_block_MAJMIN); + } + /* WRONG: free(majmin); */ +} + +static int compare_devices(const void *a, const void *b) +{ + const struct blockdev_info *da = (const struct blockdev_info *)a; + const struct blockdev_info *db = (const struct blockdev_info *)b; + return strcmp(da->name, db->name); +} + +int lsblk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int lsblk_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned i; + + INIT_G(); + + /* support/ignore -a ("all") */ + getopt32(argv, "a"); + argv += optind; + + /* If specific devices are requested, process them */ + if (*argv) { + while (*argv) { + char *devname = *argv++; + char *majmin; + char *sys_dev_block_MAJMIN; + char *target; + char *name; + + /* Try: + * cp -a /dev/DISK /tmp/bogusname; lsblk /tmp/bogusname + * ^^^^ should still show "DISK" as the name of blockdev, and show its partitions if any + */ + majmin = get_majmin_from_stat(devname); + if (!majmin) + continue; + + sys_dev_block_MAJMIN = concat_path_file("/sys/dev/block", majmin); + /* util-linux 2.41.1 gets the "real name" from the symlink's last component */ + target = xmalloc_readlink(sys_dev_block_MAJMIN); + if (target) { + name = strrchr(target, '/'); + if (name && name[1]) { + char *sys_block_NAME = concat_path_file("/sys/block", ++name); + process_SYS_BLOCK_entry(sys_block_NAME, name); + free(sys_block_NAME); + } + free(target); + } + free(majmin); + free(sys_dev_block_MAJMIN); + } + } else { + DIR *dir; + struct dirent *entry; + + /* Read all devices from /sys/block */ + dir = xopendir("/sys/block"); + while ((entry = readdir(dir)) != NULL) { + char *sys_block_NAME; + if (DOT_OR_DOTDOT(entry->d_name)) + continue; + sys_block_NAME = concat_path_file("/sys/block", entry->d_name); + process_SYS_BLOCK_entry(sys_block_NAME, entry->d_name); + free(sys_block_NAME); + } + closedir(dir); + } + + /* Sort devices by name */ + qsort(G.list, G.count, sizeof(G.list[0]), compare_devices); + + /* Print header */ + printf("%-15s MAJ:MIN SIZE TYPE MOUNTPOINT\n", "NAME"); + //util-linux 2.41.1 default set of fields: + //"NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS" + + /* Print devices */ + for (i = 0; i < G.count; i++) { + char buf6[6]; + smart_ulltoa5(G.list[i].size * 512, buf6, " KMGTPEZY"); + printf("%-15s %-7s %.5s %4s %s\n", + G.list[i].name, + G.list[i].majmin, + buf6, + G.list[i].type, + (G.list[i].mountpoint ? G.list[i].mountpoint : "") + ); + } + + fflush_stdout_and_exit_SUCCESS(); +} -- cgit v1.2.3-55-g6feb