diff options
| author | Osama Abdelkader <osama.abdelkader@gmail.com> | 2025-12-04 22:08:46 +0100 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2026-01-30 12:23:42 +0100 |
| commit | 8a347fd31aa2095be8eb683ad4f7627e525229f1 (patch) | |
| tree | 11e61da8f0db3f9675ef118556fed4df56e15bfd | |
| parent | 3fb6b31c716669e12f75a2accd31bb7685b1a1cb (diff) | |
| download | busybox-w32-8a347fd31aa2095be8eb683ad4f7627e525229f1.tar.gz busybox-w32-8a347fd31aa2095be8eb683ad4f7627e525229f1.tar.bz2 busybox-w32-8a347fd31aa2095be8eb683ad4f7627e525229f1.zip | |
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 <osama.abdelkader@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
| -rw-r--r-- | util-linux/lsblk.c | 325 |
1 files changed, 325 insertions, 0 deletions
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 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 2 | /* | ||
| 3 | * Mini lsblk implementation for busybox | ||
| 4 | * | ||
| 5 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
| 6 | */ | ||
| 7 | //config:config LSBLK | ||
| 8 | //config: bool "lsblk (2.5 kb)" | ||
| 9 | //config: default y | ||
| 10 | //config: help | ||
| 11 | //config: List information about all available or specified block devices. | ||
| 12 | |||
| 13 | //applet:IF_LSBLK(APPLET(lsblk, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
| 14 | |||
| 15 | //kbuild:lib-$(CONFIG_LSBLK) += lsblk.o | ||
| 16 | |||
| 17 | //usage:#define lsblk_trivial_usage | ||
| 18 | //usage: "[BLOCKDEV]..." | ||
| 19 | //usage:#define lsblk_full_usage "\n\n" | ||
| 20 | //usage: "List block devices" | ||
| 21 | |||
| 22 | /* from util-linux 2.41.1 | ||
| 23 | -A, --noempty don't print empty devices | ||
| 24 | -D, --discard print discard capabilities | ||
| 25 | -E, --dedup <column> de-duplicate output by <column> | ||
| 26 | -I, --include <list> show only devices with specified major numbers | ||
| 27 | -J, --json use JSON output format | ||
| 28 | -M, --merge group parents of sub-trees (RAIDs, Multi-path) | ||
| 29 | -O, --output-all output all columns | ||
| 30 | -P, --pairs use key="value" output format | ||
| 31 | -Q, --filter <expr> print only lines matching the expression | ||
| 32 | --highlight <expr> colorize lines matching the expression | ||
| 33 | --ct-filter <expr> restrict the next counter | ||
| 34 | --ct <name>[:<param>[:<func>]] define a custom counter | ||
| 35 | -S, --scsi output info about SCSI devices | ||
| 36 | -N, --nvme output info about NVMe devices | ||
| 37 | -v, --virtio output info about virtio devices | ||
| 38 | -T, --tree[=<column>] use tree format output | ||
| 39 | -a, --all print all devices | ||
| 40 | -b, --bytes print SIZE in bytes instead of a human-readable format | ||
| 41 | -d, --nodeps don't print slaves or holders | ||
| 42 | -e, --exclude <list> exclude devices by major number (default: RAM disks) | ||
| 43 | -f, --fs output info about filesystems | ||
| 44 | -i, --ascii use ascii characters only | ||
| 45 | -l, --list use list format output | ||
| 46 | -m, --perms output info about permissions | ||
| 47 | -n, --noheadings don't print headings | ||
| 48 | -o, --output <list> output columns (see --list-columns) | ||
| 49 | -p, --paths print complete device path | ||
| 50 | -r, --raw use raw output format | ||
| 51 | -s, --inverse inverse dependencies | ||
| 52 | -t, --topology output info about topology | ||
| 53 | -w, --width <num> specifies output width as number of characters | ||
| 54 | -x, --sort <column> sort output by <column> | ||
| 55 | -y, --shell use column names that can be used as shell variables | ||
| 56 | -z, --zoned print zone related information | ||
| 57 | --sysroot <dir> use specified directory as system root | ||
| 58 | --properties-by <list> | ||
| 59 | methods used to gather data (default: file,udev,blkid) | ||
| 60 | -H, --list-columns list the available columns | ||
| 61 | */ | ||
| 62 | #include "libbb.h" | ||
| 63 | #include <mntent.h> | ||
| 64 | |||
| 65 | struct blockdev_info { | ||
| 66 | char *name; | ||
| 67 | unsigned long long size; | ||
| 68 | const char *type; | ||
| 69 | const char *majmin; | ||
| 70 | const char *mountpoint; | ||
| 71 | }; | ||
| 72 | |||
| 73 | struct globals { | ||
| 74 | unsigned count; | ||
| 75 | struct blockdev_info *list; | ||
| 76 | }; | ||
| 77 | #define G (*ptr_to_globals) | ||
| 78 | #define INIT_G() do { \ | ||
| 79 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | ||
| 80 | } while (0) | ||
| 81 | |||
| 82 | static struct blockdev_info *get_or_create_info(const char *devname) | ||
| 83 | { | ||
| 84 | unsigned i; | ||
| 85 | |||
| 86 | for (i = 0; i < G.count; i++) { | ||
| 87 | if (strcmp(G.list[i].name, devname) == 0) | ||
| 88 | return &G.list[i]; | ||
| 89 | } | ||
| 90 | G.list = xrealloc_vector(G.list, 4, G.count); | ||
| 91 | G.list[G.count].name = xstrdup(devname); | ||
| 92 | return &G.list[G.count++]; | ||
| 93 | } | ||
| 94 | |||
| 95 | static char *get_mountpoint(const char *devname) | ||
| 96 | { | ||
| 97 | char devpath[256]; | ||
| 98 | struct mntent *mnt; | ||
| 99 | FILE *mtab; | ||
| 100 | char *mountpoint = NULL; | ||
| 101 | |||
| 102 | snprintf(devpath, sizeof(devpath), "/dev/%s", devname); | ||
| 103 | //TODO: use /proc/self/mountinfo instead, it has MAJ:MIN column which is unambiguous | ||
| 104 | mtab = setmntent(bb_path_mtab_file, "r"); | ||
| 105 | if (mtab) { | ||
| 106 | while ((mnt = getmntent(mtab)) != NULL) { | ||
| 107 | if (strcmp(mnt->mnt_fsname, devpath) == 0) { | ||
| 108 | mountpoint = xstrdup(mnt->mnt_dir); | ||
| 109 | break; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | endmntent(mtab); | ||
| 113 | } | ||
| 114 | |||
| 115 | return mountpoint; | ||
| 116 | } | ||
| 117 | |||
| 118 | static char *get_majmin_from_stat(const char *filename) | ||
| 119 | { | ||
| 120 | struct stat st; | ||
| 121 | if (stat(filename, &st) == 0 && S_ISBLK(st.st_mode)) { | ||
| 122 | return xasprintf("%u:%u", | ||
| 123 | (unsigned)major(st.st_rdev), | ||
| 124 | (unsigned)minor(st.st_rdev) | ||
| 125 | ); | ||
| 126 | } | ||
| 127 | return NULL; | ||
| 128 | } | ||
| 129 | |||
| 130 | static unsigned long long read_ull(const char *path, const char *name) | ||
| 131 | { | ||
| 132 | char *filename; | ||
| 133 | ssize_t len; | ||
| 134 | unsigned long long size = ~0ULL; /* error return is all-ones */ | ||
| 135 | char buf[sizeof(size)*3]; | ||
| 136 | |||
| 137 | filename = concat_path_file(path, name); | ||
| 138 | len = open_read_close(filename, buf, sizeof(buf) - 1); | ||
| 139 | #if 0 | ||
| 140 | bb_error_msg("open_read_close('%s'):'%.*s'", | ||
| 141 | filename, | ||
| 142 | len < 0 ? 5 : (int)len, | ||
| 143 | len < 0 ? "ERROR" : buf | ||
| 144 | ); | ||
| 145 | #endif | ||
| 146 | free(filename); | ||
| 147 | if (len > 0 && len < sizeof(buf) - 1) { | ||
| 148 | buf[len] = '\0'; | ||
| 149 | size = bb_strtoull(buf, NULL, 10); | ||
| 150 | } | ||
| 151 | return size; | ||
| 152 | } | ||
| 153 | |||
| 154 | /* For reading one-line /sys files. Truncates at \n. NULL on error */ | ||
| 155 | static char *read_str(const char *path, const char *name) | ||
| 156 | { | ||
| 157 | char *filename; | ||
| 158 | char *res; | ||
| 159 | |||
| 160 | filename = concat_path_file(path, name); | ||
| 161 | res = xmalloc_open_read_close(filename, NULL); | ||
| 162 | #if 0 | ||
| 163 | bb_error_msg("open_read_close('%s'):'%s'", filename, res); | ||
| 164 | #endif | ||
| 165 | free(filename); | ||
| 166 | |||
| 167 | if (res) | ||
| 168 | strchrnul(res, '\n')[0] = '\0'; | ||
| 169 | return res; | ||
| 170 | } | ||
| 171 | |||
| 172 | /* To see what util-linux does: | ||
| 173 | * strace -eopen,openat,getdents,getdents64,read,fcntl -s99 -v lsblk 2>&1 | less | ||
| 174 | * What I observed: | ||
| 175 | * open("/sys/block")+getdents() | ||
| 176 | * For each found entry DEV: | ||
| 177 | * open("/sys/block/DEV/hidden")+read, if 1, skip (probably) | ||
| 178 | * MAJMIN=open("/sys/block/DEV/dev")+read | ||
| 179 | * open("/sys/dev/block/MAJMIN/size")+read | ||
| 180 | * open("/sys/dev/block/MAJMIN")+getdents() | ||
| 181 | * For each found entry PART: | ||
| 182 | * open("/sys/block/PART/hidden") - if found then what? | ||
| 183 | * else | ||
| 184 | * MAJMIN1=open("/sys/block/DEV/PART/dev")+read | ||
| 185 | * recurse into handling MAJMIN1 for this partition | ||
| 186 | */ | ||
| 187 | static void process_SYS_BLOCK_entry(const char *path, const char *devname); | ||
| 188 | |||
| 189 | /* Note: consumes malloc'ed majmin */ | ||
| 190 | static void process_SYS_DEV_BLOCK_entry(const char *path, char *majmin, const char *devname) | ||
| 191 | { | ||
| 192 | DIR *dir; | ||
| 193 | struct dirent *entry; | ||
| 194 | struct blockdev_info *info; | ||
| 195 | |||
| 196 | info = get_or_create_info(devname); | ||
| 197 | if (info->type) /* we already saw this one! */ | ||
| 198 | return; | ||
| 199 | |||
| 200 | info->size = read_ull(path, "size"); | ||
| 201 | //see util-linux's lsblk.c::static char *get_type() | ||
| 202 | info->type = ( | ||
| 203 | (long long)read_ull(path, "partition") >= 0 ? "part" /* PATH/partition exists and has a number */ | ||
| 204 | : is_prefixed_with(devname, "loop") ? "loop" | ||
| 205 | : "disk" | ||
| 206 | ); | ||
| 207 | //TODO: ^^^^ read from /sys/XYZ/uevent, DEVTYPE=xyz line? Not what util-linux does, though... | ||
| 208 | info->majmin = majmin; | ||
| 209 | //info->rm = ...; | ||
| 210 | //info->ro = ...; | ||
| 211 | info->mountpoint = get_mountpoint(devname); | ||
| 212 | |||
| 213 | /* Scan for partititons */ | ||
| 214 | dir = xopendir(path); | ||
| 215 | while ((entry = readdir(dir)) != NULL) { | ||
| 216 | if (is_prefixed_with(entry->d_name, devname)) { | ||
| 217 | char *part = xasprintf("/sys/block/%s/%s", devname, entry->d_name); | ||
| 218 | process_SYS_BLOCK_entry(part, entry->d_name); | ||
| 219 | free(part); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | closedir(dir); | ||
| 223 | } | ||
| 224 | static void process_SYS_BLOCK_entry(const char *path, const char *devname) | ||
| 225 | { | ||
| 226 | char *majmin = read_str(path, "dev"); | ||
| 227 | if (majmin /*&& majmin[0]*/) { | ||
| 228 | char *sys_dev_block_MAJMIN = concat_path_file("/sys/dev/block", majmin); | ||
| 229 | process_SYS_DEV_BLOCK_entry(sys_dev_block_MAJMIN, majmin, devname); | ||
| 230 | /* ^^^ consumes malloc'ed majmin */ | ||
| 231 | free(sys_dev_block_MAJMIN); | ||
| 232 | } | ||
| 233 | /* WRONG: free(majmin); */ | ||
| 234 | } | ||
| 235 | |||
| 236 | static int compare_devices(const void *a, const void *b) | ||
| 237 | { | ||
| 238 | const struct blockdev_info *da = (const struct blockdev_info *)a; | ||
| 239 | const struct blockdev_info *db = (const struct blockdev_info *)b; | ||
| 240 | return strcmp(da->name, db->name); | ||
| 241 | } | ||
| 242 | |||
| 243 | int lsblk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
| 244 | int lsblk_main(int argc UNUSED_PARAM, char **argv) | ||
| 245 | { | ||
| 246 | unsigned i; | ||
| 247 | |||
| 248 | INIT_G(); | ||
| 249 | |||
| 250 | /* support/ignore -a ("all") */ | ||
| 251 | getopt32(argv, "a"); | ||
| 252 | argv += optind; | ||
| 253 | |||
| 254 | /* If specific devices are requested, process them */ | ||
| 255 | if (*argv) { | ||
| 256 | while (*argv) { | ||
| 257 | char *devname = *argv++; | ||
| 258 | char *majmin; | ||
| 259 | char *sys_dev_block_MAJMIN; | ||
| 260 | char *target; | ||
| 261 | char *name; | ||
| 262 | |||
| 263 | /* Try: | ||
| 264 | * cp -a /dev/DISK /tmp/bogusname; lsblk /tmp/bogusname | ||
| 265 | * ^^^^ should still show "DISK" as the name of blockdev, and show its partitions if any | ||
| 266 | */ | ||
| 267 | majmin = get_majmin_from_stat(devname); | ||
| 268 | if (!majmin) | ||
| 269 | continue; | ||
| 270 | |||
| 271 | sys_dev_block_MAJMIN = concat_path_file("/sys/dev/block", majmin); | ||
| 272 | /* util-linux 2.41.1 gets the "real name" from the symlink's last component */ | ||
| 273 | target = xmalloc_readlink(sys_dev_block_MAJMIN); | ||
| 274 | if (target) { | ||
| 275 | name = strrchr(target, '/'); | ||
| 276 | if (name && name[1]) { | ||
| 277 | char *sys_block_NAME = concat_path_file("/sys/block", ++name); | ||
| 278 | process_SYS_BLOCK_entry(sys_block_NAME, name); | ||
| 279 | free(sys_block_NAME); | ||
| 280 | } | ||
| 281 | free(target); | ||
| 282 | } | ||
| 283 | free(majmin); | ||
| 284 | free(sys_dev_block_MAJMIN); | ||
| 285 | } | ||
| 286 | } else { | ||
| 287 | DIR *dir; | ||
| 288 | struct dirent *entry; | ||
| 289 | |||
| 290 | /* Read all devices from /sys/block */ | ||
| 291 | dir = xopendir("/sys/block"); | ||
| 292 | while ((entry = readdir(dir)) != NULL) { | ||
| 293 | char *sys_block_NAME; | ||
| 294 | if (DOT_OR_DOTDOT(entry->d_name)) | ||
| 295 | continue; | ||
| 296 | sys_block_NAME = concat_path_file("/sys/block", entry->d_name); | ||
| 297 | process_SYS_BLOCK_entry(sys_block_NAME, entry->d_name); | ||
| 298 | free(sys_block_NAME); | ||
| 299 | } | ||
| 300 | closedir(dir); | ||
| 301 | } | ||
| 302 | |||
| 303 | /* Sort devices by name */ | ||
| 304 | qsort(G.list, G.count, sizeof(G.list[0]), compare_devices); | ||
| 305 | |||
| 306 | /* Print header */ | ||
| 307 | printf("%-15s MAJ:MIN SIZE TYPE MOUNTPOINT\n", "NAME"); | ||
| 308 | //util-linux 2.41.1 default set of fields: | ||
| 309 | //"NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS" | ||
| 310 | |||
| 311 | /* Print devices */ | ||
| 312 | for (i = 0; i < G.count; i++) { | ||
| 313 | char buf6[6]; | ||
| 314 | smart_ulltoa5(G.list[i].size * 512, buf6, " KMGTPEZY"); | ||
| 315 | printf("%-15s %-7s %.5s %4s %s\n", | ||
| 316 | G.list[i].name, | ||
| 317 | G.list[i].majmin, | ||
| 318 | buf6, | ||
| 319 | G.list[i].type, | ||
| 320 | (G.list[i].mountpoint ? G.list[i].mountpoint : "") | ||
| 321 | ); | ||
| 322 | } | ||
| 323 | |||
| 324 | fflush_stdout_and_exit_SUCCESS(); | ||
| 325 | } | ||
