From eabb4bba6b1511b0e2e872dcd5478619596d543a Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Wed, 12 Aug 2026 11:45:51 -0300 Subject: [PATCH] system/toybox: Add toybox a tool box similar to busybox Toybox is the tool box 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 Assisted-by: Claude Sonnet 5 --- system/toybox/.gitignore | 7 + system/toybox/Kconfig | 81 +++ system/toybox/Kconfig.commands | 461 ++++++++++++++++++ system/toybox/Make.defs | 25 + system/toybox/Makefile | 197 ++++++++ system/toybox/compat_nuttx.h | 83 ++++ ...statfs_frsize-for-NuttX-no-f_frsize-.patch | 433 ++++++++++++++++ ...ame-toybox_main-to-toybox_dispatcher.patch | 111 +++++ ...plicit-about-O_DIRECTORY-and-reject-.patch | 80 +++ ....c-NuttX-task-entry-point-one-shot-R.patch | 202 ++++++++ ....c-run-NuttX-builtin-apps-from-the-T.patch | 57 +++ ...X-s-native-sched_setaffinity-sched_g.patch | 61 +++ ...t-rusage-fields-NuttX-actually-track.patch | 35 ++ ...tX-s-native-setenv-unsetenv-clearenv.patch | 71 +++ ...DNS-lookup-path-on-NuttX-no-resolver.patch | 33 ++ ...p-two-more-missing-O_DIRECTORY-opens.patch | 43 ++ ...truct-utsname-isn-t-laid-out-like-gl.patch | 64 +++ ...op-local-wait4-now-provided-by-NuttX.patch | 36 ++ ...void-CODE-macro-collision-with-NuttX.patch | 38 ++ ...014-make.sh-drop-toys-pending-banner.patch | 29 ++ ...015-toybox_entry-add-cd-REPL-builtin.patch | 39 ++ ...ip-redundant-environ-extern-on-NuttX.patch | 60 +++ ...hen-getaddrinfo-family-isn-t-availab.patch | 59 +++ ...p-dead-syscall-2-copy_file_range-fas.patch | 79 +++ ...wrap-xchroot-has-no-NuttX-equivalent.patch | 37 ++ ...-declare-toybox_dispatcher-prototype.patch | 35 ++ ...UB-shifting-by-width-of-32-bit-off_t.patch | 40 ++ ...t-declare-struct-hostent-when-unused.patch | 31 ++ ...x-x-format-vs-uint32_t-type-mismatch.patch | 42 ++ 29 files changed, 2569 insertions(+) create mode 100644 system/toybox/.gitignore create mode 100644 system/toybox/Kconfig create mode 100644 system/toybox/Kconfig.commands create mode 100644 system/toybox/Make.defs create mode 100644 system/toybox/Makefile create mode 100644 system/toybox/compat_nuttx.h create mode 100644 system/toybox/patch/0001-portability-fix-statfs_frsize-for-NuttX-no-f_frsize-.patch create mode 100644 system/toybox/patch/0002-main-make.sh-rename-toybox_main-to-toybox_dispatcher.patch create mode 100644 system/toybox/patch/0003-ls-dirtree-be-explicit-about-O_DIRECTORY-and-reject-.patch create mode 100644 system/toybox/patch/0004-add-toybox_entry.c-NuttX-task-entry-point-one-shot-R.patch create mode 100644 system/toybox/patch/0005-add-nuttx_bridge.c-run-NuttX-builtin-apps-from-the-T.patch create mode 100644 system/toybox/patch/0006-taskset-use-NuttX-s-native-sched_setaffinity-sched_g.patch create mode 100644 system/toybox/patch/0007-time-only-report-rusage-fields-NuttX-actually-track.patch create mode 100644 system/toybox/patch/0008-env-use-NuttX-s-native-setenv-unsetenv-clearenv.patch create mode 100644 system/toybox/patch/0009-hostname-skip-DNS-lookup-path-on-NuttX-no-resolver.patch create mode 100644 system/toybox/patch/0010-find-cp-two-more-missing-O_DIRECTORY-opens.patch create mode 100644 system/toybox/patch/0011-uname-NuttX-s-struct-utsname-isn-t-laid-out-like-gl.patch create mode 100644 system/toybox/patch/0012-portability-drop-local-wait4-now-provided-by-NuttX.patch create mode 100644 system/toybox/patch/0013-portability-avoid-CODE-macro-collision-with-NuttX.patch create mode 100644 system/toybox/patch/0014-make.sh-drop-toys-pending-banner.patch create mode 100644 system/toybox/patch/0015-toybox_entry-add-cd-REPL-builtin.patch create mode 100644 system/toybox/patch/0016-toys.h-env.c-skip-redundant-environ-extern-on-NuttX.patch create mode 100644 system/toybox/patch/0017-net-fall-back-when-getaddrinfo-family-isn-t-availab.patch create mode 100644 system/toybox/patch/0018-portability-drop-dead-syscall-2-copy_file_range-fas.patch create mode 100644 system/toybox/patch/0019-xwrap-xchroot-has-no-NuttX-equivalent.patch create mode 100644 system/toybox/patch/0020-toybox_entry-declare-toybox_dispatcher-prototype.patch create mode 100644 system/toybox/patch/0021-cpio-avoid-UB-shifting-by-width-of-32-bit-off_t.patch create mode 100644 system/toybox/patch/0022-hostname-don-t-declare-struct-hostent-when-unused.patch create mode 100644 system/toybox/patch/0023-hash-fix-x-format-vs-uint32_t-type-mismatch.patch diff --git a/system/toybox/.gitignore b/system/toybox/.gitignore new file mode 100644 index 00000000000..3fa030f7992 --- /dev/null +++ b/system/toybox/.gitignore @@ -0,0 +1,7 @@ +/toybox +/*.tar.gz +/.toybox_allconfig +*.o +.built +.depend +Make.dep diff --git a/system/toybox/Kconfig b/system/toybox/Kconfig new file mode 100644 index 00000000000..d866fa0a4a4 --- /dev/null +++ b/system/toybox/Kconfig @@ -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_=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 diff --git a/system/toybox/Kconfig.commands b/system/toybox/Kconfig.commands new file mode 100644 index 00000000000..6cc64eb0fd4 --- /dev/null +++ b/system/toybox/Kconfig.commands @@ -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_" 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_ 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 diff --git a/system/toybox/Make.defs b/system/toybox/Make.defs new file mode 100644 index 00000000000..5077f808240 --- /dev/null +++ b/system/toybox/Make.defs @@ -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 diff --git a/system/toybox/Makefile b/system/toybox/Makefile new file mode 100644 index 00000000000..74a9b340887 --- /dev/null +++ b/system/toybox/Makefile @@ -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_ straight out of NuttX's + # own .config and translate it to the CONFIG_=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 diff --git a/system/toybox/compat_nuttx.h b/system/toybox/compat_nuttx.h new file mode 100644 index 00000000000..e4efdafbbfa --- /dev/null +++ b/system/toybox/compat_nuttx.h @@ -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 + +/* 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 + * textually defines a function literally named strncat as part + * of its __builtin_strncat() optimization (BUILTIN_FUNCTION(strncat) ...), + * which the poison macro corrupts if 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 + +/**************************************************************************** + * 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 */ diff --git a/system/toybox/patch/0001-portability-fix-statfs_frsize-for-NuttX-no-f_frsize-.patch b/system/toybox/patch/0001-portability-fix-statfs_frsize-for-NuttX-no-f_frsize-.patch new file mode 100644 index 00000000000..69bed08b1d9 --- /dev/null +++ b/system/toybox/patch/0001-portability-fix-statfs_frsize-for-NuttX-no-f_frsize-.patch @@ -0,0 +1,433 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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() 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 (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 vs to use); NuttX takes the 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 , 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 , 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 ; 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 +@@ -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 . 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 (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 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 + #ifdef __linux__ + #include +-#include + #include ++// 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 ++#endif + #endif + + #ifdef __APPLE__ +@@ -181,22 +223,34 @@ void *memmem(const void *haystack, size_t haystack_length, + #include + #else + #include ++#endif ++ ++// IUTF8 (Linux tty flag for UTF-8 input mode) is missing not just on ++// *BSD but also NuttX, which has its own 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 + #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 , 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 ++#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() + #include + #endif +-#if __has_include() ++// 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() + #include + #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 ) apply directly instead. ++#elif defined(__GLIBC__) && !defined(__NuttX__) + // Work around a glibc bug that interacts badly with a gcc bug. + #include + #include +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 + #include ++// NuttX has no 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 ++#endif + + #include "lib/lib.h" + #include "lib/lsm.h" diff --git a/system/toybox/patch/0002-main-make.sh-rename-toybox_main-to-toybox_dispatcher.patch b/system/toybox/patch/0002-main-make.sh-rename-toybox_main-to-toybox_dispatcher.patch new file mode 100644 index 00000000000..69aa41f2748 --- /dev/null +++ b/system/toybox/patch/0002-main-make.sh-rename-toybox_main-to-toybox_dispatcher.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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' \ diff --git a/system/toybox/patch/0003-ls-dirtree-be-explicit-about-O_DIRECTORY-and-reject-.patch b/system/toybox/patch/0003-ls-dirtree-be-explicit-about-O_DIRECTORY-and-reject-.patch new file mode 100644 index 00000000000..91d19475090 --- /dev/null +++ b/system/toybox/patch/0003-ls-dirtree-be-explicit-about-O_DIRECTORY-and-reject-.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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); diff --git a/system/toybox/patch/0004-add-toybox_entry.c-NuttX-task-entry-point-one-shot-R.patch b/system/toybox/patch/0004-add-toybox_entry.c-NuttX-task-entry-point-one-shot-R.patch new file mode 100644 index 00000000000..8b4f206c9eb --- /dev/null +++ b/system/toybox/patch/0004-add-toybox_entry.c-NuttX-task-entry-point-one-shot-R.patch @@ -0,0 +1,202 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 ": 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 ++ ++#include ++#include ++ ++#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 " ++ * 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 ": 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; ++} diff --git a/system/toybox/patch/0005-add-nuttx_bridge.c-run-NuttX-builtin-apps-from-the-T.patch b/system/toybox/patch/0005-add-nuttx_bridge.c-run-NuttX-builtin-apps-from-the-T.patch new file mode 100644 index 00000000000..8e521364794 --- /dev/null +++ b/system/toybox/patch/0005-add-nuttx_bridge.c-run-NuttX-builtin-apps-from-the-T.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 " 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 ++ ++#include ++#include ++ ++#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; ++} diff --git a/system/toybox/patch/0006-taskset-use-NuttX-s-native-sched_setaffinity-sched_g.patch b/system/toybox/patch/0006-taskset-use-NuttX-s-native-sched_setaffinity-sched_g.patch new file mode 100644 index 00000000000..5a1721ee619 --- /dev/null +++ b/system/toybox/patch/0006-taskset-use-NuttX-s-native-sched_setaffinity-sched_g.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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); + } + } diff --git a/system/toybox/patch/0007-time-only-report-rusage-fields-NuttX-actually-track.patch b/system/toybox/patch/0007-time-only-report-rusage-fields-NuttX-actually-track.patch new file mode 100644 index 00000000000..d32b42917c9 --- /dev/null +++ b/system/toybox/patch/0007-time-only-report-rusage-fields-NuttX-actually-track.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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); diff --git a/system/toybox/patch/0008-env-use-NuttX-s-native-setenv-unsetenv-clearenv.patch b/system/toybox/patch/0008-env-use-NuttX-s-native-setenv-unsetenv-clearenv.patch new file mode 100644 index 00000000000..2714584751e --- /dev/null +++ b/system/toybox/patch/0008-env-use-NuttX-s-native-setenv-unsetenv-clearenv.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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); diff --git a/system/toybox/patch/0009-hostname-skip-DNS-lookup-path-on-NuttX-no-resolver.patch b/system/toybox/patch/0009-hostname-skip-DNS-lookup-path-on-NuttX-no-resolver.patch new file mode 100644 index 00000000000..116275cdf25 --- /dev/null +++ b/system/toybox/patch/0009-hostname-skip-DNS-lookup-path-on-NuttX-no-resolver.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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); diff --git a/system/toybox/patch/0010-find-cp-two-more-missing-O_DIRECTORY-opens.patch b/system/toybox/patch/0010-find-cp-two-more-missing-O_DIRECTORY-opens.patch new file mode 100644 index 00000000000..90886420f36 --- /dev/null +++ b/system/toybox/patch/0010-find-cp-two-more-missing-O_DIRECTORY-opens.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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) { diff --git a/system/toybox/patch/0011-uname-NuttX-s-struct-utsname-isn-t-laid-out-like-gl.patch b/system/toybox/patch/0011-uname-NuttX-s-struct-utsname-isn-t-laid-out-like-gl.patch new file mode 100644 index 00000000000..fd2ad327c0f --- /dev/null +++ b/system/toybox/patch/0011-uname-NuttX-s-struct-utsname-isn-t-laid-out-like-gl.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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< +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 diff --git a/system/toybox/patch/0013-portability-avoid-CODE-macro-collision-with-NuttX.patch b/system/toybox/patch/0013-portability-avoid-CODE-macro-collision-with-NuttX.patch new file mode 100644 index 00000000000..c2ad06d0c88 --- /dev/null +++ b/system/toybox/patch/0013-portability-avoid-CODE-macro-collision-with-NuttX.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +Date: Wed, 29 Jul 2026 17:36:00 +0000 +Subject: [PATCH 13/13] portability: avoid CODE macro collision with NuttX + +NuttX's (pulled in transitively by , +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 (pulled in transitively by , ++// 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 diff --git a/system/toybox/patch/0014-make.sh-drop-toys-pending-banner.patch b/system/toybox/patch/0014-make.sh-drop-toys-pending-banner.patch new file mode 100644 index 00000000000..69aff31594b --- /dev/null +++ b/system/toybox/patch/0014-make.sh-drop-toys-pending-banner.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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" && + { diff --git a/system/toybox/patch/0015-toybox_entry-add-cd-REPL-builtin.patch b/system/toybox/patch/0015-toybox_entry-add-cd-REPL-builtin.patch new file mode 100644 index 00000000000..e13e3b5c331 --- /dev/null +++ b/system/toybox/patch/0015-toybox_entry-add-cd-REPL-builtin.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 " + * still reaches Toybox's own CONFIG_HELP applet, if enabled, via the diff --git a/system/toybox/patch/0016-toys.h-env.c-skip-redundant-environ-extern-on-NuttX.patch b/system/toybox/patch/0016-toys.h-env.c-skip-redundant-environ-extern-on-NuttX.patch new file mode 100644 index 00000000000..f08bff9b43b --- /dev/null +++ b/system/toybox/patch/0016-toys.h-env.c-skip-redundant-environ-extern-on-NuttX.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 #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 +--- + 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 -- #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 diff --git a/system/toybox/patch/0017-net-fall-back-when-getaddrinfo-family-isn-t-availab.patch b/system/toybox/patch/0017-net-fall-back-when-getaddrinfo-family-isn-t-availab.patch new file mode 100644 index 00000000000..70f10561633 --- /dev/null +++ b/system/toybox/patch/0017-net-fall-back-when-getaddrinfo-family-isn-t-availab.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 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 +--- + 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 diff --git a/system/toybox/patch/0018-portability-drop-dead-syscall-2-copy_file_range-fas.patch b/system/toybox/patch/0018-portability-drop-dead-syscall-2-copy_file_range-fas.patch new file mode 100644 index 00000000000..3318313b759 --- /dev/null +++ b/system/toybox/patch/0018-portability-drop-dead-syscall-2-copy_file_range-fas.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 +--- + 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 diff --git a/system/toybox/patch/0019-xwrap-xchroot-has-no-NuttX-equivalent.patch b/system/toybox/patch/0019-xwrap-xchroot-has-no-NuttX-equivalent.patch new file mode 100644 index 00000000000..393b3fcbe39 --- /dev/null +++ b/system/toybox/patch/0019-xwrap-xchroot-has-no-NuttX-equivalent.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 +--- + 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 diff --git a/system/toybox/patch/0020-toybox_entry-declare-toybox_dispatcher-prototype.patch b/system/toybox/patch/0020-toybox_entry-declare-toybox_dispatcher-prototype.patch new file mode 100644 index 00000000000..9ebfeae8408 --- /dev/null +++ b/system/toybox/patch/0020-toybox_entry-declare-toybox_dispatcher-prototype.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 +--- + 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 diff --git a/system/toybox/patch/0021-cpio-avoid-UB-shifting-by-width-of-32-bit-off_t.patch b/system/toybox/patch/0021-cpio-avoid-UB-shifting-by-width-of-32-bit-off_t.patch new file mode 100644 index 00000000000..c68b3c29e07 --- /dev/null +++ b/system/toybox/patch/0021-cpio-avoid-UB-shifting-by-width-of-32-bit-off_t.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 +--- + 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 diff --git a/system/toybox/patch/0022-hostname-don-t-declare-struct-hostent-when-unused.patch b/system/toybox/patch/0022-hostname-don-t-declare-struct-hostent-when-unused.patch new file mode 100644 index 00000000000..02758b18973 --- /dev/null +++ b/system/toybox/patch/0022-hostname-don-t-declare-struct-hostent-when-unused.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 +--- + 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 diff --git a/system/toybox/patch/0023-hash-fix-x-format-vs-uint32_t-type-mismatch.patch b/system/toybox/patch/0023-hash-fix-x-format-vs-uint32_t-type-mismatch.patch new file mode 100644 index 00000000000..a7e35a20845 --- /dev/null +++ b/system/toybox/patch/0023-hash-fix-x-format-vs-uint32_t-type-mismatch.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Alan C. Assis" +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 +--- + 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; istate.i64[i]); + else for (i=0; istate.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