aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOsama Abdelkader <osama.abdelkader@gmail.com>2025-12-04 22:08:46 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2026-01-30 12:23:42 +0100
commit8a347fd31aa2095be8eb683ad4f7627e525229f1 (patch)
tree11e61da8f0db3f9675ef118556fed4df56e15bfd
parent3fb6b31c716669e12f75a2accd31bb7685b1a1cb (diff)
downloadbusybox-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.c325
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
65struct blockdev_info {
66 char *name;
67 unsigned long long size;
68 const char *type;
69 const char *majmin;
70 const char *mountpoint;
71};
72
73struct 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
82static 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
95static 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
118static 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
130static 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
140bb_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 */
155static 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
163bb_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 */
187static void process_SYS_BLOCK_entry(const char *path, const char *devname);
188
189/* Note: consumes malloc'ed majmin */
190static 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}
224static 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
236static 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
243int lsblk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
244int 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}