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 <zhengjunbo1@xiaomi.com>
This commit is contained in:
Junbo Zheng 2026-07-23 19:47:13 +08:00 committed by Alan C. Assis
parent 6ca1272b99
commit 101b713be6
4 changed files with 249 additions and 3 deletions

View file

@ -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

View file

@ -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

View file

@ -354,6 +354,11 @@ static const struct cmdmap_s g_cmdmap[] =
CMD_MAP("ls", cmd_ls, 1, 5, "[-lRsh] <dir-path>"),
#endif
#ifndef CONFIG_NSH_DISABLE_DU
CMD_MAP("du", cmd_du, 1, 7,
"[-h] [-s] [-a] [-d N] <path>..."),
#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),

View file

@ -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
****************************************************************************/