Re: [PATCH] util-linux: add minimal lsblk implementation

Denys Vlasenko via busybox <[email protected]>
Newsgroups gmane.linux.busybox
Message-ID <CAK1hOcPN7rvfbyRR+NRbf71QZA+E5UKZ_anOJq-ZoK-Te2wELg@mail.gmail.com>
On Thu, Jan 29, 2026 at 10:12 PM Osama Abdelkader via busybox
<[email protected]> wrote:
>
> On Thu, Dec 04, 2025 at 10:08:46PM +0100, Osama Abdelkader wrote:
> > 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, rom, etc.)
> > - Shows mount point if device is mounted
> > - Sorts devices alphabetically
> > - Minimal implementation (~2.5 kb)
> > - NOFORK applet for efficiency
> >
> > Signed-off-by: Osama Abdelkader <[email protected]>
> > ---
> >  util-linux/lsblk.c | 219 +++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 219 insertions(+)
> >  create mode 100644 util-linux/lsblk.c
> >
> > diff --git a/util-linux/lsblk.c b/util-linux/lsblk.c
> > new file mode 100644
> > index 000000000..47f217fb8
> > --- /dev/null
> > +++ b/util-linux/lsblk.c
> > @@ -0,0 +1,219 @@
> > +/* 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_NOFORK(lsblk, lsblk, BB_DIR_USR_BIN, BB_SUID_DROP, lsblk))
> > +
> > +//kbuild:lib-$(CONFIG_LSBLK) += lsblk.o
> > +
> > +//usage:#define lsblk_trivial_usage
> > +//usage:       "[BLOCKDEVICE...]"
> > +//usage:#define lsblk_full_usage "\n\n"
> > +//usage:       "List information about all available or specified block devices"
> > +
> > +#include "libbb.h"
> > +#include <mntent.h>
> > +
> > +/* This is a NOFORK applet. Be very careful! */
> > +
> > +struct blockdev_info {
> > +     char *name;
> > +     unsigned long long size;
> > +     char *type;
> > +     char *mountpoint;
> > +};
> > +
> > +static unsigned long long read_size_from_sysfs(const char *devname)
> > +{
> > +     char path[256];
> > +     char buf[64];
> > +     ssize_t len;
> > +     unsigned long long size = 0;
> > +
> > +     snprintf(path, sizeof(path), "/sys/block/%s/size", devname);
> > +     len = open_read_close(path, buf, sizeof(buf) - 1);
> > +     if (len > 0) {
> > +             buf[len] = '\0';
> > +             /* Remove trailing newline if present */
> > +             if (buf[len - 1] == '\n')
> > +                     buf[len - 1] = '\0';
> > +             size = bb_strtoull(buf, NULL, 10);
> > +             /* size is in 512-byte sectors, convert to bytes */
> > +             size *= 512;
> > +     }
> > +     return size;
> > +}
> > +
> > +static char *get_device_type(const char *devname)
> > +{
> > +     char path[256];
> > +     char *buf, *type = NULL;
> > +     size_t len;
> > +
> > +     /* Try to read from uevent */
> > +     snprintf(path, sizeof(path), "/sys/block/%s/uevent", devname);
> > +     buf = xmalloc_open_read_close(path, &len);
> > +     if (buf) {
> > +             char *p = buf;
> > +             while (*p) {
> > +                     if (strncmp(p, "DEVTYPE=", 8) == 0) {
Why not get rid of while() loop and just do one search?
is_prefixed_with(buf, "DEVTYPE=") || strstr(buf, "\nDEVTYPE=")

> > +                             char *end;

> > +                             type = xstrdup(p + 8);
> > +                             /* Remove newline */
> > +                             end = strchr(type, '\n');
> > +                             if (end) *end = '\0';
> > +                             break;

Instead, you can:
p += 8;
end = strchrnul(type, '\n');
*end++ = '\0';
memmove(buf, p, end - p);
return xrealloc(buf, end - p);

If you do this, "if (!type)' later is always true and doesn't need to
be checked.

> > +                     }
> > +                     p = strchr(p, '\n');
> > +                     if (!p) break;
> > +                     p++;
> > +             }
> > +             free(buf);
> > +     }
> > +
> > +     /* Fallback: guess from device name */
> > +     if (!type) {
> > +             if (strncmp(devname, "loop", 4) == 0)
> > +                     type = xstrdup("loop");
> > +             else if (strncmp(devname, "ram", 3) == 0)
> > +                     type = xstrdup("ram");
> > +             else if (strncmp(devname, "nvme", 4) == 0 || strncmp(devname, "sd", 2) == 0 ||
> > +                      strncmp(devname, "hd", 2) == 0 || strncmp(devname, "vd", 2) == 0)
> > +                     type = xstrdup("disk");
> > +             else
> > +                     type = xstrdup("disk");

We don't need to always malloc the value.
Just return constant (not-allocated) strings.


> > +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);
> > +     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;
> > +}

Check whether find_mount_point() can be used for this.

> > +static void print_size(unsigned long long size)
> > +{
> > +     const char *units[] = {"B", "K", "M", "G", "T", "P"};
> > +     int unit_idx = 0;
> > +     double dsize = size;
> > +
> > +     while (dsize >= 1024.0 && unit_idx < 5) {
> > +             dsize /= 1024.0;
> > +             unit_idx++;
> > +     }
> > +
> > +     if (unit_idx == 0)
> > +             printf("%llu", size);
> > +     else
> > +             printf("%.1f%c", dsize, units[unit_idx][0]);
> > +}

The above can be done with smart_ulltoa5()

> > +int lsblk_main(int argc UNUSED_PARAM, char **argv)
> > +{
> > +     DIR *dir;
> > +     struct dirent *entry;
> > +     struct blockdev_info *devices = NULL;
> > +     int count = 0;
> > +     int i;
> > +     unsigned opt;
> > +
> > +     opt = getopt32(argv, "");

Variable 'opt' is unused.

> > +     argv += optind;
> > +
> > +     /* If specific devices are requested, process them */
> > +     if (*argv) {
> > +             while (*argv) {
> > +                     char *devname = *argv;
> > +                     /* Remove /dev/ prefix if present */
> > +                     if (strncmp(devname, "/dev/", 5) == 0)
> > +                             devname += 5;
> > +
> > +                     devices = xrealloc_vector(devices, 4, count);
> > +                     devices[count].name = xstrdup(devname);

strdup'ing is not needed

> > +                     devices[count].size = read_size_from_sysfs(devname);
> > +                     devices[count].type = get_device_type(devname);
> > +                     devices[count].mountpoint = get_mountpoint(devname);
> > +                     count++;
> > +                     argv++;
> > +             }
> > +     } else {
> > +             /* Read all devices from /sys/block */
> > +             dir = opendir("/sys/block");

util-linux's lsblk shows partitions too.
They can be found if you scan "/sys/class/block" instead.
(It also doesn't show "loop" - maybe shows only if it's attached?)

> > +             if (!dir)
> > +                     bb_simple_perror_msg_and_die("/sys/block");

Use xopendir()

> > +             while ((entry = readdir(dir)) != NULL) {
> > +                     if (DOT_OR_DOTDOT(entry->d_name))
> > +                             continue;
> > +
> > +                     devices = xrealloc_vector(devices, 4, count);
> > +                     devices[count].name = xstrdup(entry->d_name);
> > +                     devices[count].size = read_size_from_sysfs(entry->d_name);
> > +                     devices[count].type = get_device_type(entry->d_name);
> > +                     devices[count].mountpoint = get_mountpoint(entry->d_name);
> > +                     count++;
> > +             }
> > +             closedir(dir);

if (ENABLE_FEATURE_CLEAN_UP) closedir(dir);

> > +     /* Print header */
> > +     printf("%-15s %8s %-6s %s\n", "NAME", "SIZE", "TYPE", "MOUNTPOINT");
> > +
> > +     /* Print devices */
> > +     for (i = 0; i < count; i++) {
> > +             printf("%-15s ", devices[i].name);
> > +             print_size(devices[i].size);
> > +             printf(" %-6s ", devices[i].type);
> > +             if (devices[i].mountpoint)
> > +                     printf("%s", devices[i].mountpoint);
> > +             printf("\n");

This can be combined into fewer printf's

> > +             free(devices[i].name);
> > +             free(devices[i].type);
> > +             if (devices[i].mountpoint)
> > +                     free(devices[i].mountpoint);

Just do not bother. (Allows optimization where strings are *sometimes*
malloced).

> > +     }
> > +
> > +     if (devices)
> > +             free(devices);
> > +
> > +     return fflush_all();

fflush_stdout_and_exit_SUCCESS();
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.