From 101b713be6c42dd757e63c020af3aacbd14d257e Mon Sep 17 00:00:00 2001 From: Junbo Zheng Date: Thu, 23 Jul 2026 19:47:13 +0800 Subject: [PATCH] nshlib: add du command support Add a du command to NSH that recursively summarizes the size of each path argument in 1K-blocks, or human-readable form with -h. Supports -s (summary only), -a (all files), -d N (max-depth), and -h, matching GNU du semantics. Testing: Built and ran on sim:nsh with: ```bash cmake -B out/nuttx_sim_nsh -S nuttx -DBOARD_CONFIG=sim:nsh -GNinja ninja -C out/nuttx_sim_nsh ./out/nuttx_sim_nsh/nuttx nsh> du 397449 /data/test/elf 71993 /data/test/coredump 5 /data/test/log2 3251 /data/test/log1 472700 /data/test nsh> du -h 388.1M /data/test/elf 70.3M /data/test/coredump 4.1K /data/test/log2 3.1M /data/test/log1 461.6M /data/test ``` Host Ubuntu22.04 du on the same directory: ```bash $ du 397456 ./elf 72000 ./coredump 8 ./log2 3252 ./log1 472720 . $ du -h 389M ./elf 71M ./coredump 8.0K ./log2 3.2M ./log1 462M . ``` Signed-off-by: Junbo Zheng --- nshlib/Kconfig | 4 + nshlib/nsh.h | 10 +- nshlib/nsh_command.c | 5 + nshlib/nsh_fscmds.c | 233 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 3 deletions(-) diff --git a/nshlib/Kconfig b/nshlib/Kconfig index 165bcc8b3..499f09345 100644 --- a/nshlib/Kconfig +++ b/nshlib/Kconfig @@ -413,6 +413,10 @@ config NSH_DISABLE_DMESG bool "Disable dmesg" default DEFAULT_SMALL +config NSH_DISABLE_DU + bool "Disable du" + default DEFAULT_SMALL + config NSH_DISABLE_ECHO bool "Disable echo" default DEFAULT_SMALL diff --git a/nshlib/nsh.h b/nshlib/nsh.h index d2b7abc44..a7ceb7c95 100644 --- a/nshlib/nsh.h +++ b/nshlib/nsh.h @@ -411,12 +411,13 @@ #define NSH_HAVE_IOBUFFER 1 -/* The I/O buffer is needed for the ls, cp, and ps commands. It is also - * needed if the platform supplied MOTD is configured. +/* The I/O buffer is needed for the ls, cp, du, and ps commands. It is + * also needed if the platform supplied MOTD is configured. */ #if defined(CONFIG_NSH_DISABLE_LS) && defined(CONFIG_NSH_DISABLE_CP) && \ - defined(CONFIG_NSH_DISABLE_PS) && !defined(CONFIG_NSH_PLATFORM_MOTD) && \ + defined(CONFIG_NSH_DISABLE_DU) && defined(CONFIG_NSH_DISABLE_PS) && \ + !defined(CONFIG_NSH_PLATFORM_MOTD) && \ defined(CONFIG_DISABLE_ENVIRON) # undef NSH_HAVE_IOBUFFER #endif @@ -996,6 +997,9 @@ int cmd_irqinfo(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv); #ifndef CONFIG_NSH_DISABLE_LS int cmd_ls(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv); #endif +#ifndef CONFIG_NSH_DISABLE_DU + int cmd_du(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv); +#endif #if defined(CONFIG_SYSLOG_DEVPATH) && !defined(CONFIG_NSH_DISABLE_DMESG) int cmd_dmesg(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv); #endif diff --git a/nshlib/nsh_command.c b/nshlib/nsh_command.c index a50e223c9..098abaa76 100644 --- a/nshlib/nsh_command.c +++ b/nshlib/nsh_command.c @@ -354,6 +354,11 @@ static const struct cmdmap_s g_cmdmap[] = CMD_MAP("ls", cmd_ls, 1, 5, "[-lRsh] "), #endif +#ifndef CONFIG_NSH_DISABLE_DU + CMD_MAP("du", cmd_du, 1, 7, + "[-h] [-s] [-a] [-d N] ..."), +#endif + #if defined(CONFIG_MODULE) && !defined(CONFIG_NSH_DISABLE_MODCMDS) # if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE) CMD_MAP("lsmod", cmd_lsmod, 1, 1, NULL), diff --git a/nshlib/nsh_fscmds.c b/nshlib/nsh_fscmds.c index a3423494c..0a6fd945f 100644 --- a/nshlib/nsh_fscmds.c +++ b/nshlib/nsh_fscmds.c @@ -1855,6 +1855,239 @@ int cmd_ls(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv) } #endif +/**************************************************************************** + * Name: du_print + ****************************************************************************/ + +#ifndef CONFIG_NSH_DISABLE_DU +#define DU_FLAG_HUMANREADABLE (1 << 0) /* -h: human readable sizes */ +#define DU_FLAG_ALL (1 << 1) /* -a: list all files, not only dirs */ + +static void du_print(FAR struct nsh_vtbl_s *vtbl, FAR const char *path, + off_t bytes, unsigned int flags) +{ + off_t kblocks = (bytes + 1023) / 1024; + + if ((flags & DU_FLAG_HUMANREADABLE) != 0) + { + off_t unit; + char suffix; + + if (bytes >= GB) + { + unit = GB; + suffix = 'G'; + } + else if (bytes >= MB) + { + unit = MB; + suffix = 'M'; + } + else if (bytes >= KB) + { + unit = KB; + suffix = 'K'; + } + else + { + nsh_output(vtbl, "%" PRIdOFF "B\t%s\n", bytes, path); + return; + } + + /* Use integer arithmetic to avoid floating point */ + + nsh_output(vtbl, "%" PRIdOFF ".%" PRIdOFF "%c\t%s\n", + bytes / unit, (bytes % unit) * 10 / unit, suffix, path); + } + else + { + nsh_output(vtbl, "%" PRIdOFF "\t%s\n", kblocks, path); + } +} + +static off_t du_recursive(FAR struct nsh_vtbl_s *vtbl, FAR const char *path, + unsigned int flags, int printlimit, int depth) +{ + FAR struct dirent *entry; + FAR char *child; + struct stat st; + off_t total; + DIR *dp; + + if (lstat(path, &st) < 0) + { + nsh_error(vtbl, g_fmtcmdfailed, "du", "stat", NSH_ERRNO); + return 0; + } + + /* st_size not st_blocks: units differ (NuttX FS=st_blksize, hostfs=512) */ + + total = st.st_size; + + /* A file argument (depth 0) is always shown; nested files need -a. */ + + if (!S_ISDIR(st.st_mode)) + { + if (depth == 0 || + ((flags & DU_FLAG_ALL) != 0 && depth <= printlimit)) + { + du_print(vtbl, path, total, flags); + } + + return total; + } + + dp = opendir(path); + if (dp == NULL) + { + nsh_error(vtbl, g_fmtcmdfailed, "du", "opendir", NSH_ERRNO); + } + else + { + while ((entry = readdir(dp)) != NULL) + { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + { + continue; + } + + child = nsh_getdirpath(vtbl, path, entry->d_name); + if (child == NULL) + { + nsh_error(vtbl, g_fmtcmdfailed, "du", "nsh_getdirpath", + NSH_ERRNO); + continue; + } + + total += du_recursive(vtbl, child, flags, printlimit, depth + 1); + free(child); + } + + closedir(dp); + } + + /* Print this directory's cumulative total unless suppressed by -s/-d. */ + + if (depth <= printlimit) + { + du_print(vtbl, path, total, flags); + } + + return total; +} +#endif + +/**************************************************************************** + * Name: cmd_du + ****************************************************************************/ + +#ifndef CONFIG_NSH_DISABLE_DU +int cmd_du(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv) +{ + unsigned int flags = 0; + int printlimit = INT_MAX; + bool summary = false; + bool badarg = false; + int option; + int i; + int ret = OK; + + /* Get the du options */ + + while ((option = getopt(argc, argv, "hsad:")) != ERROR) + { + switch (option) + { + case 'h': + flags |= DU_FLAG_HUMANREADABLE; + break; + + case 's': + summary = true; + break; + + case 'a': + flags |= DU_FLAG_ALL; + break; + + case 'd': + printlimit = atoi(optarg); + break; + + case '?': + default: + nsh_error(vtbl, g_fmtarginvalid, argv[0]); + badarg = true; + break; + } + } + + /* If a bad argument was encountered, + * then return without processing the command + */ + + if (badarg) + { + return ERROR; + } + + /* -s reports only each argument's own total (depth 0); -d N overrides -s + * when both are given. -s also suppresses the -a file listing. + */ + + if (summary) + { + if (printlimit == INT_MAX) + { + printlimit = 0; + } + + flags &= ~DU_FLAG_ALL; + } + + /* Walk each path argument (default: current directory). */ + + if (optind >= argc) + { +#ifndef CONFIG_DISABLE_ENVIRON + FAR char *fullpath = nsh_getfullpath(vtbl, nsh_getcwd(vtbl)); + + if (fullpath == NULL) + { + nsh_error(vtbl, g_fmtcmdoutofmemory, argv[0]); + return ERROR; + } + + du_recursive(vtbl, fullpath, flags, printlimit, 0); + nsh_freefullpath(fullpath); +#else + nsh_error(vtbl, g_fmtargrequired, argv[0]); + return ERROR; +#endif + } + else + { + for (i = optind; i < argc; i++) + { + FAR char *fullpath = nsh_getfullpath(vtbl, argv[i]); + + if (fullpath == NULL) + { + nsh_error(vtbl, g_fmtcmdoutofmemory, argv[0]); + ret = ERROR; + continue; + } + + du_recursive(vtbl, fullpath, flags, printlimit, 0); + nsh_freefullpath(fullpath); + } + } + + return ret; +} +#endif + /**************************************************************************** * Name: cmd_mkdir ****************************************************************************/