system/toybox: Add toybox a tool box similar to busybox

Toybox is the toolbox used on Android by default. Adding it to NuttX
allows to have more advanced features from Linux, even better support
for shell scripts.

Signed-off-by: Alan C. Assis <acassis@gmail.com>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Alan Carvalho de Assis 2026-08-12 11:45:51 -03:00 committed by CeDeROM
parent 3328bbb0be
commit 84ffa84e24
29 changed files with 2569 additions and 0 deletions

7
system/toybox/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
/toybox
/*.tar.gz
/.toybox_allconfig
*.o
.built
.depend
Make.dep

81
system/toybox/Kconfig Normal file
View file

@ -0,0 +1,81 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
menuconfig SYSTEM_TOYBOX
bool "Toybox command suite"
default n
depends on SCHED_WAITPID
depends on SCHED_HAVE_PARENT
depends on !DISABLE_PSEUDOFS_OPERATIONS
---help---
Download, patch and build Toybox (https://landley.net/toybox/), a
0BSD-licensed multi-call binary providing a POSIX/LSB command suite
and a small interactive shell. Toybox is built as a single NuttX
builtin application (PROGNAME=toybox) that can be run directly
("toybox ls", "toybox cat file") or, with no arguments, as an
interactive command prompt suitable for use as the system's
CONFIG_INIT_ENTRYPOINT -- see boards/sim/sim/sim/configs/toybox for
an example. Toybox runs standalone (no NSH required) while still
being able to run whatever other NuttX builtin apps happen to be
enabled, NSH included if the user also enables it. See
Documentation/applications/system/toybox/ for details.
Only the commands enabled below (see "Toybox commands") are
compiled in.
if SYSTEM_TOYBOX
config SYSTEM_TOYBOX_VERSION
string "Toybox version to download"
default "0.8.14"
---help---
Upstream Toybox release tag to download and build against. The
patches in apps/system/toybox/patch/ are written against this
version; bumping it may require refreshing those patches.
config SYSTEM_TOYBOX_URL
string "Toybox download URL"
default "https://github.com/landley/toybox/archive/refs/tags"
---help---
Base URL Toybox release tarballs are downloaded from.
config SYSTEM_TOYBOX_PRIORITY
int "Toybox task priority"
default 100
config SYSTEM_TOYBOX_STACKSIZE
int "Toybox stack size"
default 8192
config SYSTEM_TOYBOX_BUILTIN_BRIDGE
bool "Run other NuttX builtin apps from the Toybox prompt"
default y
select BUILTIN
---help---
When a command typed at the Toybox prompt isn't a Toybox applet,
look it up in NuttX's registered builtin application table and run
it if found. This is what lets "hello", "nsh" (if the user also
enables CONFIG_SYSTEM_NSH) and any other app registered as a
builtin (MODULE=y) run from the Toybox prompt without Toybox
needing to know about them individually -- useful when
CONFIG_INIT_ENTRYPOINT is
"toybox_main" and Toybox is the only shell running. Selects
CONFIG_BUILTIN (off by default in NuttX); nothing else is
required -- NSH is not a dependency of Toybox in any direction.
Toybox applets always take precedence over a same-named builtin.
menu "Toybox commands"
comment "Each option here is one Toybox applet. ../Makefile translates"
comment "whichever are enabled into the CONFIG_<CMD>=y file it feeds"
comment "to Toybox's own scripts/genconfig.sh at build time -- toggling"
comment "a command here is the whole mechanism, nothing else to sync."
source "$APPSDIR/system/toybox/Kconfig.commands"
endmenu # Toybox commands
endif # SYSTEM_TOYBOX

View file

@ -0,0 +1,461 @@
#
# apps/system/toybox/Kconfig.commands
#
# One config per Toybox applet, sourced from apps/system/toybox/Kconfig's
# "Toybox commands" menu. Hand-maintained (unlike apps/Kconfig or
# apps/system/Kconfig, this file is NOT regenerated by tools/mkkconfig.sh),
# curated to applets known to build on NuttX. To add
# another command, add a "config SYSTEM_TOYBOX_CMD_<NAME>" block (all-caps
# applet name) matching the pattern below. The CMD_ segment is what lets
# ../Makefile tell actual commands apart from this directory's other
# SYSTEM_TOYBOX_* options (VERSION, BUILTIN_BRIDGE, ...) when it translates
# enabled commands into Toybox's own CONFIG_<NAME> form -- don't drop it.
#
config SYSTEM_TOYBOX_CMD_BASE64
bool "base64"
default y
config SYSTEM_TOYBOX_CMD_BASENAME
bool "basename"
default y
config SYSTEM_TOYBOX_CMD_CAL
bool "cal"
default y
config SYSTEM_TOYBOX_CMD_CAT
bool "cat"
default y
config SYSTEM_TOYBOX_CMD_CHGRP
bool "chgrp"
default y
config SYSTEM_TOYBOX_CMD_CHMOD
bool "chmod"
default y
config SYSTEM_TOYBOX_CMD_CHOWN
bool "chown"
default y
config SYSTEM_TOYBOX_CMD_CKSUM
bool "cksum"
default y
config SYSTEM_TOYBOX_CMD_CLEAR
bool "clear"
default y
config SYSTEM_TOYBOX_CMD_CMP
bool "cmp"
default y
config SYSTEM_TOYBOX_CMD_COMM
bool "comm"
default y
config SYSTEM_TOYBOX_CMD_CP
bool "cp"
default y
config SYSTEM_TOYBOX_CMD_CPIO
bool "cpio"
default y
config SYSTEM_TOYBOX_CMD_CUT
bool "cut"
default y
config SYSTEM_TOYBOX_CMD_DATE
bool "date"
default y
config SYSTEM_TOYBOX_CMD_DD
bool "dd"
default y
config SYSTEM_TOYBOX_CMD_DF
bool "df"
default y
config SYSTEM_TOYBOX_CMD_DIFF
bool "diff"
default y
config SYSTEM_TOYBOX_CMD_DIRNAME
bool "dirname"
default y
config SYSTEM_TOYBOX_CMD_DOS2UNIX
bool "dos2unix"
default y
config SYSTEM_TOYBOX_CMD_DU
bool "du"
default y
config SYSTEM_TOYBOX_CMD_ECHO
bool "echo"
default y
config SYSTEM_TOYBOX_CMD_ENV
bool "env"
default y
config SYSTEM_TOYBOX_CMD_EXPAND
bool "expand"
default y
config SYSTEM_TOYBOX_CMD_EXPR
bool "expr"
default y
config SYSTEM_TOYBOX_CMD_FALLOCATE
bool "fallocate"
default y
config SYSTEM_TOYBOX_CMD_FALSE
bool "false"
default y
config SYSTEM_TOYBOX_CMD_FILE
bool "file"
default y
config SYSTEM_TOYBOX_CMD_FIND
bool "find"
default y
config SYSTEM_TOYBOX_CMD_FMT
bool "fmt"
default y
config SYSTEM_TOYBOX_CMD_FREE
bool "free"
default y
config SYSTEM_TOYBOX_CMD_FSYNC
bool "fsync"
default y
config SYSTEM_TOYBOX_CMD_GETOPT
bool "getopt"
default y
config SYSTEM_TOYBOX_CMD_GREP
bool "grep"
default y
config SYSTEM_TOYBOX_CMD_GROUPS
bool "groups"
default y
config SYSTEM_TOYBOX_CMD_GUNZIP
bool "gunzip"
default y
config SYSTEM_TOYBOX_CMD_GZIP
bool "gzip"
default y
config SYSTEM_TOYBOX_CMD_HEAD
bool "head"
default y
config SYSTEM_TOYBOX_CMD_HELP
bool "help"
default y
config SYSTEM_TOYBOX_CMD_HOSTNAME
bool "hostname"
default y
config SYSTEM_TOYBOX_CMD_ID
bool "id"
default y
config SYSTEM_TOYBOX_CMD_KILL
bool "kill"
default y
config SYSTEM_TOYBOX_CMD_KILLALL
bool "killall"
default y
config SYSTEM_TOYBOX_CMD_LN
bool "ln"
default y
config SYSTEM_TOYBOX_CMD_LOGNAME
bool "logname"
default y
config SYSTEM_TOYBOX_CMD_LS
bool "ls"
default y
config SYSTEM_TOYBOX_CMD_MD5SUM
bool "md5sum"
default y
config SYSTEM_TOYBOX_CMD_MKDIR
bool "mkdir"
default y
config SYSTEM_TOYBOX_CMD_MKFIFO
bool "mkfifo"
default y
config SYSTEM_TOYBOX_CMD_MKTEMP
bool "mktemp"
default y
config SYSTEM_TOYBOX_CMD_MV
bool "mv"
default y
config SYSTEM_TOYBOX_CMD_NICE
bool "nice"
default y
config SYSTEM_TOYBOX_CMD_NL
bool "nl"
default y
config SYSTEM_TOYBOX_CMD_NOHUP
bool "nohup"
default y
config SYSTEM_TOYBOX_CMD_NPROC
bool "nproc"
default y
config SYSTEM_TOYBOX_CMD_OD
bool "od"
default y
config SYSTEM_TOYBOX_CMD_PASTE
bool "paste"
default y
config SYSTEM_TOYBOX_CMD_PGREP
bool "pgrep"
default y
config SYSTEM_TOYBOX_CMD_PIDOF
bool "pidof"
default y
config SYSTEM_TOYBOX_CMD_PKILL
bool "pkill"
default y
config SYSTEM_TOYBOX_CMD_PRINTENV
bool "printenv"
default y
config SYSTEM_TOYBOX_CMD_PRINTF
bool "printf"
default y
config SYSTEM_TOYBOX_CMD_PS
bool "ps"
default y
config SYSTEM_TOYBOX_CMD_PWD
bool "pwd"
default y
config SYSTEM_TOYBOX_CMD_READLINK
bool "readlink"
default y
config SYSTEM_TOYBOX_CMD_REALPATH
bool "realpath"
default y
config SYSTEM_TOYBOX_CMD_RENICE
bool "renice"
default y
config SYSTEM_TOYBOX_CMD_REV
bool "rev"
default y
config SYSTEM_TOYBOX_CMD_RM
bool "rm"
default y
config SYSTEM_TOYBOX_CMD_RMDIR
bool "rmdir"
default y
config SYSTEM_TOYBOX_CMD_SED
bool "sed"
default y
config SYSTEM_TOYBOX_CMD_SEQ
bool "seq"
default y
config SYSTEM_TOYBOX_CMD_SETSID
bool "setsid"
default y
config SYSTEM_TOYBOX_CMD_SHA1SUM
bool "sha1sum"
default y
config SYSTEM_TOYBOX_CMD_SHA224SUM
bool "sha224sum"
default y
config SYSTEM_TOYBOX_CMD_SHA256SUM
bool "sha256sum"
default y
config SYSTEM_TOYBOX_CMD_SHA384SUM
bool "sha384sum"
default y
config SYSTEM_TOYBOX_CMD_SHA512SUM
bool "sha512sum"
default y
config SYSTEM_TOYBOX_CMD_SLEEP
bool "sleep"
default y
config SYSTEM_TOYBOX_CMD_SORT
bool "sort"
default y
config SYSTEM_TOYBOX_CMD_SPLIT
bool "split"
default y
config SYSTEM_TOYBOX_CMD_STAT
bool "stat"
default y
config SYSTEM_TOYBOX_CMD_STRINGS
bool "strings"
default y
config SYSTEM_TOYBOX_CMD_SYNC
bool "sync"
default y
config SYSTEM_TOYBOX_CMD_TAC
bool "tac"
default y
config SYSTEM_TOYBOX_CMD_TAIL
bool "tail"
default y
config SYSTEM_TOYBOX_CMD_TAR
bool "tar"
default y
config SYSTEM_TOYBOX_CMD_TEE
bool "tee"
default y
config SYSTEM_TOYBOX_CMD_TEST
bool "test"
default y
config SYSTEM_TOYBOX_CMD_TIME
bool "time"
default y
config SYSTEM_TOYBOX_CMD_TIMEOUT
bool "timeout"
default y
config SYSTEM_TOYBOX_CMD_TOUCH
bool "touch"
default y
config SYSTEM_TOYBOX_CMD_TR
bool "tr"
default y
config SYSTEM_TOYBOX_CMD_TRUE
bool "true"
default y
config SYSTEM_TOYBOX_CMD_TRUNCATE
bool "truncate"
default y
config SYSTEM_TOYBOX_CMD_ULIMIT
bool "ulimit"
default y
config SYSTEM_TOYBOX_CMD_UNAME
bool "uname"
default y
config SYSTEM_TOYBOX_CMD_UNIQ
bool "uniq"
default y
config SYSTEM_TOYBOX_CMD_UNIX2DOS
bool "unix2dos"
default y
config SYSTEM_TOYBOX_CMD_UNLINK
bool "unlink"
default y
config SYSTEM_TOYBOX_CMD_UPTIME
bool "uptime"
default y
config SYSTEM_TOYBOX_CMD_USLEEP
bool "usleep"
default y
config SYSTEM_TOYBOX_CMD_UUDECODE
bool "uudecode"
default y
config SYSTEM_TOYBOX_CMD_UUENCODE
bool "uuencode"
default y
config SYSTEM_TOYBOX_CMD_UUIDGEN
bool "uuidgen"
default y
config SYSTEM_TOYBOX_CMD_WC
bool "wc"
default y
config SYSTEM_TOYBOX_CMD_WHICH
bool "which"
default y
config SYSTEM_TOYBOX_CMD_WHOAMI
bool "whoami"
default y
config SYSTEM_TOYBOX_CMD_XARGS
bool "xargs"
default y
config SYSTEM_TOYBOX_CMD_XXD
bool "xxd"
default y
config SYSTEM_TOYBOX_CMD_YES
bool "yes"
default y
config SYSTEM_TOYBOX_CMD_ZCAT
bool "zcat"
default y

25
system/toybox/Make.defs Normal file
View file

@ -0,0 +1,25 @@
############################################################################
# apps/system/toybox/Make.defs
#
# 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.
#
############################################################################
ifneq ($(CONFIG_SYSTEM_TOYBOX),)
CONFIGURED_APPS += $(APPDIR)/system/toybox
endif

197
system/toybox/Makefile Normal file
View file

@ -0,0 +1,197 @@
############################################################################
# apps/system/toybox/Makefile
#
# 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.
#
############################################################################
include $(APPDIR)/Make.defs
ifneq ($(CONFIG_SYSTEM_TOYBOX),)
# Download (fetch a pinned release, don't vendor)
TOYBOX_VERSION := $(patsubst "%",%,$(strip $(CONFIG_SYSTEM_TOYBOX_VERSION)))
TOYBOX_URL := $(patsubst "%",%,$(strip $(CONFIG_SYSTEM_TOYBOX_URL)))
TOYBOX_TARBALL := $(TOYBOX_VERSION).tar.gz
TOYBOX_UNPACKNAME := toybox
WD := ${shell echo $(CURDIR) | sed -e 's/ /\\ /g'}
CURL ?= curl -L -o
UNPACK ?= tar xzf
ifeq ($(CONFIG_HOST_MACOS),y)
PATCH ?= gpatch
else
PATCH ?= patch
endif
TOYBOX_PATCHES := $(sort $(wildcard $(WD)$(DELIM)patch$(DELIM)*.patch))
$(TOYBOX_TARBALL):
$(ECHO_BEGIN)"Downloading: $(TOYBOX_TARBALL)"
$(Q) $(CURL) $(TOYBOX_TARBALL) $(TOYBOX_URL)/$(TOYBOX_TARBALL)
$(ECHO_END)
$(TOYBOX_UNPACKNAME): $(TOYBOX_TARBALL)
$(ECHO_BEGIN)"Unpacking: $(TOYBOX_TARBALL) -> $(TOYBOX_UNPACKNAME)"
$(Q) $(UNPACK) $(TOYBOX_TARBALL)
$(Q) rm -rf $(TOYBOX_UNPACKNAME)
$(Q) mv toybox-$(TOYBOX_VERSION) $(TOYBOX_UNPACKNAME)
$(ECHO_END)
# Release tarballs preserve their original commit timestamps (long
# before "now"). Any file a patch doesn't touch keeps that old mtime,
# which can be *older* than .o/.a files left over from a previous
# build of a different version/config -- make's mtime-based dependency
# tracking then considers those stale objects still up to date and
# silently keeps the old compiled code. Touching everything to the
# current time up front avoids that regardless of patch coverage.
$(Q) find $(TOYBOX_UNPACKNAME) -exec touch {} +
$(Q) $(foreach p, $(TOYBOX_PATCHES), \
echo "Applying: $(notdir $(p))"; \
$(PATCH) -s -N -p1 -d $(TOYBOX_UNPACKNAME) -i $(p) || exit 1;)
$(Q) touch $(TOYBOX_UNPACKNAME)
# Regenerating headers is its own target, depending on $(TOPDIR)/.config as
# well as the unpacked source: unlike download/unpack/patch (genuinely
# one-time -- nothing above depends on *what* is selected, only that some
# version is unpacked), which applets end up in generated/toyfiles.txt (and
# so CSRCS) is a direct function of the "Toybox commands" Kconfig menu.
# Without this as a separate, .config-dependent target, toggling a command
# in menuconfig would have no effect: $(TOYBOX_UNPACKNAME) already exists,
# so its rule (and this regeneration) would never run again.
$(TOYBOX_UNPACKNAME)/generated/toyfiles.txt: $(TOYBOX_UNPACKNAME) $(TOPDIR)$(DELIM).config
# Which applets to build is a Kconfig question (see Kconfig.commands,
# "Toybox commands" submenu), not a separately-maintained list: pull
# every enabled CONFIG_SYSTEM_TOYBOX_CMD_<NAME> straight out of NuttX's
# own .config and translate it to the CONFIG_<NAME>=y form Toybox's own
# genconfig.sh expects (via KCONFIG_ALLCONFIG, same mechanism upstream
# uses for scripts/android_miniconfig). The CMD_ segment (see
# Kconfig.commands) is what keeps this from also matching this
# directory's other SYSTEM_TOYBOX_* options (VERSION, BUILTIN_BRIDGE,
# ...), which aren't applets and aren't valid Toybox Kconfig symbols.
$(Q) sed -n 's/^CONFIG_SYSTEM_TOYBOX_CMD_\([A-Z0-9_]*\)=y$$/CONFIG_\1=y/p' \
$(TOPDIR)$(DELIM).config > $(WD)$(DELIM).toybox_allconfig
$(Q) echo CONFIG_TOYBOX_FLOAT=y >> $(WD)$(DELIM).toybox_allconfig
$(Q) echo CONFIG_TOYBOX_HELP=y >> $(WD)$(DELIM).toybox_allconfig
$(Q) echo CONFIG_TOYBOX_HELP_DASHDASH=y >> $(WD)$(DELIM).toybox_allconfig
$(Q) echo CONFIG_TOYBOX_NORECURSE=y >> $(WD)$(DELIM).toybox_allconfig
# Targets without CONFIG_ARCH_HAVE_FORK (e.g. flat/nommu builds like
# stm32f4discovery) have no real fork(), only vfork() -- tell Toybox's
# own Kconfig via the same TOYBOX_FORCE_NOMMU it already offers nommu
# Linux builds for, or xfork()'s call to fork() is left dangling at
# link time on targets that never defined the symbol.
ifneq ($(CONFIG_ARCH_HAVE_FORK),y)
$(Q) echo CONFIG_TOYBOX_FORCE_NOMMU=y >> $(WD)$(DELIM).toybox_allconfig
endif
$(ECHO_BEGIN)"Generating Toybox headers for NuttX ($(TOYBOX_VERSION))"
$(Q) cd $(TOYBOX_UNPACKNAME) && \
CC="$(HOSTCC)" HOSTCC="$(HOSTCC)" CROSS_COMPILE= \
KCONFIG_ALLCONFIG=$(WD)$(DELIM).toybox_allconfig \
bash scripts/genconfig.sh -n
# scripts/make.sh *appends* its "FILES=..." build recipe to
# generated/build.sh instead of overwriting it (upstream only expects
# this to run once per checkout); since we deliberately re-run it
# every time the "Toybox commands" selection changes, remove the
# previous run's block first or the awk extraction below picks up
# stale (possibly no longer selected) applets from it, not the
# current one.
$(Q) rm -f $(TOYBOX_UNPACKNAME)$(DELIM)generated$(DELIM)build.sh
$(Q) cd $(TOYBOX_UNPACKNAME) && \
CC="$(HOSTCC)" HOSTCC="$(HOSTCC)" CROSS_COMPILE= NOBUILD=1 \
NUTTX_TOYBOX_MULTIPLEX_NAME=toybox_multiplex \
bash scripts/make.sh
$(Q) awk '/^FILES="/{sub(/^FILES="/,""); if ($$0!="") print; f=1; next} \
f && /^"$$/{f=0; next} f' $(TOYBOX_UNPACKNAME)/generated/build.sh | \
tr ' ' '\n' | grep -v '^$$' | grep -v '^main\.c$$' | sort -u \
> $(TOYBOX_UNPACKNAME)/generated/toyfiles.txt
$(ECHO_END)
ifeq ($(wildcard $(TOYBOX_UNPACKNAME)/.git),)
context:: $(TOYBOX_UNPACKNAME)/generated/toyfiles.txt
endif
# App registration
MODULE = $(CONFIG_SYSTEM_TOYBOX)
PROGNAME = toybox
PRIORITY = $(CONFIG_SYSTEM_TOYBOX_PRIORITY)
STACKSIZE = $(CONFIG_SYSTEM_TOYBOX_STACKSIZE)
MAINSRC = $(TOYBOX_UNPACKNAME)/toybox_entry.c
# Sources: our glue + upstream lib/*.c + whatever's enabled in the
# "Toybox commands" Kconfig menu, discovered from toybox's own
# generated/build.sh so enabling more applets is "select it in
# menuconfig", not "edit CSRCS by hand".
CSRCS += $(TOYBOX_UNPACKNAME)/nuttx_bridge.c
CSRCS += $(TOYBOX_UNPACKNAME)/main.c
# All of lib/*.c, same as upstream's own scripts/make.sh ($BUILD lib/*.c
# $FILES ...) -- toybox's lib helpers call across files in ways that make
# curating a subset fragile (a missing lib/foo.c shows up as a link error
# for whatever helper it happened to provide, not a hint about foo.c).
CSRCS += $(wildcard $(TOYBOX_UNPACKNAME)/lib/*.c)
CSRCS += $(shell test -f $(TOYBOX_UNPACKNAME)/generated/toyfiles.txt && \
sed "s|^|$(TOYBOX_UNPACKNAME)/|" $(TOYBOX_UNPACKNAME)/generated/toyfiles.txt)
# ── Include paths / compat shim ───────────────────────────────────────────
CFLAGS += ${INCDIR_PREFIX}$(WD)$(DELIM)$(TOYBOX_UNPACKNAME)
CFLAGS += ${INCDIR_PREFIX}$(WD)$(DELIM)$(TOYBOX_UNPACKNAME)$(DELIM)generated
CFLAGS += ${INCDIR_PREFIX}$(WD)$(DELIM)$(TOYBOX_UNPACKNAME)$(DELIM)lib
CFLAGS += -include $(WD)$(DELIM)compat_nuttx.h
CFLAGS += -D__linux__
CFLAGS += -D__NuttX__
CFLAGS += -DTOYBOX_VERSION='"$(TOYBOX_VERSION)-nuttx"'
# Upstream Toybox coding conventions NuttX's stricter default warning set
# (-Wall -Wshadow -Wstrict-prototypes, see arch/sim/src/sim/Toolchain.defs
# and equivalents) flags throughout the tree, on any platform -- reusing a
# scratch variable name in a narrower scope, "char" as an array index
# (values used this way are always small/non-negative in practice), and
# "void foo()" instead of "void foo(void)". None of these are NuttX
# portability issues, so they're silenced here rather than the alternative
# of patching dozens of unrelated upstream files to work around pedantic
# warnings this port doesn't otherwise need to touch.
CFLAGS += -Wno-shadow
CFLAGS += -Wno-char-subscripts
CFLAGS += -Wno-strict-prototypes
# chmod.c's -v/-c output builds a message in toybuf while also formatting
# a mode string into toybuf+64 (same object, disjoint region -- the message
# text is always well under 64 bytes) and passing that second pointer as a
# %s argument to sprintf()'s first: -Wrestrict can't see the offset is
# always big enough to avoid the actual overlap sprintf's restrict-qualified
# parameters forbid, so it flags the pattern rather than a real one.
CFLAGS += -Wno-restrict
ifneq ($(CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE),)
CFLAGS += -DCONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE=1
endif
include $(APPDIR)/Application.mk
ifeq ($(wildcard $(TOYBOX_UNPACKNAME)/.git),)
distclean::
$(call DELDIR, $(TOYBOX_UNPACKNAME))
$(call DELFILE, $(TOYBOX_TARBALL))
endif
endif # CONFIG_SYSTEM_TOYBOX

View file

@ -0,0 +1,83 @@
/****************************************************************************
* apps/system/toybox/compat_nuttx.h
*
* 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.
*
****************************************************************************/
/* Force-included ahead of every Toybox translation unit (../Makefile:
* CFLAGS += -include compat_nuttx.h), same as e.g. apps/crypto/mbedtls does
* for its own NuttX shim. Toybox's own portability layer (lib/portability.h)
* already branches on __linux__/__APPLE__/__FreeBSD__/etc; this file is only
* for the handful of gaps that don't fit that model cleanly enough to carry
* as a source patch instead -- prefer a fix under ../patch/ when there's a
* clear single call site to patch (see ../patch/ for the existing ones).
*
* Earlier iterations of this port carried a large set of workarounds (a
* synthetic fd table, #define open/fstatat/fdopendir redirections) for VFS
* gaps that no longer reproduce against current NuttX (dirfd()/fdopendir()/
* O_DIRECTORY all work normally). Add fixes here only for problems actually
* observed against the current NuttX tree, not preemptively.
*/
#ifndef __APPS_SYSTEM_TOYBOX_COMPAT_NUTTX_H
#define __APPS_SYSTEM_TOYBOX_COMPAT_NUTTX_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
/* lib/portability.h #define's the bare identifier strncat(...) to a
* poison name (catching accidental unsafe calls from Toybox's own code --
* see the comment there), unconditionally on every platform. NuttX's own
* <string.h> textually defines a function literally named strncat as part
* of its __builtin_strncat() optimization (BUILTIN_FUNCTION(strncat) ...),
* which the poison macro corrupts if <string.h> is first parsed *after*
* that macro is active -- pre-including it here, before toys.h reaches
* portability.h, makes NuttX's own header guard skip it the second time
* around instead.
*/
#include <string.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* toy_list[0]'s NEWTOY entry -- the multiplexer's own self-entry -- is
* renamed from "toybox" to "toybox_multiplex" by scripts/make.sh (see
* ../patch/0002-*.patch) to avoid a symbol collision: NEWTOY's name##_main
* convention would otherwise declare a "void toybox_main(void)" in every
* Toybox translation unit (via toys.h's default NEWTOY macro), conflicting
* with the "int toybox_main(int, char **)" NuttX's sys/types.h forward-
* declares everywhere once CONFIG_INIT_ENTRYPOINT="toybox_main" is set --
* that's our actual task entry point, toybox_entry.c.
*
* The rename leaves two macros unresolved that would normally come from
* generated/help.h and generated/flags.h (both driven by Config.in's
* "config TOYBOX" stanza, which still says "toybox", not the renamed
* identifier). The multiplexer entry has TOYFLAG_NOHELP and opts==0 (no
* option string), so both are safe to stub directly instead of aliasing
* through to the real (still-"toybox"-named) generated macros.
*/
#define HELP_toybox_multiplex "\0"
#define OPTSTR_toybox_multiplex 0
#endif /* __APPS_SYSTEM_TOYBOX_COMPAT_NUTTX_H */

View file

@ -0,0 +1,433 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 12:39:01 +0000
Subject: [PATCH 1/7] portability: NuttX statfs/utmpx/RLIMIT_*/syscall/
mount-list/swap.h fixes
NuttX's struct statfs has no f_frsize member (fragment size == block
size there, same as the existing __APPLE__/__FreeBSD__/__OpenBSD__
case, but block size comes from f_bsize instead of f_iosize).
__has_include(<utmpx.h>) sees the host's glibc utmpx.h on the sim
target (nothing stops it being found on the host include path), but
including it fails without __pid_t already being typedef'd by an
internal glibc header NuttX's own sys/types.h never pulls in. NuttX
doesn't implement the utmp/utmpx accounting APIs at all, so always
take the stub branch instead. Same story for <mntent.h> (used to
enumerate mounted filesystems): it resolves to the host's glibc
header, whose own nested includes conflict with NuttX's FILE type.
NuttX has no /etc/mtab or mntent(3) to read anyway -- it exposes
mount info to NSH's df/mount commands through a procfs file in its
own format instead (see apps/nshlib/nsh_mntcmds.c) -- so give NuttX
its own xgetmountlist() that reports no mounted filesystems for now;
teaching it to parse that procfs file is future work.
IUTF8 (Linux tty flag for UTF-8 input mode) was only given a fallback
definition on the *BSD branch of an unrelated #ifdef (which include
of <pty.h> vs <termios.h> to use); NuttX takes the <pty.h> branch (it
has its own, unlike *BSD) but still doesn't define IUTF8. Move the
fallback out to apply regardless of which branch was taken.
NuttX's sys/resource.h implements most RLIMIT_* resources (used by
toys/posix/ulimit.c) but leaves a few slots in its 0..RLIM_NLIMITS-1
range reserved/unused where Linux has resources it doesn't track
per-process (RSS, SIGPENDING, MSGQUEUE, NPROC). Fill those slots the
same way this file already does for RLIMIT_RTTIME on old build
environments, rather than leaving them undeclared.
NuttX's sim target pulls in enough of glibc's headers to define
__GLIBC__ (and __linux__, which our own build sets explicitly -- see
../Makefile), but has neither Linux's raw SYS_timer_create/
SYS_timer_settime/SYS_renameat2 syscall numbers nor the BLKGETSIZE64
ioctl. It does have real timer_create()/timer_settime() natively
(just not the RENAME_EXCHANGE rename or block-device-size-by-ioctl
behavior), so exclude __NuttX__ from those Linux/glibc branches:
timer_create()/timer_settime() then resolve to NuttX's own straight,
and rename_exchange()/get_block_device_size() fall through to their
existing "unsupported" returns (ENOSYS / 0) other unsupported
platforms already use.
NuttX has no swap subsystem at all, on any target -- sys/swap.h isn't
part of its own include tree. That went unnoticed on the sim target
specifically: nothing here calls into it, but the header still
resolved and parsed fine because sim builds with the host's own
compiler, which finds the host's real glibc sys/swap.h on its system
include path. A real cross-compiled target (arm-none-eabi-gcc, no
host headers to fall back on) has no such header at all, so guard
this one include specifically rather than assuming __linux__ implies
every Linux-only header exists.
Two more of the same kind, both only caught by cross-compiling for a
real target (arm-none-eabi-gcc / stm32f4discovery) instead of building
sim against the host's own headers: sys/personality.h doesn't exist on
NuttX either, so take the same stub personality()/PER_LINUX32 path
already used for non-Linux platforms; and NuttX has no extended-
attribute support at all (no getxattr/setxattr/listxattr family, no
sys/xattr.h), so xattr_get()/xattr_set()/etc get a NuttX stub branch
that fails with ENOSYS, which their only caller (cp_xattr() in
toys/posix/cp.c) already treats as "nothing to copy, skip".
toys.h itself unconditionally includes <sys/ttydefaults.h>, which
NuttX doesn't have either. Of what it provides, only the CTRL() macro
is actually used anywhere in this tree (toys/net/microcom.c), so
define that one directly for NuttX instead of the whole header.
_PATH_DEFPATH (glibc's <paths.h>, also pulled in unconditionally by
toys.h) is the safe default $PATH lib/env.c's reset_env() sets for
su/login. NuttX has no <paths.h>; fall back to CONFIG_PATH_INITIAL
(the one directory NuttX's own boot code puts on PATH) when set, or
plain "/bin" otherwise, rather than requiring a config option this
file has no other reason to depend on.
toys/posix/ulimit.c declares and calls prlimit(2) directly, expecting
the OS to provide it the way glibc does (a Linux syscall since
2.6.36). NuttX has no equivalent and no notion of inspecting or
changing a *different* task's rlimits at all -- add a NuttX prlimit()
that forwards to getrlimit()/setrlimit() for pid 0 or the caller's own
pid, and fails with ESRCH otherwise (same as querying a nonexistent
process would on Linux).
toys/posix/time.c calls wait4(2) (waitpid() plus the reaped child's
rusage in one call), another BSD/Linux syscall NuttX doesn't have --
add one that calls waitpid() then getrusage(RUSAGE_CHILDREN, ...),
exact for a single outstanding child (the only case anything in this
tree needs, since NuttX's getrusage() can't target a specific pid).
diff --git a/lib/portability.c b/lib/portability.c
index 6acb0ec..5a3dcfd 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -93,6 +93,19 @@ struct mtab_list *xgetmountlist(char *path)
return mtlist;
}
+#elif defined(__NuttX__)
+
+// NuttX has no /etc/mtab or glibc mntent(3) API to read it with. NuttX
+// itself exposes mounted-filesystem info to NSH's own df/mount commands
+// through a procfs file (CONFIG_NSH_PROC_MOUNTPOINT "/fs/mount", see
+// apps/nshlib/nsh_mntcmds.c) in a NuttX-specific format; teaching this to
+// parse that is future work -- for now report no mounted filesystems
+// rather than fail to build.
+struct mtab_list *xgetmountlist(char *path)
+{
+ return 0;
+}
+
#else
#include <mntent.h>
@@ -313,6 +326,71 @@ ssize_t xattr_fset(int fd, const char* name,
return fsetxattr(fd, name, value, size, 0, flags);
}
+#elif defined(__NuttX__)
+
+// NuttX has no extended-attribute support: no getxattr/setxattr/listxattr
+// family, no <sys/xattr.h>. Every caller here (cp_xattr() in toys/posix/
+// cp.c, tar.c) already treats a negative return as "nothing to copy" and
+// moves on, so stub out to that rather than declaring the feature absent
+// at the call site.
+
+ssize_t xattr_get(const char *path, const char *name, void *value, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_lget(const char *path, const char *name, void *value, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_fget(int fd, const char *name, void *value, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_list(const char *path, char *list, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_llist(const char *path, char *list, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_flist(int fd, char *list, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_set(const char* path, const char* name,
+ const void* value, size_t size, int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_lset(const char* path, const char* name,
+ const void* value, size_t size, int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+ssize_t xattr_fset(int fd, const char* name,
+ const void* value, size_t size, int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
#elif !defined(__FreeBSD__) && !defined(__OpenBSD__)
ssize_t xattr_get(const char *path, const char *name, void *value, size_t size)
@@ -597,7 +675,7 @@ int get_block_device_size(int fd, unsigned long long* size)
}
return 0;
}
-#elif defined(__linux__)
+#elif defined(__linux__) && !defined(__NuttX__)
int get_block_device_size(int fd, unsigned long long* size)
{
return (ioctl(fd, BLKGETSIZE64, size) >= 0);
@@ -705,7 +783,9 @@ int timer_settime(timer_t t, int flags, struct itimerspec *new, void *old)
// glibc requires -lrt for linux syscalls, which pulls in libgcc_eh.a for
// static linking, and gcc 9.3 leaks pthread calls from that breaking the build
// These are both just linux syscalls: wrap them ourselves
-#elif defined(__GLIBC__)
+//
+// (see the matching #elif in portability.h for why __NuttX__ is excluded)
+#elif defined(__GLIBC__) && !defined(__NuttX__)
int timer_create_wrap(clockid_t c, struct sigevent *se, timer_t *t)
{
// convert overengineered structure to what kernel actually uses
@@ -736,10 +816,58 @@ int timer_settime_wrap(timer_t t, int flags, struct itimerspec *val,
// Atomically swap two files
int rename_exchange(char *file1, char *file2)
{
-#if defined(__linux__)
+// NuttX has no Linux SYS_renameat2/RENAME_EXCHANGE equivalent.
+#if defined(__linux__) && !defined(__NuttX__)
// 2 is RENAME_EXCHANGE
return syscall(SYS_renameat2, AT_FDCWD, file1, AT_FDCWD, file2, 2);
#else
return ENOSYS;
#endif
}
+
+#ifdef __NuttX__
+// prlimit(2) is a Linux syscall (2.6.36+) for getting/setting another
+// process's rlimits; toys/posix/ulimit.c declares and calls it directly,
+// expecting the OS to provide it the way glibc does. NuttX has no
+// equivalent -- it has no notion of inspecting or changing a *different*
+// task's rlimits at all, only getrlimit()/setrlimit() for the caller's
+// own. Forward to those for pid 0 (the getrlimit/setrlimit convention for
+// "caller") or the caller's own pid; anything else fails with ESRCH,
+// same as it would against a nonexistent process on Linux.
+int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
+ struct rlimit *old_limit)
+{
+ if (pid != 0 && pid != getpid())
+ {
+ errno = ESRCH;
+ return -1;
+ }
+
+ if (old_limit && getrlimit(resource, old_limit))
+ {
+ return -1;
+ }
+
+ if (new_limit && setrlimit(resource, new_limit))
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+// wait4(2) (used by toys/posix/time.c) is waitpid() plus the reaped
+// child's struct rusage in one call; NuttX has waitpid() and getrusage()
+// separately but not the combined BSD/Linux call. NuttX's getrusage()
+// only supports RUSAGE_SELF/RUSAGE_CHILDREN (not a specific pid), so this
+// is only exact when the caller has a single outstanding child -- true
+// for "time cmd", the only caller in this tree.
+pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage)
+{
+ pid_t ret = waitpid(pid, status, options);
+
+ if (ret > 0 && rusage) getrusage(RUSAGE_CHILDREN, rusage);
+
+ return ret;
+}
+#endif
diff --git a/lib/portability.h b/lib/portability.h
index e87b919..9d3ce42 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -77,6 +77,40 @@
#define RLIMIT_RTTIME 15
#endif
+// glibc's <paths.h> (pulled in unconditionally by toys.h) defines
+// _PATH_DEFPATH, the safe default $PATH lib/env.c's reset_env() sets for
+// e.g. su/login. NuttX has no <paths.h> at all -- the closest equivalent
+// it has is CONFIG_PATH_INITIAL, the single directory its own boot code
+// puts on PATH (see boards/*/configs/*/defconfig), but that's only
+// defined when CONFIG_LIBC_ENVPATH is set, so fall back to "/bin" (what
+// every current board's defconfig actually uses for it) rather than
+// depending on a config option this file has no other reason to require.
+#if defined(__NuttX__) && !defined(_PATH_DEFPATH)
+#ifdef CONFIG_PATH_INITIAL
+#define _PATH_DEFPATH CONFIG_PATH_INITIAL
+#else
+#define _PATH_DEFPATH "/bin"
+#endif
+#endif
+
+// NuttX's sys/resource.h implements most RLIMIT_* resources but leaves a
+// few reserved/unused slots in its 0..RLIM_NLIMITS-1 range where Linux has
+// resources it doesn't track per-process. Filling those slots (rather than
+// picking numbers outside NuttX's range) keeps getrlimit()/setrlimit()
+// calls for them safe no-ops instead of out-of-bounds accesses.
+#ifndef RLIMIT_RSS
+#define RLIMIT_RSS 9
+#endif
+#ifndef RLIMIT_MSGQUEUE
+#define RLIMIT_MSGQUEUE 12
+#endif
+#ifndef RLIMIT_NPROC
+#define RLIMIT_NPROC 13
+#endif
+#ifndef RLIMIT_SIGPENDING
+#define RLIMIT_SIGPENDING 11
+#endif
+
// Introduced in Linux 3.1 (Commit 982d816581eee 2011)
#ifndef SEEK_DATA
#define SEEK_DATA 3
@@ -171,8 +205,16 @@ void *memmem(const void *haystack, size_t haystack_length,
#include <sys/mount.h>
#ifdef __linux__
#include <sys/statfs.h>
-#include <sys/swap.h>
#include <sys/sysinfo.h>
+// NuttX has no swap subsystem at all -- sys/swap.h doesn't exist in its own
+// include tree. On the "sim" target this went unnoticed: it built against
+// the host's own glibc headers, which happen to provide a real sys/swap.h
+// nothing here actually calls. A real cross-compiled target has no such
+// fallback, so guard this one specifically instead of assuming __linux__
+// means every Linux-only header is present.
+#ifndef __NuttX__
+#include <sys/swap.h>
+#endif
#endif
#ifdef __APPLE__
@@ -181,22 +223,34 @@ void *memmem(const void *haystack, size_t haystack_length,
#include <pty.h>
#else
#include <termios.h>
+#endif
+
+// IUTF8 (Linux tty flag for UTF-8 input mode) is missing not just on
+// *BSD but also NuttX, which has its own <pty.h> without it -- keep this
+// check unconditional rather than only reachable from the *BSD branch.
#ifndef IUTF8
#define IUTF8 0
#endif
-#endif
-#ifdef __linux__
+// NuttX has no personality(2) syscall or sys/personality.h -- take the
+// same stub path as every other non-Linux port instead (only toys/other/
+// linux32.c calls this, which isn't part of NuttX's curated applet set).
+#if defined(__linux__) && !defined(__NuttX__)
#include <sys/personality.h>
#else
#define PER_LINUX32 0
int personality(int);
#endif
-#if defined(__APPLE__) || defined(__linux__)
+#if defined(__APPLE__) || defined(__linux__) || defined(__NuttX__)
// Linux and macOS has both have getxattr and friends in <sys/xattr.h>, but
-// they aren't compatible.
+// they aren't compatible. NuttX has neither the header nor any extended-
+// attribute support at all -- xattr_*() below stub out to ENOSYS for it
+// instead (see lib/portability.c), which cp_xattr() (toys/posix/cp.c)
+// already treats as "nothing to copy, skip".
+#ifndef __NuttX__
#include <sys/xattr.h>
+#endif
ssize_t xattr_get(const char *, const char *, void *, size_t);
ssize_t xattr_lget(const char *, const char *, void *, size_t);
ssize_t xattr_fget(int fd, const char *, void *, size_t);
@@ -220,6 +274,10 @@ int posix_fallocate(int, off_t, off_t);
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static inline long statfs_bsize(struct statfs *sf) { return sf->f_iosize; }
static inline long statfs_frsize(struct statfs *sf) { return sf->f_bsize; }
+#elif defined(__NuttX__)
+// NuttX's struct statfs has no f_frsize (fragment size == block size there).
+static inline long statfs_bsize(struct statfs *sf) { return sf->f_bsize; }
+static inline long statfs_frsize(struct statfs *sf) { return sf->f_bsize; }
#else
static inline long statfs_bsize(struct statfs *sf) { return sf->f_bsize; }
static inline long statfs_frsize(struct statfs *sf) { return sf->f_frsize; }
@@ -231,7 +289,13 @@ static inline long statfs_frsize(struct statfs *sf) { return sf->f_frsize; }
#if __has_include(<shadow.h>)
#include <shadow.h>
#endif
-#if __has_include(<utmpx.h>)
+// On the NuttX "sim" target, __has_include() sees the *host's* glibc
+// utmpx.h (nothing stops it being found on the host include path), but
+// including it fails: glibc's utmpx.h expects __pid_t to already be
+// typedef'd by an internal header NuttX's own (different) sys/types.h
+// never pulls in. NuttX doesn't implement the utmp/utmpx accounting APIs
+// at all, so always take the stub branch below instead.
+#if !defined(__NuttX__) && __has_include(<utmpx.h>)
#include <utmpx.h>
#else
struct utmpx {int ut_type;};
@@ -377,7 +441,12 @@ struct itimerspec {
};
int timer_create(clock_t c, struct sigevent *se, timer_t *t);
int timer_settime(timer_t t, int flags, struct itimerspec *new, void *old);
-#elif defined(__GLIBC__)
+// NuttX's sim target pulls in enough of glibc's headers to define
+// __GLIBC__, but doesn't have the raw Linux SYS_timer_create/
+// SYS_timer_settime syscall numbers this branch needs -- it does have its
+// own real timer_create()/timer_settime(), so skip the wrapper and let
+// those declarations (from NuttX's own <time.h>) apply directly instead.
+#elif defined(__GLIBC__) && !defined(__NuttX__)
// Work around a glibc bug that interacts badly with a gcc bug.
#include <syscall.h>
#include <signal.h>
diff --git a/toys.h b/toys.h
index f7de91b..4f144d6 100644
--- a/toys.h
+++ b/toys.h
@@ -66,7 +66,15 @@
// Non-posix headers
#include <sys/ioctl.h>
#include <sys/syscall.h>
+// NuttX has no <sys/ttydefaults.h> at all. Of everything it normally
+// provides, only CTRL() is actually used anywhere in this tree (by
+// toys/net/microcom.c) -- define that one directly instead, rather than
+// pulling in a header NuttX doesn't have for macros nothing here needs.
+#ifdef __NuttX__
+#define CTRL(x) ((x) & 037)
+#else
#include <sys/ttydefaults.h>
+#endif
#include "lib/lib.h"
#include "lib/lsm.h"

View file

@ -0,0 +1,111 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Mon, 27 Jul 2026 22:49:38 -0300
Subject: [PATCH 2/7] main, make.sh: rename toybox_main() to
toybox_dispatcher(), skip main() on NuttX
NuttX's apps/system/toybox/Makefile builds its own NuttX task entry
point (toybox_entry.c) as MAINSRC, and Application.mk renames that
file's main() to toybox_main() to register it as the PROGNAME=toybox
builtin. That collides two ways with this file's own multiplexer:
- toy_list[]'s own self-entry (name "toybox") token-pastes to a
function called toybox_main via NEWTOY's name##_main convention
(scripts/make.sh emits that entry unconditionally, it isn't config-
gated). Renaming just the definition doesn't fix this: the mere
*declaration* toys.h emits for it (from generated/newtoys.h, in
every Toybox source file) already conflicts with NuttX's own
sys/types.h forward declaration of toybox_main(int, char**), which
appears everywhere once CONFIG_INIT_ENTRYPOINT="toybox_main" is
set. So make.sh now renames that one generated entry (gated on
$NUTTX_TOYBOX_MULTIPLEX_NAME, a no-op for anyone else running it) to
toybox_multiplex, and main.c grows a toybox_multiplex_main() that
just redispatches -- toy_list[0] stays the sentinel other code
depends on existing, just spelled differently.
- the real multiplexer *function* (the one toybox_entry.c actually
wants to call) is renamed toybox_dispatcher() -- the name
toybox_entry.c already expects -- and upstream's own process-per-
command main() is guarded out on NuttX, where it would be dead code.
diff --git a/main.c b/main.c
index aa6a52c..da1a301 100644
--- a/main.c
+++ b/main.c
@@ -300,7 +300,11 @@ void toy_exec(char *argv[])
// Multiplexer command, first argument is command to run, rest are args to that.
// If first argument starts with - output list of command install paths.
-void toybox_main(void)
+//
+// Named toybox_dispatcher() rather than toybox_main() because NuttX's
+// Application.mk builds our NuttX task entry point (toybox_entry.c's
+// main()) into a function literally called toybox_main().
+void toybox_dispatcher(void)
{
char *toy_paths[] = {"usr/", "bin/", "sbin/", 0}, *s = toys.argv[1];
int i, len = 0;
@@ -341,6 +345,23 @@ void toybox_main(void)
xputc('\n');
}
+// toy_list[0]'s underlying function, per scripts/make.sh's NuttX-specific
+// rename of that entry (see the comment there). Only reachable if
+// something explicitly looks up and runs the multiplexer's own self-entry
+// (toy_find() prefix-matching a name against toy_list[0].name); redispatch
+// same as a fresh invocation would.
+void toybox_multiplex_main(void)
+{
+ toybox_dispatcher();
+}
+
+// On NuttX this multi-call binary is entered through toybox_entry.c's
+// main(), which dispatches into toy_exec_which()/toybox_dispatcher()
+// directly for each command instead of going through a fork/exec'd
+// process per invocation. Upstream's own process-per-command main() would
+// still link and work, but is dead code in that model, so it's compiled
+// out to avoid a duplicate/unreachable entry point.
+#ifndef __NuttX__
int main(int argc, char *argv[])
{
// don't segfault if our environment is crazy
@@ -359,7 +380,7 @@ int main(int argc, char *argv[])
if (CFG_TOYBOX) {
// Call the multiplexer with argv[] as its arguments so it can toy_find()
toys.argv = argv-1;
- toybox_main();
+ toybox_dispatcher();
} else {
// single command built standalone with no multiplexer is first list entry
toy_singleinit(toy_list, argv);
@@ -368,3 +389,4 @@ int main(int argc, char *argv[])
xexit();
}
+#endif /* !__NuttX__ */
diff --git a/scripts/make.sh b/scripts/make.sh
index 59dabb8..19e4e77 100755
--- a/scripts/make.sh
+++ b/scripts/make.sh
@@ -122,6 +122,22 @@ COMPFLAGS="$(compflags)" && eval "$COMPFLAGS" &&
| sort -s -k 1,1 | $SED 's/[^ ]* //'
} > "$GENDIR"/newtoys.h || exit 1
+# NuttX: the multiplexer's own toy_list self-entry (name "toybox") token-
+# pastes to a function called toybox_main via the NEWTOY(name,...) ->
+# name##_main convention. NuttX's apps/system/toybox/Makefile also needs a
+# real toybox_main(int argc, char **argv) as its PROGNAME=toybox task entry
+# point (toybox_entry.c), and NuttX's sys/types.h forward-declares that
+# exact name/signature everywhere CONFIG_INIT_ENTRYPOINT="toybox_main" is
+# set. Two incompatible declarations of the same symbol name don't compile.
+# Rename just this one entry's underlying name/function token so it stops
+# colliding; toy_list[0] stays the multiplexer sentinel other code depends
+# on existing, just spelled differently (nothing user-visible depends on
+# self-referential "toybox"-named symlink resolution, which is what that
+# name would otherwise be used for).
+[ -n "$NUTTX_TOYBOX_MULTIPLEX_NAME" ] &&
+ $SED -i "s/^USE_TOYBOX(NEWTOY(toybox,/USE_TOYBOX(NEWTOY($NUTTX_TOYBOX_MULTIPLEX_NAME,/" \
+ "$GENDIR"/newtoys.h
+
# Rebuild config.h from .config
$SED -En $KCONFIG_CONFIG > "$GENDIR"/config.h \
-e 's/^# CONFIG_(.*) is not set.*/#define CFG_\1 0\n#define USE_\1(...)\n#define SKIP_\1(...) __VA_ARGS__/p;t' \

View file

@ -0,0 +1,80 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 15:22:14 +0000
Subject: [PATCH 3/10] ls, dirtree: be explicit about O_DIRECTORY and reject
negative dirfd
Four related fixes, all the same root cause: NuttX's VFS open() needs
an explicit O_DIRECTORY hint to correctly open a directory path -- a
bare O_RDONLY/0 (or no access-mode flag at all) isn't enough and
fails with ENXIO -- unlike Linux, which is happy either way, so these
are safe (and arguably more correct, since they cross-check against
the directory-ness already known at each call site) on both:
- lib/dirtree.c's dirtree_handle_callback(): the openat() that opens
each directory found during a DIRTREE_RECURSE walk (dirtree_read()
with recursion -- e.g. "find") was missing O_DIRECTORY entirely.
- toys/posix/ls.c has *two* separate directory-recursion paths (its
own listfiles() loop for the general case, plus a single-
subdirectory fast path) -- both were missing O_DIRECTORY, breaking
"ls -R" specifically (the fast path only kicks in without -R).
- lib/dirtree.c's dirtree_recurse() also gets a dirfd>=0 guard (not
just != -1) before calling fdopendir(xdup(node->dirfd)), since a
failed upstream open() can hand back other negative fds here.
diff --git a/lib/dirtree.c b/lib/dirtree.c
index 5634759..89ab3a1 100644
--- a/lib/dirtree.c
+++ b/lib/dirtree.c
@@ -126,7 +126,12 @@ static struct dirtree *dirtree_handle_callback(struct dirtree *new,
if (S_ISDIR(new->st.st_mode) && (flags & df)) {
// TODO: check openat returned fd for errors... and do what about it?
- if (*new->name) fd = openat(dirtree_parentfd(new), new->name, O_CLOEXEC);
+ // O_DIRECTORY: we already know (S_ISDIR above) this should be a
+ // directory; NuttX's VFS open() needs the explicit hint to route a
+ // directory path correctly (see toys/posix/ls.c's other O_DIRECTORY
+ // open for the same reason), and it's a no-op cross-check elsewhere.
+ if (*new->name)
+ fd = openat(dirtree_parentfd(new), new->name, O_CLOEXEC|O_DIRECTORY);
if (flags&DIRTREE_BREADTH) {
new->again |= DIRTREE_BREADTH;
if ((DIRTREE_ABORT & dirtree_recurse(new, 0, fd, flags)) ||
@@ -157,8 +162,10 @@ int dirtree_recurse(struct dirtree *node,
DIR *dir = 0;
// fdopendir() doesn't support AT_FDCWD, closedir() closes fd from opendir()
+ // node->dirfd >= 0 (not just != -1): a failed open() upstream can leave
+ // negative fds other than -1 flowing through here on NuttX.
if (AT_FDCWD == (node->dirfd = dirfd)) dir = opendir(".");
- else if (node->dirfd != -1) dir = fdopendir(xdup(node->dirfd));
+ else if (node->dirfd >= 0) dir = fdopendir(xdup(node->dirfd));
if (!dir) {
if (!(flags & DIRTREE_SHUTUP)) {
diff --git a/toys/posix/ls.c b/toys/posix/ls.c
index 7a8dfd2..a94ab30 100644
--- a/toys/posix/ls.c
+++ b/toys/posix/ls.c
@@ -364,7 +364,9 @@ static void listfiles(int dirfd, struct dirtree *indir)
// In this case only show dirname/total header when given -R.
dt = indir->child;
if (dt && S_ISDIR(dt->st.st_mode) && !dt->next && !(FLAG(d)||FLAG(R))) {
- listfiles(open(dt->name, 0), TT.singledir = dt);
+ // Explicit O_DIRECTORY: some VFS implementations (e.g. NuttX) don't
+ // accept a plain O_RDONLY open() of a directory path here.
+ listfiles(open(dt->name, O_RDONLY|O_DIRECTORY), TT.singledir = dt);
return;
}
@@ -572,8 +574,9 @@ static void listfiles(int dirfd, struct dirtree *indir)
if (FLAG(d) || !S_ISDIR(sort[ul]->st.st_mode)) continue;
// Recurse into dirs if at top of the tree or given -R
+ // (O_DIRECTORY: see the other openat()/open() calls in this file.)
if (!indir->parent || (FLAG(R) && dirtree_notdotdot(sort[ul])))
- listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]);
+ listfiles(openat(dirfd, sort[ul]->name, O_DIRECTORY), sort[ul]);
free((void *)sort[ul]->extra);
}
free(sort);

View file

@ -0,0 +1,202 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Mon, 27 Jul 2026 22:41:49 -0300
Subject: [PATCH 4/7] add toybox_entry.c: NuttX task entry point (one-shot +
REPL)
See the file's own header comment for the design. In short: dispatches
into the real multiplexer (toybox_dispatcher(), see main.c) either
once per task invocation ("toybox ls" from NSH or a script) or in a
loop reading one line at a time from stdin (interactive use /
CONFIG_INIT_ENTRYPOINT="toybox_main"), trapping the xexit() at the end
of each command via the same toys.rebound sigsetjmp/siglongjmp
mechanism toys/pending/sh.c uses to run builtins in-process.
diff --git a/toybox_entry.c b/toybox_entry.c
new file mode 100644
index 0000000..f6df9d6
--- /dev/null
+++ b/toybox_entry.c
@@ -0,0 +1,182 @@
+/* toybox_entry.c - NuttX task entry point for the Toybox multi-call binary.
+ *
+ * Upstream Toybox is a process-per-command model: each applet invocation is
+ * its own fork/exec'd process, and "the multiplexer" (toybox_dispatcher(),
+ * see main.c) is just the argv[0]=="toybox" case of that same model. NuttX
+ * builds this as a single task instead (PROGNAME=toybox in ../Makefile), so
+ * this file provides two entry paths into that one task:
+ *
+ * - "toybox <cmd> <args...>": argc>=2, forwarded straight into
+ * toybox_dispatcher() exactly as upstream's own main() would, so one
+ * invocation runs one command and exits -- this is the path used when
+ * something else (NSH, a script) runs "toybox ls" as a builtin.
+ *
+ * - "toybox" with no arguments: an interactive prompt that reads one line
+ * at a time and dispatches each as its own toybox_dispatcher() call, so
+ * the task survives from one command to the next. This is the path used
+ * when CONFIG_INIT_ENTRYPOINT="toybox_main" makes Toybox the system's
+ * only shell (see boards/sim/sim/sim/configs/toybox/defconfig).
+ *
+ * Running each line through toybox_dispatcher() rather than reaching for
+ * toy_find()/toy_main() by hand reuses upstream's own command lookup,
+ * symlink handling and "no such command" reporting instead of duplicating
+ * it, and gets the bare "toybox" applet listing for free.
+ *
+ * Each dispatched command can end in xexit(), which upstream expects to
+ * terminate the process. Since here it must instead return control to this
+ * loop, every dispatch is wrapped in the same toys.rebound sigsetjmp/
+ * siglongjmp trap toybox's own (pending) shell implementation
+ * (toys/pending/sh.c) uses to run builtins in-process -- see xexit() and
+ * _xexit() in lib/xwrap.c.
+ */
+
+#include <nuttx/config.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "toys.h"
+
+#ifdef CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE
+/* nuttx_bridge.c: run a NuttX builtin application by name when the typed
+ * command isn't a Toybox applet. Returns 0 if it found and ran (regardless
+ * of that command's own exit status) and a negative errno-style value if no
+ * such builtin exists.
+ */
+int nuttx_builtin_exec(int argc, char *argv[]);
+#endif
+
+#define MAX_LINE_ARGS 32
+
+/* Bytes of "struct toy_context" that precede the "rebound" field -- the
+ * portion toy_init()/toy_singleinit() reset per-command. Mirrors the
+ * save/restore trick in toys/pending/sh.c's run_command().
+ */
+#define TOYS_SAVE_SIZE offsetof(struct toy_context, rebound)
+
+/* Run one already-tokenized command line through the real multiplexer,
+ * trapping the xexit() at the end so it returns here instead of ending the
+ * task. Returns the command's exit value.
+ */
+static int dispatch(int argc, char *argv[])
+{
+ char saved[TOYS_SAVE_SIZE];
+ sigjmp_buf rebound, *prebound = toys.rebound;
+ char *dispatch_argv[MAX_LINE_ARGS + 2];
+ int i, exitval;
+
+ dispatch_argv[0] = "toybox";
+ for (i = 0; i < argc && i < MAX_LINE_ARGS; i++) dispatch_argv[i + 1] = argv[i];
+ dispatch_argv[i + 1] = NULL;
+
+ memcpy(saved, &toys, TOYS_SAVE_SIZE);
+ memset(&toys, 0, TOYS_SAVE_SIZE);
+
+ /* Also reset GLOBALS()-declared per-applet state ("this", see
+ * generated/globals.h): normally fresh BSS on Linux/Android's
+ * process-per-command model, but this task dispatches many
+ * commands in place, so a stale nonzero value left by an earlier
+ * applet at the same union offset (e.g. cp_main()'s
+ * "if (!TT.callback) TT.callback = cp_node") would otherwise
+ * silently survive into the next command.
+ */
+ memset(&this, 0, sizeof(this));
+
+ if (!sigsetjmp(rebound, 1))
+ {
+ toys.rebound = &rebound;
+ toys.argv = dispatch_argv;
+ toybox_dispatcher();
+
+ /* toybox_dispatcher() always ends in xexit(), which siglongjmp()s
+ * back into the branch below because toys.rebound is set above --
+ * this point should be unreachable.
+ */
+ xexit();
+ }
+
+ exitval = toys.exitval;
+ toys.rebound = prebound;
+ clearerr(stdout);
+ memcpy(&toys, saved, TOYS_SAVE_SIZE);
+
+ return exitval;
+}
+
+/* Split a line on whitespace in place. No quoting support -- this is a
+ * bare-bones command prompt, not toysh. argv[] is left NULL-terminated
+ * (room for one more slot beyond the returned argc) since
+ * nuttx_builtin_exec()'s exec_builtin()/posix_spawn() need that.
+ */
+static int tokenize(char *line, char *argv[], int max)
+{
+ int argc = 0;
+ char *tok = strtok(line, " \t");
+
+ while (tok && argc < max - 1)
+ {
+ argv[argc++] = tok;
+ tok = strtok(NULL, " \t");
+ }
+
+ argv[argc] = NULL;
+
+ return argc;
+}
+
+static int run_line(char *line)
+{
+ char *argv[MAX_LINE_ARGS];
+ int argc = tokenize(line, argv, MAX_LINE_ARGS);
+
+ if (argc == 0) return 0;
+
+ /* Bare "help"/"?" (no arguments of their own): list every Toybox applet,
+ * same as running "toybox" with no arguments would. A "help <command>"
+ * still reaches Toybox's own CONFIG_HELP applet, if enabled, via the
+ * normal dispatch path below.
+ */
+ if (argc == 1 && (!strcmp(argv[0], "help") || !strcmp(argv[0], "?")))
+ return dispatch(0, argv);
+
+#ifdef CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE
+ /* Toybox applets always take precedence over a same-named NuttX builtin. */
+ if (!toy_find(argv[0]) && nuttx_builtin_exec(argc, argv) == 0) return 0;
+#endif
+
+ return dispatch(argc, argv);
+}
+
+int main(int argc, FAR char *argv[])
+{
+ /* "toybox <cmd> <args...>": one-shot, behaves like upstream's own main(). */
+ if (argc >= 2)
+ {
+ toys.argv = argv;
+ toys.optc = argc;
+ toybox_dispatcher();
+ return toys.exitval;
+ }
+
+ /* No arguments: interactive prompt. */
+ printf("\nToybox " TOYBOX_VERSION " for NuttX -- 'help' lists commands, "
+ "'exit' leaves the prompt.\n");
+
+ for (; ; )
+ {
+ char line[256];
+
+ printf("toybox> ");
+ fflush(stdout);
+
+ if (!fgets(line, sizeof(line), stdin)) break;
+ line[strcspn(line, "\r\n")] = '\0';
+
+ if (!strcmp(line, "exit") || !strcmp(line, "quit")) break;
+
+ run_line(line);
+ }
+
+ printf("\n");
+ return 0;
+}

View file

@ -0,0 +1,57 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Mon, 27 Jul 2026 22:41:49 -0300
Subject: [PATCH 5/7] add nuttx_bridge.c: run NuttX builtin apps from the
Toybox prompt
Lets commands that aren't Toybox applets (nsh, ps, hello, ...) run
from the Toybox prompt via NuttX's apps/builtin exec_builtin()/
builtin_isavail(), gated by CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE. Only
needed in this direction: NSH already runs "toybox <cmd>" as a
builtin via its own CONFIG_NSH_BUILTIN_APPS, no code needed here for
that.
diff --git a/nuttx_bridge.c b/nuttx_bridge.c
new file mode 100644
index 0000000..eb59d83
--- /dev/null
+++ b/nuttx_bridge.c
@@ -0,0 +1,38 @@
+/* nuttx_bridge.c - run NuttX builtin applications from the Toybox prompt.
+ *
+ * When CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE is set, toybox_entry.c falls
+ * back to nuttx_builtin_exec() for any command that isn't a Toybox applet.
+ * This is what lets "nsh", "ps", "hello" and other NuttX builtin
+ * applications (apps/builtin) run from the Toybox prompt when Toybox is the
+ * system's only shell (CONFIG_INIT_ENTRYPOINT="toybox_main").
+ *
+ * The reverse direction (running "toybox ls" from NSH) needs no code here:
+ * once Toybox itself is a registered NuttX builtin (MODULE=CONFIG_SYSTEM_
+ * TOYBOX in ../Makefile), CONFIG_NSH_BUILTIN_APPS already covers it.
+ */
+
+#include <nuttx/config.h>
+
+#include <sys/wait.h>
+#include <stdio.h>
+
+#include "builtin/builtin.h"
+
+int nuttx_builtin_exec(int argc, char *argv[])
+{
+ pid_t pid;
+ int status;
+
+ if (builtin_isavail(argv[0]) < 0) return -1;
+
+ pid = exec_builtin(argv[0], argv, NULL);
+ if (pid < 0)
+ {
+ perror(argv[0]);
+ return 0;
+ }
+
+ waitpid(pid, &status, 0);
+
+ return 0;
+}

View file

@ -0,0 +1,61 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 16:00:00 +0000
Subject: [PATCH 6/6] taskset: use NuttX's native sched_setaffinity()/
sched_getaffinity()
NuttX has no Linux-style __NR_sched_setaffinity/__NR_sched_getaffinity
syscall numbers to build the raw syscall() wrappers this file
otherwise uses, but it does provide real sched_setaffinity()/
sched_getaffinity() (sched.h) with the exact same signature -- these
are needed unconditionally since nproc_main() (same file, same config
group) calls sched_getaffinity() too, not just taskset's own code.
Also drop the (void *) casts at both call sites: on non-SMP NuttX (no
CONFIG_SMP), sched_getaffinity()/sched_setaffinity() are macros that
dereference this argument directly, which doesn't compile through
void * -- unsigned long * converts to cpu_set_t * either way, so the
cast was only ever needed for the raw syscall() path this replaces.
diff --git a/toys/other/taskset.c b/toys/other/taskset.c
index e45b100..e8bb7b0 100644
--- a/toys/other/taskset.c
+++ b/toys/other/taskset.c
@@ -38,10 +38,16 @@ config TASKSET
#include "toys.h"
// mask is array of long which makes layout a bit weird on big endian systems
+//
+// NuttX has no Linux-style __NR_* syscall numbers to build these from, but
+// it does provide real sched_setaffinity()/sched_getaffinity() (see
+// sched.h) with the same signature these wrap, so just use those directly.
+#ifndef __NuttX__
#define sched_setaffinity(pid, size, cpuset) \
syscall(__NR_sched_setaffinity, (pid_t)pid, (size_t)size, (void *)cpuset)
#define sched_getaffinity(pid, size, cpuset) \
syscall(__NR_sched_getaffinity, (pid_t)pid, (size_t)size, (void *)cpuset)
+#endif
#define TOYBUF_BITS (8*sizeof(toybuf))
@@ -62,7 +68,10 @@ static void do_taskset(pid_t pid)
// loop through twice to display before/after affinity masks
for (i=0; ; i++) {
if (FLAG(p) || !toys.optc) {
- if (-1 == sched_getaffinity(pid, sizeof(toybuf), (void *)mask))
+ // No (void *) cast: on non-SMP NuttX, sched_getaffinity() is a macro
+ // that dereferences this argument, which doesn't compile through
+ // void *; unsigned long * converts to cpu_set_t * either way.
+ if (-1 == sched_getaffinity(pid, sizeof(toybuf), mask))
perror_exit(failed, "get", pid);
if (toys.optc)
@@ -165,7 +174,7 @@ static void do_taskset(pid_t pid)
}
}
- if (-1 == sched_setaffinity(pid, sizeof(toybuf), (void *)mask))
+ if (-1 == sched_setaffinity(pid, sizeof(toybuf), mask))
perror_exit(failed, "set", pid);
}
}

View file

@ -0,0 +1,35 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 16:30:00 +0000
Subject: [PATCH 7/7] time: only report rusage fields NuttX actually tracks
NuttX's struct rusage only has ru_utime/ru_stime/ru_maxrss -- no page
fault, I/O block or context switch counters, unlike Linux. Those only
feed "time -v"'s extra verbose output (the default real/user/sys
timing already only used ru_utime/ru_stime, which NuttX has); report
just Max RSS there instead of the full Linux field set.
diff --git a/toys/posix/time.c b/toys/posix/time.c
index 078aca5..01609af 100644
--- a/toys/posix/time.c
+++ b/toys/posix/time.c
@@ -48,12 +48,19 @@ void time_main(void)
idx += sprintf(toybuf+idx, "%s%s%c%lld.%0*d\n", label[ii],
FLAG(v) ? " time (s):" : "", tab, sec[ii],
6>>!toys.optflags, nano[ii]);
+ // NuttX's struct rusage only tracks ru_utime/ru_stime/ru_maxrss -- no
+ // page fault, I/O block or context switch counters -- so -v only reports
+ // what's actually available there instead of the full Linux field set.
+#ifdef __NuttX__
+ if (FLAG(v)) idx += sprintf(toybuf+idx, "Max RSS (KiB): %ld\n", ru.ru_maxrss);
+#else
if (FLAG(v)) idx += sprintf(toybuf+idx,
"Max RSS (KiB): %ld\nMajor faults: %ld\n"
"Minor faults: %ld\nFile system inputs: %ld\nFile system outputs: %ld\n"
"Voluntary context switches: %ld\nInvoluntary context switches: %ld\n",
ru.ru_maxrss, ru.ru_majflt, ru.ru_minflt, ru.ru_inblock,
ru.ru_oublock, ru.ru_nvcsw, ru.ru_nivcsw);
+#endif
writeall(2, toybuf, idx);
toys.exitval = WIFEXITED(stat) ? WEXITSTATUS(stat) : WTERMSIG(stat);

View file

@ -0,0 +1,71 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 17:00:00 +0000
Subject: [PATCH 8/8] env: use NuttX's native setenv/unsetenv/clearenv
NuttX has no swappable "char **environ" global to reassign (it's a
get_environ_ptr() macro backed by task-owned storage) -- three direct
"environ = ..." assignments in xclearenv()/xsetenv()'s own malloc/
free/toys.envc-tracked reimplementation don't compile there. NuttX
does provide real setenv()/unsetenv()/clearenv() that manage that
storage safely, so use those directly on NuttX instead of replacing
what environ points to.
diff --git a/lib/env.c b/lib/env.c
index d47e8d2..e281820 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -18,6 +18,44 @@ long environ_bytes(void)
return bytes;
}
+#ifdef __NuttX__
+
+// NuttX doesn't expose a swappable "char **environ" global -- it's a
+// get_environ_ptr() macro backed by task-owned storage, and nothing outside
+// NuttX's own libc can replace what it points to by assignment, which is
+// what toybox's own malloc/free/toys.envc-tracked implementation below
+// needs to do. NuttX does provide real setenv()/unsetenv()/clearenv() that
+// manage that storage safely, so use those instead here.
+//
+// Neither of toybox's own callers (env.c, xargs.c) use xsetenv()'s return
+// value, so returning `name` rather than a freshly allocated "name=value"
+// string (which nothing takes ownership of here, unlike the toys.envc path)
+// doesn't change their behavior.
+
+void xclearenv(void)
+{
+ clearenv();
+}
+
+char *xsetenv(char *name, char *val)
+{
+ char *eq = strchr(name, '=');
+
+ if (!eq) {
+ if (val) setenv(name, val, 1);
+ else unsetenv(name);
+ } else {
+ if (val) error_exit("xsetenv %s to %s", name, val);
+ *eq = 0;
+ setenv(name, eq+1, 1);
+ *eq = '=';
+ }
+
+ return name;
+}
+
+#else
+
// This will clear the inherited environment if called first thing.
// Use this instead of envc so we keep track of what needs to be freed.
void xclearenv(void)
@@ -83,6 +121,8 @@ char *xsetenv(char *name, char *val)
return environ[i] = new;
}
+#endif /* __NuttX__ */
+
void xunsetenv(char *name)
{
if (strchr(name, '=')) error_exit("xunsetenv %s name has =", name);

View file

@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 17:15:00 +0000
Subject: [PATCH 9/9] hostname: skip DNS lookup path on NuttX (no resolver)
NuttX has no gethostbyname()/h_errno without CONFIG_NET wired up with
a resolver, which "hostname -d"/"-f" need to turn the local hostname
into a fully-qualified domain name. Everything else this command does
(read/set the hostname, -s, -b) doesn't touch DNS at all, so only
guard out the -d/-f lookup path instead of the whole command.
diff --git a/toys/lsb/hostname.c b/toys/lsb/hostname.c
index 3c15b86..4a07fb9 100644
--- a/toys/lsb/hostname.c
+++ b/toys/lsb/hostname.c
@@ -60,11 +60,17 @@ void hostname_main(void)
}
// We only do the DNS lookup for -d and -f.
+#ifdef __NuttX__
+ // NuttX has no gethostbyname()/h_errno (no resolver without CONFIG_NET
+ // wired up); -s/-b/set/plain "hostname" don't need DNS at all, only this.
+ if (FLAG(d) || FLAG(f)) error_exit("-d/-f need a resolver, not available");
+#else
if (FLAG(d) || FLAG(f)) {
if (!(h = gethostbyname(toybuf)))
error_exit("gethostbyname: %s", hstrerror(h_errno));
snprintf(toybuf, sizeof(toybuf), "%s", h->h_name);
}
+#endif
dot = toybuf+strcspn(toybuf, ".");
if (FLAG(s)) *dot = 0;
xputs(FLAG(d) ? dot+1 : toybuf);

View file

@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 17:45:00 +0000
Subject: [PATCH 10/10] find, cp: two more missing O_DIRECTORY opens
Same root cause as patch 0003 (NuttX's open() needs an explicit
O_DIRECTORY hint for directory paths):
- find.c's "-empty" test opens the candidate directory to check
whether readdir() finds anything past "." and "..".
- cp.c's recursive copy (-r/-a/-p) opens the just-created destination
directory back up to verify it really is one before recursing into
it (see the comment already there about the mkdir()/open() race).
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index 1f1816e..7a66f6c 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -234,7 +234,8 @@ static int cp_node(struct dirtree *try)
// that what we open _is_ a directory rather than something else.
if (!mkdirat(cfd, catch, try->st.st_mode | 0200) || errno == EEXIST)
- if (-1 != (try->extra = openat(cfd, catch, O_NOFOLLOW)))
+ // O_DIRECTORY: see the openat()/open() calls in toys/posix/ls.c.
+ if (-1 != (try->extra = openat(cfd, catch, O_NOFOLLOW|O_DIRECTORY)))
if (!fstat(try->extra, &st2) && S_ISDIR(st2.st_mode))
return DIRTREE_COMEAGAIN | DIRTREE_SYMFOLLOW*FLAG(L);
diff --git a/toys/posix/find.c b/toys/posix/find.c
index ac14881..a99095b 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -334,7 +334,9 @@ static int do_find(struct dirtree *new)
if (check) {
// Alas neither st_size nor st_blocks reliably show an empty directory
if (S_ISDIR(new->st.st_mode)) {
- int fd = openat(dirtree_parentfd(new), new->name, O_RDONLY);
+ // O_DIRECTORY: see the openat()/open() calls in toys/posix/ls.c.
+ int fd = openat(dirtree_parentfd(new), new->name,
+ O_RDONLY|O_DIRECTORY);
DIR *dfd = fdopendir(fd);
struct dirent *de = (void *)1;
if (dfd) {

View file

@ -0,0 +1,64 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Sat, 18 Jul 2026 18:00:00 +0000
Subject: [PATCH 11/11] uname: NuttX's struct utsname isn't laid out like
glibc's
uname_main() finds each field by computing "toybuf + sizeof(sysname)
* i", which assumes struct utsname is 6 same-size char[] fields
(sysname/nodename/release/version/machine/domainname) back to back --
true for glibc, not for NuttX, whose struct utsname has only 5 fields
(no domainname) of three different sizes (SYS_NAMELEN, HOST_NAME_MAX,
VERSION_NAMELEN). The offset arithmetic silently read garbage/wrong
data across field boundaries instead of failing to build. Index the
real fields by name on NuttX instead of relying on the memory layout.
diff --git a/toys/posix/uname.c b/toys/posix/uname.c
index dfa5762..bf86773 100644
--- a/toys/posix/uname.c
+++ b/toys/posix/uname.c
@@ -37,6 +37,36 @@ config UNAME
#define FORCE_FLAGS
#include "toys.h"
+#ifdef __NuttX__
+void uname_main(void)
+{
+ // NuttX's struct utsname has 5 fields, not Linux's 6 (no domainname),
+ // and they're not all the same size (nodename is HOST_NAME_MAX, version
+ // is VERSION_NAMELEN, the rest are SYS_NAMELEN) -- the "toybuf + uniform
+ // field size * i" trick the non-NuttX version below uses only works for
+ // glibc/Linux's actual (uniform, 6-field) layout, so index the real
+ // fields directly here instead.
+ struct utsname un;
+ char *field[5], *c;
+ int i, needspace = 0;
+
+ uname(&un);
+ field[0] = un.sysname;
+ field[1] = un.nodename;
+ field[2] = un.release;
+ field[3] = un.version;
+ field[4] = un.machine;
+
+ if (!toys.optflags) toys.optflags = FLAG_s;
+ for (i=0; i<6; i++) if (toys.optflags & ((1<<i)|FLAG_a)) {
+ c = (i==5) ? " Toybox"+!needspace : field[i];
+ if (i<5 && needspace++) xputc(' ');
+ xputsn(c);
+ }
+ if (FLAG(p)) xputsn(" unknown"+!needspace);
+ xputc('\n');
+}
+#else
void uname_main(void)
{
int i, needspace = 0;
@@ -55,6 +85,7 @@ void uname_main(void)
if (FLAG(p)) xputsn(" unknown"+!needspace);
xputc('\n');
}
+#endif
void arch_main(void)
{

View file

@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 29 Jul 2026 17:35:00 +0000
Subject: [PATCH 12/13] portability: drop local wait4(), now provided by
NuttX
wait4() is now a real NuttX libc function (libs/libc/unistd/lib_wait4.c),
built the same way this shim was (waitpid() + getrusage()), but visible
from any program instead of duplicated per-app. Keeping this local copy
would make both definitions link into the final image and fail with
"multiple definition of wait4".
diff --git a/lib/portability.c b/lib/portability.c
index 3a1f9c2..7e2b881 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -855,19 +855,4 @@ int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
return 0;
}
-
-// wait4(2) (used by toys/posix/time.c) is waitpid() plus the reaped
-// child's struct rusage in one call; NuttX has waitpid() and getrusage()
-// separately but not the combined BSD/Linux call. NuttX's getrusage()
-// only supports RUSAGE_SELF/RUSAGE_CHILDREN (not a specific pid), so this
-// is only exact when the caller has a single outstanding child -- true
-// for "time cmd", the only caller in this tree.
-pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage)
-{
- pid_t ret = waitpid(pid, status, options);
-
- if (ret > 0 && rusage) getrusage(RUSAGE_CHILDREN, rusage);
-
- return ret;
-}
#endif

View file

@ -0,0 +1,38 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 29 Jul 2026 17:36:00 +0000
Subject: [PATCH 13/13] portability: avoid CODE macro collision with NuttX
NuttX's <nuttx/compiler.h> (pulled in transitively by <sys/types.h>,
included above this point) #defines the bare identifier CODE as a
memory-space qualifier used pervasively across NuttX's own headers
(mirroring FAR/DATA). That collides with this typedef's name, silently
macro-expanding it away and leaving "extern CODE prioritynames[]..." as
an implicit-int declaration instead of a compile error, since CODE is
object-like and expands to nothing on most archs.
toys/pending/syslogd.c is the only user of prioritynames/facilitynames/
CODE in this tree, and it isn't built here, so skip the declaration
entirely on NuttX rather than renaming around a macro nothing needs.
diff --git a/lib/portability.h b/lib/portability.h
index 9c4d271..1f8a3e0 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -385,7 +385,15 @@ extern CODE prioritynames[], facilitynames[];
#endif
-#ifndef SYSLOG_NAMES
+// NuttX's <nuttx/compiler.h> (pulled in transitively by <sys/types.h>,
+// included above) #defines the bare identifier CODE as a memory-space
+// qualifier used pervasively across NuttX's own headers (mirroring FAR/
+// DATA). That collides with this typedef's name, silently macro-expanding
+// it away and leaving "extern CODE prioritynames[]..." as an implicit-int
+// declaration instead of an error, since CODE is object-like and expands
+// to nothing on most archs. toys/pending/syslogd.c is the only user of
+// prioritynames/facilitynames/CODE in this tree, and it isn't built here.
+#if !defined(SYSLOG_NAMES) && !defined(__NuttX__)
typedef struct {char *c_name; int c_val;} CODE;
extern CODE prioritynames[], facilitynames[];
#endif

View file

@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 29 Jul 2026 17:54:00 +0000
Subject: [PATCH 14/14] make.sh: drop toys/pending banner
Our command selection deliberately includes three toys/pending applets
that have no toys/posix equivalent in this Toybox release -- diff,
expr, tr (see Kconfig.commands: CONFIG_SYSTEM_TOYBOX_CMD_{DIFF,EXPR,TR}).
All three build and run fine here; upstream's "pending" label just
means they haven't been promoted out of toys/pending yet, not that
anything is broken for us. Since this is a fixed, known, intentional
part of our command set rather than something menuconfig toggles
around, the every-build red warning banner is just noise -- drop it.
diff --git a/scripts/make.sh b/scripts/make.sh
index a4c8f19..b6d2e57 100755
--- a/scripts/make.sh
+++ b/scripts/make.sh
@@ -100,11 +100,6 @@ TOYFILES="$($SED -n 's/^CONFIG_\([^=]*\)=.*/\1/p' "$KCONFIG_CONFIG" | xargs | t
TOYFILES="main.c $(egrep -l "^USE_($TOYFILES)[(]...TOY[(]" toys/*/*.c | xargs)"
-if [ "${TOYFILES/pending//}" != "$TOYFILES" ]
-then
- echo -e "\n\033[1;31mwarning: using unfinished code from toys/pending\033[0m"
-fi
-
# Write build variables (and set them locally), then append build invocation.
COMPFLAGS="$(compflags)" && eval "$COMPFLAGS" &&
{

View file

@ -0,0 +1,39 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 29 Jul 2026 17:58:00 +0000
Subject: [PATCH 15/15] toybox_entry: add "cd" REPL builtin
"cd" has no Toybox applet of its own -- upstream only implements it as
a shell builtin (toys/pending/sh.c, not built here), because changing
directory only means anything if it happens in the caller's own
process. Our REPL already special-cases "help"/"?" the same way for a
similar reason; add "cd" alongside them instead of falling through to
"Unknown command cd".
diff --git a/toybox_entry.c b/toybox_entry.c
index 2f6a7c1..8d914bb 100644
--- a/toybox_entry.c
+++ b/toybox_entry.c
@@ -121,6 +121,22 @@ static int run_line(char *line)
if (argc == 0) return 0;
+ /* "cd" has no Toybox applet of its own -- upstream only implements it as
+ * a shell builtin (toys/pending/sh.c, not built here) because changing
+ * directory only means anything done in-process; running it through
+ * dispatch() like any other command would still be in-process (see
+ * dispatch()'s comment), but there is no toys/.../cd.c for toy_find()
+ * to locate in the first place. Handle it directly at the REPL level
+ * instead, same as "help"/"?" below.
+ */
+ if (!strcmp(argv[0], "cd"))
+ {
+ const char *dir = (argc >= 2) ? argv[1] : "/";
+
+ if (chdir(dir)) perror("cd");
+ return 0;
+ }
+
/* Bare "help"/"?" (no arguments of their own): list every Toybox applet,
* same as running "toybox" with no arguments would. A "help <command>"
* still reaches Toybox's own CONFIG_HELP applet, if enabled, via the

View file

@ -0,0 +1,60 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:00:00 +0000
Subject: [PATCH 16/23] toys.h, env.c: skip redundant environ extern on NuttX
NuttX's <stdlib.h> #defines environ to get_environ_ptr() (a real,
fully-prototyped function -- there's no plain "char **environ" global
to point at). Toybox's "extern char **environ" then macro-expands to
"extern char **get_environ_ptr()" -- a K&R-style redeclaration ("()"
isn't "(void)") of a function already declared properly elsewhere,
which is exactly what -Wstrict-prototypes warns about. Skip the
redundant redeclaration on NuttX in both spots that had one; nothing
else changes; every ordinary use of "environ" still resolves through
the same macro either way.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
lib/env.c | 4 ++++
toys.h | 10 +++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/env.c b/lib/env.c
index e281820..2cd491a 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -3,7 +3,11 @@
#include "toys.h"
// In libc, populated by start code, used by getenv() and exec() and friends.
+// See toys.h's own environ declaration for why NuttX skips this: it's a
+// get_environ_ptr() macro there, already declared with a full prototype.
+#ifndef __NuttX__
extern char **environ;
+#endif
// Returns the number of bytes taken by the environment variables. For use
// when calculating the maximum bytes of environment+argument data that can
diff --git a/toys.h b/toys.h
index 4f144d6..8673d8f 100644
--- a/toys.h
+++ b/toys.h
@@ -137,7 +137,15 @@ extern struct toy_context {
// Two big temporary buffers: one for use by commands, one for library functions
-extern char **environ, *toybox_version, toybuf[4096], libbuf[4096];
+// NuttX doesn't expose environ as a plain global -- <stdlib.h> #defines it to
+// get_environ_ptr(), already declared there with a full prototype. Redeclaring
+// it here the usual "extern char **environ" way would instead redeclare that
+// function without one (a K&R-style "()" isn't "(void)"), which just warns
+// since the two declarations still agree once macro-expanded.
+#ifndef __NuttX__
+extern char **environ;
+#endif
+extern char *toybox_version, toybuf[4096], libbuf[4096];
#define FLAG(x) (!!(toys.optflags&FLAG_##x)) // Return 1 if flag set, 0 if not
--
2.43.0

View file

@ -0,0 +1,59 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:05:00 +0000
Subject: [PATCH 17/23] net: fall back when getaddrinfo() family isn't
available
getaddrinfo()/freeaddrinfo()/gai_strerror() are only declared in
NuttX's <netdb.h> behind CONFIG_LIBC_NETDB, itself needing CONFIG_NET
-- this toybox port doesn't require a network stack be configured at
all, so boards without one implicitly-declared these (int-returning by
default, silently wrong for the two that aren't) instead of failing to
link, since nothing enabled here currently calls into xgetaddrinfo().
Guard both call sites the same way hostname.c's DNS lookup path
already handles the same gap (see that patch), falling back to a clean
error_exit() instead when the network stack isn't there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
lib/net.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/lib/net.c b/lib/net.c
index 8fe006b..085fd74 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -19,6 +19,11 @@ void xsetsockopt(int fd, int level, int opt, void *val, socklen_t len)
struct addrinfo *xgetaddrinfo(char *host, char *port, int family, int socktype,
int protocol, int flags)
{
+// getaddrinfo()/gai_strerror() are only declared (behind CONFIG_LIBC_NETDB,
+// itself needing CONFIG_NET) on boards that opted into NuttX's network
+// stack -- this toybox port doesn't require one, so fall back the same way
+// hostname.c's DNS lookup path already does when it's missing.
+#if !defined(__NuttX__) || defined(CONFIG_LIBC_NETDB)
struct addrinfo info, *ai;
int rc;
@@ -35,6 +40,9 @@ struct addrinfo *xgetaddrinfo(char *host, char *port, int family, int socktype,
port ? port : "", rc ? gai_strerror(rc) : "not found");
return ai;
+#else
+ error_exit("no network stack");
+#endif
}
static int xconnbind(struct addrinfo *ai_arg, int dobind)
@@ -51,7 +59,9 @@ static int xconnbind(struct addrinfo *ai_arg, int dobind)
else if (!ai->ai_next) perror_exit_raw(dobind ? "bind" : "connect");
close(fd);
}
+#if !defined(__NuttX__) || defined(CONFIG_LIBC_NETDB)
freeaddrinfo(ai_arg);
+#endif
return fd;
}
--
2.43.0

View file

@ -0,0 +1,79 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:10:00 +0000
Subject: [PATCH 18/23] portability: drop dead syscall(2) copy_file_range
fast path on NuttX
sendfile_len()'s fast path calls the raw copy_file_range syscall by
number via syscall(2) when check_copy_file_range() finds
__NR_copy_file_range defined. NuttX never defines that (no
copy_file_range support), so check_copy_file_range() already always
returns 0 there and the fast path is permanently dead code -- but
syscall() itself isn't something NuttX provides at all (no generic
syscall-number trampoline the way Linux has one), so the call was left
as an implicit (int-returning, wrong for this long long-returning
call) declaration instead of a link error only because nothing
currently reaches it at runtime. Skip declaring/calling it outright
for a platform that can never take this path, instead of relying on
that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
lib/portability.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/portability.c b/lib/portability.c
index 8f4bd38..8721f4c 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -708,6 +708,7 @@ static int android_api_level(void)
}
#endif
+#ifndef __NuttX__
static int check_copy_file_range(void)
{
#if defined(__ANDROID__)
@@ -721,13 +722,21 @@ static int check_copy_file_range(void)
return 0;
#endif
}
+#endif
// Return bytes copied from in to out. If bytes <0 copy all of in to out.
// If consumed isn't null, amount read saved there (return is written or error)
long long sendfile_len(int in, int out, long long bytes, long long *consumed)
{
long long total = 0, len, ww;
+ // check_copy_file_range() already always returns 0 here (no
+ // __NR_copy_file_range on NuttX), so the syscall(2)-based fast path below
+ // is permanently dead code on this platform -- and syscall() itself isn't
+ // a thing NuttX provides (no generic syscall-number trampoline), so skip
+ // declaring/calling it entirely instead of just never taking the branch.
+#ifndef __NuttX__
int try_cfr = check_copy_file_range();
+#endif
if (consumed) *consumed = 0;
if (in>=0) while (bytes != total) {
@@ -735,6 +744,7 @@ long long sendfile_len(int in, int out, long long bytes, long long *consumed)
len = bytes-total;
errno = 0;
+#ifndef __NuttX__
if (try_cfr) {
if (bytes<0 || len>(1<<30)) len = (1<<30);
len = syscall(try_cfr, in, 0, out, 0, len, 0);
@@ -743,7 +753,9 @@ long long sendfile_len(int in, int out, long long bytes, long long *consumed)
continue;
}
- } else {
+ } else
+#endif
+ {
if (bytes<0 || len>sizeof(libbuf)) len = sizeof(libbuf);
ww = len = read(in, libbuf, len);
}
--
2.43.0

View file

@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:15:00 +0000
Subject: [PATCH 19/23] xwrap: xchroot() has no NuttX equivalent
NuttX has no per-process filesystem root, so no chroot() syscall to
wrap -- it was left as an implicit (and therefore always wrong, since
chroot() returns int but nothing declared that) declaration instead of
an undefined reference only because nothing built by this port
currently calls xchroot() (tftpd's -r option, in toys/pending, isn't
enabled). Fail cleanly with error_exit() on NuttX instead of relying
on that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
lib/xwrap.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/xwrap.c b/lib/xwrap.c
index fb608d5..589c2d8 100644
--- a/lib/xwrap.c
+++ b/lib/xwrap.c
@@ -707,7 +707,12 @@ void xchdir(char *path)
void xchroot(char *path)
{
+ // NuttX has no per-process filesystem root, so no chroot() to call.
+#ifdef __NuttX__
+ error_exit("chroot: not available");
+#else
if (chroot(path)) error_exit("chroot '%s'", path);
+#endif
xchdir("/");
}
--
2.43.0

View file

@ -0,0 +1,35 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:20:00 +0000
Subject: [PATCH 20/23] toybox_entry: declare toybox_dispatcher() prototype
toybox_dispatcher() (main.c, renamed from toybox_main() by patch 0002)
is only ever called from within main.c itself upstream, so it never
needed a prototype in a shared header -- until this file's dispatch()
became a second, cross-file caller. Give it one here rather than rely
on an implicit declaration matching by accident.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
toybox_entry.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/toybox_entry.c b/toybox_entry.c
index 009dacd..3ed8f92 100644
--- a/toybox_entry.c
+++ b/toybox_entry.c
@@ -37,6 +37,12 @@
#include "toys.h"
+/* main.c: the real multiplexer (renamed from toybox_main(), see patch
+ * 0002) -- only ever called from within main.c itself upstream, so it has
+ * no prototype in a shared header for this file's cross-file call to see.
+ */
+void toybox_dispatcher(void);
+
#ifdef CONFIG_SYSTEM_TOYBOX_BUILTIN_BRIDGE
/* nuttx_bridge.c: run a NuttX builtin application by name when the typed
* command isn't a Toybox applet. Returns 0 if it found and ran (regardless
--
2.43.0

View file

@ -0,0 +1,40 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:25:00 +0000
Subject: [PATCH 21/23] cpio: avoid UB shifting by width of 32-bit off_t
"st.st_size >> 32" assumes off_t is 64 bits, true on Linux/Android but
not universally true on NuttX: without CONFIG_FS_LARGEFILE, off_t is a
32-bit int32_t there, and shifting a 32-bit value by 32 is undefined
behavior (shift count equal to the operand's width). A file can never
exceed 2G in the first place when off_t is 32 bits, so the sizeof()
check makes the shift unreachable there instead of relying on the
compiler's specific (and unspecified-by-the-standard) handling of the
UB case to still come out false.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
toys/posix/cpio.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c
index 321bbfd..d9a57cc 100644
--- a/toys/posix/cpio.c
+++ b/toys/posix/cpio.c
@@ -281,7 +281,13 @@ void cpio_main(void)
if (Rgid>=0) st.st_gid = Rgid;
if (FLAG(no_preserve_owner)) st.st_uid = st.st_gid = 0;
if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) st.st_size = 0;
- if (st.st_size >> 32) perror_msg("skipping >2G file '%s'", name);
+ // Plain ">> 32" is undefined behavior when off_t is 32 bits (e.g. NuttX
+ // without CONFIG_FS_LARGEFILE): shifting by the full width of the
+ // (int-promoted) operand. The sizeof() check makes the shift
+ // unreachable/dead-code-eliminated there instead, where a file can
+ // never exceed 2G in the first place.
+ if ((sizeof(st.st_size)>4) && ((uint64_t)st.st_size>>32))
+ perror_msg("skipping >2G file '%s'", name);
else {
if (FLAG(renumber_inodes)) st.st_ino = ++inode;
if (FLAG(ignore_devno)) st.st_rdev = 0;
--
2.43.0

View file

@ -0,0 +1,31 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:30:00 +0000
Subject: [PATCH 22/23] hostname: don't declare struct hostent when unused
Patch 0009 stubbed out -d/-f's gethostbyname() lookup on NuttX (no
resolver without CONFIG_NET), but left the "struct hostent *h" it read
the result into declared unconditionally, unused on NuttX now that
nothing assigns to it there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
toys/lsb/hostname.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/toys/lsb/hostname.c b/toys/lsb/hostname.c
index 4a07fb9..c56788e 100644
--- a/toys/lsb/hostname.c
+++ b/toys/lsb/hostname.c
@@ -41,7 +41,9 @@ GLOBALS(
void hostname_main(void)
{
char *hostname = toybuf, *dot;
+#ifndef __NuttX__
struct hostent *h;
+#endif
gethostname(toybuf, sizeof(toybuf)-1);
if (TT.F && (hostname = xreadfile(TT.F, 0, 0))) {
--
2.43.0

View file

@ -0,0 +1,42 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Alan C. Assis" <acassis@gmail.com>
Date: Wed, 12 Aug 2026 15:35:00 +0000
Subject: [PATCH 23/23] hash: fix %x format vs uint32_t type mismatch
hash->state.i32[] is "unsigned" (unsigned int), matching "%08x", but
the ternary that conditionally byteswaps it doesn't: bswap_32() returns
uint32_t, which some NuttX arm builds typedef to "unsigned long" rather
than "unsigned int" (arch/arm/include/types.h's __UINT32_TYPE__
fallback). The usual arithmetic conversions then give the whole ?:
expression that wider-looking type even though both branches are the
same 32 bits, so it stops matching "%x" on those targets. An explicit
(unsigned) cast on the result sidesteps whatever uint32_t happens to
alias to.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
lib/hash.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/lib/hash.c b/lib/hash.c
index 2ba4014..2aa33ca 100644
--- a/lib/hash.c
+++ b/lib/hash.c
@@ -383,8 +383,13 @@ void hash_by_name(int fd, char *name, char *result)
if (method>=4) for (i=0; i<digestlen/8; i++)
result += sprintf(result, "%016llx", hash->state.i64[i]);
else for (i=0; i<digestlen/4; i++)
- result += sprintf(result, "%08x",
- !method ? bswap_32(hash->state.i32[i]) : hash->state.i32[i]);
+ // The (unsigned) cast matters on targets (some NuttX/arm builds) where
+ // uint32_t is "unsigned long" rather than "unsigned int": bswap_32()
+ // returns uint32_t, so without it the ?: here has type unsigned long
+ // (usual arithmetic conversions promote to the wider-looking type even
+ // though both are 32 bits), which doesn't match "%x"'s unsigned int.
+ result += sprintf(result, "%08x", (unsigned)
+ (!method ? bswap_32(hash->state.i32[i]) : hash->state.i32[i]));
// Wipe variables. Cryptographer paranoia. Avoid "optimizing" out memset
// by looping on a volatile pointer.
for (pp = (void *)hash; pp-(unsigned *)hash<sizeof(*hash)/4; pp++) *pp = 0;
--
2.43.0