← Commit list

8a347fd31

Osama Abdelkader
Osama Abdelkader
2026-01-30
SUCCESS Duration: 145s

Subject

lsblk: new applet

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
gcc -Wp,-MD,applets/.usage.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer      -Iinclude -Iinclude -o applets/usage applets/usage.c

Build Log

(log yok)

Commit Diff GitHub →

util-linux/lsblk.c+325 -0
@@ -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 <column> de-duplicate output by <column>
+ -I, --include <list> 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 <expr> print only lines matching the expression
+ --highlight <expr> colorize lines matching the expression
+ --ct-filter <expr> restrict the next counter
+ --ct <name>[:<param>[:<func>]] 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[=<column>] 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 <list> 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 <list> 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 <num> specifies output width as number of characters
+ -x, --sort <column> sort output by <column>
+ -y, --shell use column names that can be used as shell variables
+ -z, --zoned print zone related information
+ --sysroot <dir> use specified directory as system root
+ --properties-by <list>
+ methods used to gather data (default: file,udev,blkid)
+ -H, --list-columns list the available columns
+*/
+#include "libbb.h"
+#include <mntent.h>
+
+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();
+}