From 7ace1d77ffe3098137516fb86524efd09e487928 Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Tue, 28 Jul 2026 04:07:16 -0300 Subject: [PATCH 1/7] libs/libc/grp: fix getgrbuf_r() pointer-alignment padding padlen = sizeof(void *) - (addr % sizeof(void *)) never returns 0, even when addr is already pointer-aligned -- it returns a full alignment unit instead. Since callers size buflen for zero padding, the subsequent "buflen < padlen + reqdlen" check then always fails, so getgrgid()/ getgrnam() and their _r variants always return ERANGE. Found via `id` on sim:toybox, which resolves gid 0 to "root" through this path. Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- libs/libc/grp/lib_getgrbufr.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/libc/grp/lib_getgrbufr.c b/libs/libc/grp/lib_getgrbufr.c index 80bbd3b25dea3..0dd3e2594d86b 100644 --- a/libs/libc/grp/lib_getgrbufr.c +++ b/libs/libc/grp/lib_getgrbufr.c @@ -77,7 +77,15 @@ int getgrbuf_r(gid_t gid, FAR const char *name, FAR const char *passwd, namesize = strlen(name) + 1; passwdsize = strlen(passwd) + 1; - padlen = sizeof(FAR void *) - ((uintptr_t)buf % sizeof(FAR char *)); + + /* Bytes needed to round 'buf' up to the next pointer-aligned address. + * The two's-complement modulo trick below yields 0 when 'buf' is + * already aligned; "sizeof(void *) - (addr % sizeof(void *))" (the + * previous formula) does not, always returning a full alignment unit + * in that case, which made the buflen check below always fail. + */ + + padlen = (-(uintptr_t)buf) % sizeof(FAR void *); reqdlen = sizeof(FAR void *) + namesize + passwdsize; if (buflen < padlen + reqdlen) From a35832892e636f733bdebc743222f5f23739b77f Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Wed, 29 Jul 2026 17:40:28 -0300 Subject: [PATCH 2/7] libc: add wait4() wait4() is BSD/Linux-standard (used by toybox's "time" applet) but NuttX only had waitpid()+getrusage() separately. Add it to libs/libc/unistd/ built on top of those two existing primitives, so it needs no syscall plumbing of its own and works unmodified across flat/protected/kernel build separation. Prototype added to include/sys/wait.h. Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- include/sys/wait.h | 9 +++++ libs/libc/unistd/CMakeLists.txt | 4 +++ libs/libc/unistd/Make.defs | 4 +++ libs/libc/unistd/lib_wait4.c | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 libs/libc/unistd/lib_wait4.c diff --git a/include/sys/wait.h b/include/sys/wait.h index 4f653a8fbe426..f693436cf4227 100644 --- a/include/sys/wait.h +++ b/include/sys/wait.h @@ -96,6 +96,15 @@ pid_t wait(FAR int *stat_loc); int waitid(idtype_t idtype, id_t id, FAR siginfo_t *info, int options); pid_t waitpid(pid_t pid, FAR int *stat_loc, int options); +/* wait4() is waitpid() plus the reaped child's struct rusage in one call. + * struct rusage is forward-declared here (not #include ) + * since it is only ever used as a pointer type in this prototype. + */ + +struct rusage; +pid_t wait4(pid_t pid, FAR int *stat_loc, int options, + FAR struct rusage *rusage); + #undef EXTERN #if defined(__cplusplus) } diff --git a/libs/libc/unistd/CMakeLists.txt b/libs/libc/unistd/CMakeLists.txt index eb4ca8e5e937d..6950a69d4efed 100644 --- a/libs/libc/unistd/CMakeLists.txt +++ b/libs/libc/unistd/CMakeLists.txt @@ -76,6 +76,10 @@ set(SRCS lib_confstr.c lib_ulimit.c) +if(CONFIG_SCHED_WAITPID) + list(APPEND SRCS lib_wait4.c) +endif() + if(NOT CONFIG_SCHED_USER_IDENTITY) list( APPEND diff --git a/libs/libc/unistd/Make.defs b/libs/libc/unistd/Make.defs index e3e5ed3a2148d..c78e7640c9e09 100644 --- a/libs/libc/unistd/Make.defs +++ b/libs/libc/unistd/Make.defs @@ -36,6 +36,10 @@ CSRCS += lib_getsid.c lib_getgroups.c lib_setpgid.c lib_setsid.c CSRCS += lib_lockf.c lib_flock.c lib_getpass.c CSRCS += lib_chdir.c lib_fchdir.c lib_confstr.c lib_ulimit.c +ifeq ($(CONFIG_SCHED_WAITPID),y) +CSRCS += lib_wait4.c +endif + ifneq ($(CONFIG_SCHED_USER_IDENTITY),y) CSRCS += lib_setuid.c lib_setgid.c lib_getuid.c lib_getgid.c CSRCS += lib_seteuid.c lib_setegid.c lib_geteuid.c lib_getegid.c diff --git a/libs/libc/unistd/lib_wait4.c b/libs/libc/unistd/lib_wait4.c new file mode 100644 index 0000000000000..747eeb3d3f3d6 --- /dev/null +++ b/libs/libc/unistd/lib_wait4.c @@ -0,0 +1,64 @@ +/**************************************************************************** + * libs/libc/unistd/lib_wait4.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: wait4 + * + * Description: + * wait4() is waitpid() plus the reaped child's resource usage in one + * call. Built entirely on top of the existing waitpid()/getrusage() + * primitives (each already correctly proxied across build separation -- + * waitpid() is a real syscall, getrusage() is plain libc), so this needs + * no syscall plumbing of its own and works unmodified in flat, + * protected, and kernel builds alike. + * + * NuttX's getrusage() only supports RUSAGE_SELF/RUSAGE_CHILDREN (not a + * specific pid), so the returned usage is exact only when the caller has + * a single outstanding child at a time. + * + ****************************************************************************/ + +pid_t wait4(pid_t pid, FAR int *stat_loc, int options, + FAR struct rusage *rusage) +{ + pid_t ret = waitpid(pid, stat_loc, options); + + if (ret > 0 && rusage != NULL) + { + getrusage(RUSAGE_CHILDREN, rusage); + } + + return ret; +} From b0df4722f69413e387dc6a0b24a60b7aa7ceb053 Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Wed, 29 Jul 2026 22:02:16 -0300 Subject: [PATCH 3/7] fs: resolve a trailing lone '.' path component inode_nextname() already skipped a '.' segment mid-path (e.g. "./foo"), but only checked for a '/' right after it -- a path ending in a bare '.' (e.g. "/foo/.", or "." itself once AT_FDCWD resolution prepends $PWD) fell through and was looked up as a literal child named ".", which no real node is ever named, failing with ENOENT. This broke every "operate on the current directory" idiom relative paths rely on: bare `ls`, `stat .`, `cd .`, etc., all failed outright even though the equivalent absolute path worked fine. Found while testing the Toybox port's interactive REPL, but this is generic VFS path resolution, not Toybox-specific. Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- fs/inode/fs_inodesearch.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/fs/inode/fs_inodesearch.c b/fs/inode/fs_inodesearch.c index 1aecb529ad01e..67a14cf474eef 100644 --- a/fs/inode/fs_inodesearch.c +++ b/fs/inode/fs_inodesearch.c @@ -558,15 +558,32 @@ FAR const char *inode_nextname(FAR const char *name) name++; } - /* Skip single '.' path segment, but not '..' */ + /* Skip single '.' path segment, but not '..'. This includes a lone + * trailing '.' as the final path component (e.g. "/foo/."), which + * refers to "foo" itself the same way "/foo/./" would -- without this, + * a trailing '.' is instead treated as a literal child name to look up + * under "foo" and fails to resolve, since no real node is ever named + * ".", rather than resolving to the node the search already reached. + */ - if (*name == '.' && *(name + 1) == '/') + if (*name == '.' && (*(name + 1) == '/' || *(name + 1) == '\0')) { - /* If there is a '/' after '.', - * continue searching from the next character - */ + if (*(name + 1) == '/') + { + /* If there is a '/' after '.', + * continue searching from the next character + */ - name = inode_nextname(name); + name = inode_nextname(name); + } + else + { + /* Lone trailing '.': point past it, at the terminating NUL, + * the same as if the path had ended one character earlier. + */ + + name++; + } } return name; From 2b371ec1b63fe8d29852e4cd6699e0db56a555e2 Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Tue, 28 Jul 2026 04:30:23 -0300 Subject: [PATCH 4/7] boards: sim: add sim:toybox defconfig CONFIG_SYSTEM_TOYBOX=y with CONFIG_INIT_ENTRYPOINT="toybox_main": Toybox as the system's shell instead of NSH. No NSH config is present -- Toybox has no dependency on it in either direction (see apps/system/toybox/Kconfig's SYSTEM_TOYBOX_BUILTIN_BRIDGE). Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- boards/sim/sim/sim/configs/toybox/defconfig | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 boards/sim/sim/sim/configs/toybox/defconfig diff --git a/boards/sim/sim/sim/configs/toybox/defconfig b/boards/sim/sim/sim/configs/toybox/defconfig new file mode 100644 index 0000000000000..56281dd0373cf --- /dev/null +++ b/boards/sim/sim/sim/configs/toybox/defconfig @@ -0,0 +1,78 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +CONFIG_ARCH="sim" +CONFIG_ARCH_BOARD="sim" +CONFIG_ARCH_BOARD_SIM=y +CONFIG_ARCH_CHIP="sim" +CONFIG_ARCH_SIM=y +CONFIG_BOARDCTL=y +CONFIG_BOARDCTL_APP_SYMTAB=y +CONFIG_BOARDCTL_MKRD=y +CONFIG_BOARDCTL_POWEROFF=y +CONFIG_BOARD_LOOPSPERMSEC=0 +CONFIG_BOOT_RUNFROMEXTSRAM=y +CONFIG_COVERAGE_ALL=y +CONFIG_COVERAGE_TOOLCHAIN=y +CONFIG_DEBUG_ASSERTIONS=y +CONFIG_DEBUG_ASSERTIONS_EXPRESSION=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEV_GPIO=y +CONFIG_DEV_LOOP=y +CONFIG_ETC_FATDEVNO=2 +CONFIG_ETC_ROMFS=y +CONFIG_ETC_ROMFSDEVNO=1 +CONFIG_EXAMPLES_GPIO=y +CONFIG_EXAMPLES_HELLO=y +CONFIG_FAT_LCNAMES=y +CONFIG_FAT_LFN=y +CONFIG_FS_BINFS=y +CONFIG_FS_FAT=y +CONFIG_FS_HOSTFS=y +CONFIG_FS_PROCFS=y +CONFIG_FS_RAMMAP=y +CONFIG_FS_ROMFS=y +CONFIG_GPIO_LOWER_HALF=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_IDLETHREAD_STACKSIZE=4096 +CONFIG_INIT_ENTRYPOINT="toybox_main" +CONFIG_IOEXPANDER=y +CONFIG_IOEXPANDER_DUMMY=y +CONFIG_LIBC_ENVPATH=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_LIBC_LOCALE=y +CONFIG_LIBC_LOCALE_CATALOG=y +CONFIG_LIBC_LOCALE_GETTEXT=y +CONFIG_LIBC_MAX_EXITFUNS=1 +CONFIG_LIBC_NUMBERED_ARGS=y +CONFIG_NDEBUG=y +CONFIG_PATH_INITIAL="/bin" +CONFIG_PIPES=y +CONFIG_PSEUDOFS_ATTRIBUTES=y +CONFIG_PSEUDOFS_FILE=y +CONFIG_PSEUDOFS_SOFTLINKS=y +CONFIG_READLINE_CMD_HISTORY=y +CONFIG_READLINE_EDIT_EMACS=y +CONFIG_READLINE_TABCOMPLETION=y +CONFIG_RTC=y +CONFIG_RTC_ARCH=y +CONFIG_RTC_DRIVER=y +CONFIG_SCHED_BACKTRACE=y +CONFIG_SCHED_EVENTS=y +CONFIG_SCHED_HAVE_PARENT=y +CONFIG_SCHED_WAITPID=y +CONFIG_SIM_HOSTFS=y +CONFIG_SIM_WALLTIME_SIGNAL=y +CONFIG_START_MONTH=6 +CONFIG_START_YEAR=2008 +CONFIG_SYSTEM_CLE=y +CONFIG_SYSTEM_DUMPSTACK=y +CONFIG_SYSTEM_GCOV=y +CONFIG_SYSTEM_READLINE=y +CONFIG_SYSTEM_TOYBOX=y +CONFIG_TESTING_OSTEST=y From 0e86fc8625e0e5fb7ddf7402e9722ae470785e10 Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Tue, 28 Jul 2026 05:11:51 -0300 Subject: [PATCH 5/7] boards: stm32f4discovery: add toybox defconfig Same shape as boards/sim/sim/sim/configs/toybox: CONFIG_SYSTEM_TOYBOX=y with CONFIG_INIT_ENTRYPOINT="toybox_main", built on top of the existing stm32f4discovery:nsh defconfig's board/console setup. Needs several options nsh's defconfig doesn't, since Toybox's library code references more of NuttX's libc unconditionally than NSH does: CONFIG_ALLOW_MIT_COMPONENTS (gates CONFIG_LIBC_REGEX -- grep/sed/etc), CONFIG_ARCH_SETJMP_H (sigjmp_buf; the REPL's rebound trap uses sigsetjmp/siglongjmp), CONFIG_LIBC_EXECFUNCS, CONFIG_LIBC_LOCALE, CONFIG_LIBC_LOCALTIME, CONFIG_PIPES, CONFIG_PSEUDOFS_SOFTLINKS, CONFIG_FS_NOTIFY (tail -f), CONFIG_SCHED_HAVE_PARENT (waitpid()). Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- .../stm32f4discovery/configs/toybox/defconfig | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 boards/arm/stm32f4/stm32f4discovery/configs/toybox/defconfig diff --git a/boards/arm/stm32f4/stm32f4discovery/configs/toybox/defconfig b/boards/arm/stm32f4/stm32f4discovery/configs/toybox/defconfig new file mode 100644 index 0000000000000..d8fad21601d21 --- /dev/null +++ b/boards/arm/stm32f4/stm32f4discovery/configs/toybox/defconfig @@ -0,0 +1,53 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ARCH_FPU is not set +CONFIG_ALLOW_MIT_COMPONENTS=y +CONFIG_ARCH="arm" +CONFIG_ARCH_BOARD="stm32f4discovery" +CONFIG_ARCH_BOARD_STM32F4_DISCOVERY=y +CONFIG_ARCH_BUTTONS=y +CONFIG_ARCH_CHIP="stm32f4" +CONFIG_ARCH_CHIP_STM32=y +CONFIG_ARCH_CHIP_STM32F407VG=y +CONFIG_ARCH_CHIP_STM32F4=y +CONFIG_ARCH_SETJMP_H=y +CONFIG_ARCH_STACKDUMP=y +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_EXAMPLES_HELLO=y +CONFIG_FS_NOTIFY=y +CONFIG_FS_PROCFS=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_HOST_WINDOWS=y +CONFIG_INIT_ENTRYPOINT="toybox_main" +CONFIG_INTELHEX_BINARY=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_LIBC_LOCALE=y +CONFIG_LIBC_LOCALTIME=y +CONFIG_LINE_MAX=64 +CONFIG_MM_REGIONS=2 +CONFIG_PIPES=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_PSEUDOFS_SOFTLINKS=y +CONFIG_RAM_SIZE=114688 +CONFIG_RAM_START=0x20000000 +CONFIG_RAW_BINARY=y +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_HAVE_PARENT=y +CONFIG_SCHED_WAITPID=y +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_STM32_JTAG_SW_ENABLE=y +CONFIG_STM32_PWR=y +CONFIG_STM32_SPI1=y +CONFIG_STM32_USART2=y +CONFIG_SYSTEM_TOYBOX=y +CONFIG_USART2_RXBUFSIZE=128 +CONFIG_USART2_SERIAL_CONSOLE=y +CONFIG_USART2_TXBUFSIZE=128 From 32e0e8a19c9351902f6bf64b43eeaf26652debdf Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Tue, 28 Jul 2026 04:30:54 -0300 Subject: [PATCH 6/7] Documentation: add apps/system/toybox entry Basic usage/configuration reference for the toybox application, plus known limitations: ps lists no processes (it expects Linux's /proc//stat, which NuttX's procfs doesn't provide), and grep -r is unreliable against procfs. Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- .../applications/system/toybox/index.rst | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/applications/system/toybox/index.rst diff --git a/Documentation/applications/system/toybox/index.rst b/Documentation/applications/system/toybox/index.rst new file mode 100644 index 0000000000000..9e345f3ae6559 --- /dev/null +++ b/Documentation/applications/system/toybox/index.rst @@ -0,0 +1,54 @@ +=================================== +``toybox`` Toybox Command Suite +=================================== + +Overview +======== + +Toybox (https://landley.net/toybox/) is a 0BSD-licensed multi-call binary +providing a POSIX/LSB command suite and a small interactive shell. The +``toybox`` application downloads a pinned upstream release during the +build, patches it for NuttX, and builds it as a single NuttX task +(``PROGNAME=toybox``). + +Run with no arguments it acts as an interactive ``toybox>`` command +prompt, suitable for use as the system's ``CONFIG_INIT_ENTRYPOINT``. Run +with arguments (``toybox ls``, ``toybox cat file``) it runs one command +and exits, which is how it is invoked as an NSH builtin. + +Toybox does not depend on NSH in either direction. When both are enabled, +each can invoke the other at runtime: NSH via its own +``CONFIG_NSH_BUILTIN_APPS``, Toybox via ``CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE``. + +Configuration +============= + +Enable the application with ``CONFIG_SYSTEM_TOYBOX``, under +:menuselection:`System Libraries and NSH Add-Ons` in ``menuconfig``. + +- ``CONFIG_SYSTEM_TOYBOX_VERSION`` -- upstream release tag to download +- ``CONFIG_SYSTEM_TOYBOX_PRIORITY``, ``CONFIG_SYSTEM_TOYBOX_STACKSIZE`` +- ``CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE`` -- run other NuttX builtin + applications (``nsh``, ``hello``, ...) from the Toybox prompt + +Which commands are built in is selected individually under the "Toybox +commands" submenu (one ``CONFIG_SYSTEM_TOYBOX_CMD_`` option per +applet). + +Usage +===== + +.. code-block:: console + + nsh> toybox ls / + nsh> toybox + +The second form starts the interactive prompt; ``exit`` or ``quit`` +leaves it. + +Known limitations +================= + +- ``ps`` builds but lists no processes: it parses Linux's + ``/proc//stat``, which has no equivalent in NuttX's procfs. +- ``grep -r`` is unreliable against procfs. From be998ebb91ebebfd84e934e63ba4a84aeaa57b2a Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Wed, 12 Aug 2026 14:43:35 -0300 Subject: [PATCH 7/7] boards: sim: enable CONFIG_LIBC_LOCALTIME for sim:toybox tzset() is unconditionally defined in libs/libc/time/lib_localtime.c, but its prototype in is gated behind CONFIG_LIBC_LOCALTIME -- without it, Toybox's own tzset() calls (lib/xwrap.c, toys/posix/date.c) compile as implicit declarations instead. The stm32f4discovery:toybox defconfig already carries this option (see its own commit message); sim:toybox's was simply missing it. Signed-off-by: Alan C. Assis Assisted-by: Claude Sonnet 5 --- boards/sim/sim/sim/configs/toybox/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/boards/sim/sim/sim/configs/toybox/defconfig b/boards/sim/sim/sim/configs/toybox/defconfig index 56281dd0373cf..b8a6c76c23267 100644 --- a/boards/sim/sim/sim/configs/toybox/defconfig +++ b/boards/sim/sim/sim/configs/toybox/defconfig @@ -48,6 +48,7 @@ CONFIG_LIBC_EXECFUNCS=y CONFIG_LIBC_LOCALE=y CONFIG_LIBC_LOCALE_CATALOG=y CONFIG_LIBC_LOCALE_GETTEXT=y +CONFIG_LIBC_LOCALTIME=y CONFIG_LIBC_MAX_EXITFUNS=1 CONFIG_LIBC_NUMBERED_ARGS=y CONFIG_NDEBUG=y