From 17a4f455ecc4e5e3dbd73041e8dc995aec7b0861 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 2 Aug 2026 19:05:27 +0200 Subject: [PATCH 1/2] testing, system: do not build tests that call fork() where it is absent. Two places call fork() from code that is compiled unconditionally, which is fine only for as long as every architecture provides it. NuttX is splitting fork() into three primitives -- see apache/nuttx#19562 -- after which ARCH_HAVE_FORK announces POSIX fork() specifically, and is off until an architecture implements it. Both then fail to link. Each is dropped only where ARCH_HAVE_FORK is unset, so builds that have fork() are unaffected. system/libuv: test-fork.c and test-pipe-close-stdout-read-stdin.c are filtered out of the test-*.c glob. Nothing is lost even where they are dropped: every test they define is already excluded from the task list on NuttX by 0001-libuv-port-for-nuttx.patch, which extends the _WIN32 guards around them to __NuttX__ -- all nine fork_* entries and pipe_close_stdout_read_stdin. They are compiled today but never run. testing/ltp: the open_posix_testsuite is filtered through LTP's existing BLACKWORDS mechanism, which already drops tests for absent features and is already conditioned on configuration symbols. The pattern spares vfork() and task_fork(). Where fork() is absent this drops 278 of 1943 test files; those tests exercise fork() and cannot link without it, and they return per architecture as fork() lands. Against today's master this is a no-op: ARCH_HAVE_FORK is set everywhere, so neither filter drops anything. It is part of what lets the NuttX side build against apps master. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- system/libuv/CMakeLists.txt | 7 +++++++ system/libuv/Makefile | 14 +++++++++++++- testing/ltp/CMakeLists.txt | 6 ++++++ testing/ltp/Makefile | 7 +++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/system/libuv/CMakeLists.txt b/system/libuv/CMakeLists.txt index 12cde4fa749..5364fb959f5 100644 --- a/system/libuv/CMakeLists.txt +++ b/system/libuv/CMakeLists.txt @@ -171,6 +171,13 @@ if(CONFIG_LIBUV) ${LIBUV_TEST_DIR}/run-tests.c ${LIBUV_TEST_DIR}/runner.c ${LIBUV_TEST_DIR}/runner-unix.c ${LIBUV_TEST_DIR}/echo-server.c) file(GLOB TEST_CSRCS ${LIBUV_TEST_DIR}/test-*.c) + + # See system/libuv/Makefile. + + if(NOT CONFIG_ARCH_HAVE_FORK) + list(REMOVE_ITEM TEST_CSRCS ${LIBUV_TEST_DIR}/test-fork.c + ${LIBUV_TEST_DIR}/test-pipe-close-stdout-read-stdin.c) + endif() list(APPEND LIBUV_UTILS_TEST_SRCS ${TEST_CSRCS}) nuttx_add_application( NAME diff --git a/system/libuv/Makefile b/system/libuv/Makefile index dec76cc1aa0..483f6194f30 100644 --- a/system/libuv/Makefile +++ b/system/libuv/Makefile @@ -144,7 +144,19 @@ CSRCS += runner.c CSRCS += runner-unix.c CSRCS += echo-server.c -CSRCS += $(wildcard libuv/test/test-*.c) +LIBUV_TEST_CSRCS = $(wildcard libuv/test/test-*.c) + +# test-fork.c and test-pipe-close-stdout-read-stdin.c call fork(), so they +# cannot be built where NuttX does not provide it. Nothing is lost either +# way: every test they define is already excluded from the task list on +# NuttX by 0001-libuv-port-for-nuttx.patch. + +ifeq ($(CONFIG_ARCH_HAVE_FORK),) +LIBUV_TEST_CSRCS := $(filter-out libuv/test/test-fork.c,$(LIBUV_TEST_CSRCS)) +LIBUV_TEST_CSRCS := $(filter-out libuv/test/test-pipe-close-stdout-read-stdin.c,$(LIBUV_TEST_CSRCS)) +endif + +CSRCS += $(LIBUV_TEST_CSRCS) endif ifneq ($(CONFIG_LIBUV_UTILS_BENCHMARK),) diff --git a/testing/ltp/CMakeLists.txt b/testing/ltp/CMakeLists.txt index 2e458d5e4d2..8d4674ecdb0 100644 --- a/testing/ltp/CMakeLists.txt +++ b/testing/ltp/CMakeLists.txt @@ -86,6 +86,12 @@ if(CONFIG_TESTING_LTP) list(APPEND BLACKWORDS "pthread_spin_init" "pthread_spin_destroy" "pthread_spin_trylock") endif() + + # See testing/ltp/Makefile. + + if(NOT CONFIG_ARCH_HAVE_FORK) + list(APPEND BLACKWORDS "[^v_]fork(") + endif() list( APPEND BLACKWORDS diff --git a/testing/ltp/Makefile b/testing/ltp/Makefile index 39a7d931b94..cee750cd0a1 100644 --- a/testing/ltp/Makefile +++ b/testing/ltp/Makefile @@ -44,6 +44,13 @@ BLACKWORDS += "pthread_spin_destroy" BLACKWORDS += "pthread_spin_trylock" endif +# Where NuttX does not declare fork(), a test that calls it cannot be built. +# The pattern spares vfork() and task_fork(), which remain available. + +ifeq ($(CONFIG_ARCH_HAVE_FORK),) +BLACKWORDS += "[^v_]fork(" +endif + BLACKWORDS += "CHILD_MAX" BLACKWORDS += "setpgid(" BLACKWORDS += "PTHREAD_SCOPE_PROCESS" From 63b94b4af7967d2891af89e78578b10049bcf03b Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 2 Aug 2026 19:05:33 +0200 Subject: [PATCH 2/2] netutils/dropbear, testing/nand_sim: do not use fork() to run in background. Neither of these wants fork() semantics. Both reach for fork() only to put work in the background, and each has a NuttX-native way to do that, so neither needs a fork primitive at all -- which matters once apache/nuttx#19562 makes ARCH_HAVE_FORK conditional on the architecture implementing POSIX fork(). netutils/dropbear: the port already routes every fork-then-exec through vfork(), because sysoptions.h selects DROPBEAR_VFORK when HAVE_FORK is undefined and the port leaves it undefined. spawn_command() in dbutil.c and both call sites in scp.c follow that switch. The one exception is the daemon() fallback that compat.c compiles under #ifndef HAVE_DAEMON, which calls fork() directly and bypasses it. NuttX provides daemon() in libs/libc/unistd/lib_daemon.c and declares it in unistd.h, so the fallback is redundant; define HAVE_DAEMON alongside the HAVE_STRLCAT and HAVE_STRLCPY entries that are there for exactly the same reason. The code was unreachable in any case -- the port hands svr_getopts() an argv containing -F, so svr_opts.forkbg is always zero and dropbear never calls daemon() at all. testing/drivers/nand_sim: forked so that the parent could return to the shell while the child registered the MTD device and slept forever. Nothing from before the fork is used after it, so the child is a self-contained entry point, and task_create() expresses that directly. The emulator body moves into nand_sim_daemon() unchanged. TESTING_NAND_SIM therefore needs no fork dependency, and the two sim configurations that enable it keep working whatever ARCH_HAVE_FORK is set to. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- netutils/dropbear/port/nuttx_config.h | 1 + testing/drivers/nand_sim/nand_sim_main.c | 43 ++++++++++++++++-------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/netutils/dropbear/port/nuttx_config.h b/netutils/dropbear/port/nuttx_config.h index d91709d1e90..6ab83fd171a 100644 --- a/netutils/dropbear/port/nuttx_config.h +++ b/netutils/dropbear/port/nuttx_config.h @@ -48,6 +48,7 @@ #define HAVE_CLOCK_GETTIME 1 #define HAVE_CONST_GAI_STRERROR_PROTO 1 #define HAVE_CRYPT 1 +#define HAVE_DAEMON 1 #define HAVE_DECL_HTOLE64 1 #define HAVE_ENDIAN_H 1 #define HAVE_EXPLICIT_BZERO 1 diff --git a/testing/drivers/nand_sim/nand_sim_main.c b/testing/drivers/nand_sim/nand_sim_main.c index 70ea24227ff..48ee969e5fc 100644 --- a/testing/drivers/nand_sim/nand_sim_main.c +++ b/testing/drivers/nand_sim/nand_sim_main.c @@ -25,6 +25,7 @@ ****************************************************************************/ #include +#include #include #include @@ -128,26 +129,17 @@ void terminate(int sig) } /**************************************************************************** - * Name: nand_sim_main + * Name: nand_sim_daemon * * Description: - * Entry point of the device emulator. + * Body of the device emulator. Registers the simulated MTD device and + * then sleeps forever; all events are handled by signals. * ****************************************************************************/ -int main(int argc, FAR char *argv[]) +static int nand_sim_daemon(int argc, FAR char *argv[]) { - int ret; - pid_t pid; - - /* Daemon */ - - pid = fork(); - - if (pid > 0) - { - return OK; - } + int ret; if (daemon(0, 1) == -1) { @@ -223,3 +215,26 @@ int main(int argc, FAR char *argv[]) errout: return ret; } + +/**************************************************************************** + * Name: nand_sim_main + * + * Description: + * Entry point of the device emulator. Starts the emulator as an + * independent task so that the caller gets its shell back. + * + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + int ret; + + ret = task_create(NAND_SIM_NAME, SCHED_PRIORITY_DEFAULT, + CONFIG_TESTING_NAND_SIM_STACK, nand_sim_daemon, NULL); + if (ret < 0) + { + return EXIT_FAILURE; + } + + return OK; +}