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