diff --git a/interpreters/python/Makefile b/interpreters/python/Makefile index bac4013f886..8f1a510e0ea 100644 --- a/interpreters/python/Makefile +++ b/interpreters/python/Makefile @@ -114,6 +114,11 @@ ifeq ($(CONFIG_ARCH_HAVE_FORK),y) else @echo "export ac_cv_func_fork=\"no\"" >> $@ endif +ifneq ($(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),) + @echo "export ac_cv_func_vfork=\"yes\"" >> $@ +else + @echo "export ac_cv_func_vfork=\"no\"" >> $@ +endif ifeq ($(CONFIG_SYSTEM_SYSTEM),y) @echo "export ac_cv_func_system=\"yes\"" >> $@ else @@ -135,7 +140,10 @@ endif $(SETUP_LOCAL): $(Q) ( cp $(SETUP_LOCAL).in $(SETUP_LOCAL)) -ifneq ($(CONFIG_ARCH_HAVE_FORK),y) +# _posixsubprocess is the fork-then-exec path, so vfork() is enough for it; +# os.fork() itself needs a real fork() and is governed by ac_cv_func_fork +# above. +ifeq ($(CONFIG_ARCH_HAVE_FORK)$(CONFIG_ARCH_HAVE_VFORK),) @echo "_posixsubprocess" >> $@ endif ifneq ($(CONFIG_LIBC_DLFCN),y) diff --git a/netutils/libwebsockets/lws_config_private.h b/netutils/libwebsockets/lws_config_private.h index 8f5170faff9..cacf92f5f79 100644 --- a/netutils/libwebsockets/lws_config_private.h +++ b/netutils/libwebsockets/lws_config_private.h @@ -43,7 +43,10 @@ /* #undef USE_CYASSL */ /* Define to 1 if you have the `fork' function. */ + +#ifdef CONFIG_ARCH_HAVE_FORK #define LWS_HAVE_FORK +#endif #ifndef CONFIG_DISABLE_ENVIRON /* Define to 1 if you have the `getenv' function. */ @@ -111,10 +114,14 @@ /* #undef LWS_HAVE_VFORK_H */ /* Define to 1 if `fork' works. */ +#ifdef CONFIG_ARCH_HAVE_FORK #define LWS_HAVE_WORKING_FORK +#endif /* Define to 1 if `vfork' works. */ +#if defined(CONFIG_ARCH_HAVE_VFORK) || defined(CONFIG_ARCH_HAVE_FORK) #define LWS_HAVE_WORKING_VFORK +#endif /* Define to 1 if execvpe() exists */ #define LWS_HAVE_EXECVPE diff --git a/system/libuv/CMakeLists.txt b/system/libuv/CMakeLists.txt index 12cde4fa749..22232faf023 100644 --- a/system/libuv/CMakeLists.txt +++ b/system/libuv/CMakeLists.txt @@ -171,6 +171,11 @@ 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. + + list(REMOVE_ITEM TEST_CSRCS ${LIBUV_TEST_DIR}/test-fork.c + ${LIBUV_TEST_DIR}/test-pipe-close-stdout-read-stdin.c) list(APPEND LIBUV_UTILS_TEST_SRCS ${TEST_CSRCS}) nuttx_add_application( NAME diff --git a/system/libuv/Makefile b/system/libuv/Makefile index dec76cc1aa0..7498919b5ab 100644 --- a/system/libuv/Makefile +++ b/system/libuv/Makefile @@ -144,7 +144,15 @@ CSRCS += runner.c CSRCS += runner-unix.c CSRCS += echo-server.c -CSRCS += $(wildcard libuv/test/test-*.c) +# test-fork.c and test-pipe-close-stdout-read-stdin.c call fork(). Every +# test they define is already excluded from the task list on NuttX by +# 0001-libuv-port-for-nuttx.patch, so they are dead code here. + +LIBUV_TEST_CSRCS = $(wildcard libuv/test/test-*.c) +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)) + +CSRCS += $(LIBUV_TEST_CSRCS) endif ifneq ($(CONFIG_LIBUV_UTILS_BENCHMARK),) diff --git a/testing/drivers/nand_sim/Kconfig b/testing/drivers/nand_sim/Kconfig index e76cdc3f5a4..d6240d740ee 100644 --- a/testing/drivers/nand_sim/Kconfig +++ b/testing/drivers/nand_sim/Kconfig @@ -6,6 +6,7 @@ config TESTING_NAND_SIM boolean "NAND Flash Simulator" depends on MTD_NAND_RAM && ENABLE_ALL_SIGNALS + depends on ARCH_HAVE_TASK_FORK || ARCH_HAVE_FORK default n ---help--- Enable the NAND Flash Simulator device. diff --git a/testing/drivers/nand_sim/nand_sim_main.c b/testing/drivers/nand_sim/nand_sim_main.c index 70ea24227ff..151589b3f0a 100644 --- a/testing/drivers/nand_sim/nand_sim_main.c +++ b/testing/drivers/nand_sim/nand_sim_main.c @@ -25,7 +25,9 @@ ****************************************************************************/ #include +#include #include +#include #include #include @@ -140,9 +142,16 @@ int main(int argc, FAR char *argv[]) int ret; pid_t pid; - /* Daemon */ + /* task_fork() rather than fork(): this wants a clone that outlives the + * caller and shares its memory. The fallback below covers a nuttx that + * does not have task_fork() yet. + */ - pid = fork(); +#ifndef CONFIG_ARCH_HAVE_TASK_FORK +# define task_fork() fork() +#endif + + pid = task_fork(); if (pid > 0) { diff --git a/testing/fs/fdsantest/fdsantest_simple.c b/testing/fs/fdsantest/fdsantest_simple.c index 128e2878bff..e5dc468178a 100644 --- a/testing/fs/fdsantest/fdsantest_simple.c +++ b/testing/fs/fdsantest/fdsantest_simple.c @@ -96,6 +96,7 @@ static void test_case_overflow(void **state) assert_int_equal(open_count, close_count); } +#if defined(CONFIG_ARCH_HAVE_VFORK) || defined(CONFIG_ARCH_HAVE_FORK) static void test_case_vfork(void **state) { int fd = open("/dev/null", O_RDONLY); @@ -112,6 +113,7 @@ static void test_case_vfork(void **state) android_fdsan_close_with_tag(fd, 0xbadc0de); } +#endif /**************************************************************************** * Public Functions @@ -129,7 +131,9 @@ int main(int argc, FAR char *argv[]) cmocka_unit_test(test_case_unowned_tagged_close), cmocka_unit_test(test_case_owned_tagged_close), cmocka_unit_test(test_case_overflow), +#if defined(CONFIG_ARCH_HAVE_VFORK) || defined(CONFIG_ARCH_HAVE_FORK) cmocka_unit_test(test_case_vfork), +#endif }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/testing/ltp/CMakeLists.txt b/testing/ltp/CMakeLists.txt index 2e458d5e4d2..dde159976fc 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 AND NOT CONFIG_FORK_IS_TASK_FORK) + list(APPEND BLACKWORDS "[^v_]fork(") + endif() list( APPEND BLACKWORDS diff --git a/testing/ltp/Makefile b/testing/ltp/Makefile index 39a7d931b94..50a631d39b0 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)$(CONFIG_FORK_IS_TASK_FORK),) +BLACKWORDS += "[^v_]fork(" +endif + BLACKWORDS += "CHILD_MAX" BLACKWORDS += "setpgid(" BLACKWORDS += "PTHREAD_SCOPE_PROCESS" diff --git a/testing/ostest/CMakeLists.txt b/testing/ostest/CMakeLists.txt index 74103db4118..2ab55d88444 100644 --- a/testing/ostest/CMakeLists.txt +++ b/testing/ostest/CMakeLists.txt @@ -143,10 +143,19 @@ if(CONFIG_TESTING_OSTEST) endif() endif() - if(CONFIG_ARCH_HAVE_FORK) - if(CONFIG_SCHED_WAITPID) - list(APPEND SRCS vfork.c) - endif() + # See testing/ostest/Makefile for why each test also accepts the symbol that + # stands in for it on a nuttx without the fork()/vfork() split. + + if(CONFIG_ARCH_HAVE_TASK_FORK OR CONFIG_ARCH_HAVE_FORK) + list(APPEND SRCS task_fork.c) + endif() + + if(CONFIG_ARCH_HAVE_VFORK OR (CONFIG_ARCH_HAVE_FORK AND CONFIG_SCHED_WAITPID)) + list(APPEND SRCS vfork.c) + endif() + + if(CONFIG_ARCH_HAVE_FORK AND CONFIG_ARCH_HAVE_VFORK) + list(APPEND SRCS fork.c) endif() if(CONFIG_ARCH_SETJMP_H) diff --git a/testing/ostest/Makefile b/testing/ostest/Makefile index f443ecb0b39..d5d8df42aae 100644 --- a/testing/ostest/Makefile +++ b/testing/ostest/Makefile @@ -144,10 +144,21 @@ CSRCS += sigev_thread.c endif endif -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) -ifeq ($(CONFIG_SCHED_WAITPID),y) +# Each test is built where the primitive it tests exists. See ostest.h for +# why each also accepts ARCH_HAVE_FORK, and why fork.c does not. + +ifneq ($(CONFIG_ARCH_HAVE_TASK_FORK)$(CONFIG_ARCH_HAVE_FORK),) +CSRCS += task_fork.c +endif + +ifeq ($(CONFIG_ARCH_HAVE_VFORK),y) +CSRCS += vfork.c +else ifeq ($(CONFIG_ARCH_HAVE_FORK)$(CONFIG_SCHED_WAITPID),yy) CSRCS += vfork.c endif + +ifeq ($(CONFIG_ARCH_HAVE_FORK)$(CONFIG_ARCH_HAVE_VFORK),yy) +CSRCS += fork.c endif ifeq ($(CONFIG_ARCH_SETJMP_H),y) diff --git a/testing/ostest/fork.c b/testing/ostest/fork.c new file mode 100644 index 00000000000..e6a3a0945f7 --- /dev/null +++ b/testing/ostest/fork.c @@ -0,0 +1,280 @@ +/**************************************************************************** + * apps/testing/ostest/fork.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ostest.h" + +#ifdef OSTEST_HAVE_FORK + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FORK_HEAPSIZE 256 +#define FORK_PARENTMARK 0x5a +#define FORK_CHILDMARK 0xa5 + +/* Distinct values written through a pointer to a stack local, to check that + * the child's stack is at the same virtual address as the parent's. + */ + +#define FORK_STACKPARENT 0x1234 +#define FORK_STACKCHILD 0x5678 + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* .data and .bss, which a fork() child must get its own copy of */ + +static volatile int g_forkdata = 1; +static volatile int g_forkbss; +static FAR unsigned char *g_forkheap; + +/* Pointer to a local in fork_test()'s frame, taken before fork(). In .data + * rather than on the stack: the compiler could rematerialise a local + * pointer from the current stack pointer, which would test nothing. + */ + +static FAR volatile int *g_forkstackptr; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fork_child + * + * Description: + * Everything here is forbidden to a vfork() child and permitted to a + * fork() child. Returns the child's exit status. + * + ****************************************************************************/ + +static int fork_child(void) +{ + FAR char *scratch; + + /* The parent must see none of this. */ + + g_forkdata = 2; + g_forkbss = 2; + memset(g_forkheap, FORK_CHILDMARK, FORK_HEAPSIZE); + + /* A vfork() child may not call these; a fork() child may. */ + + scratch = malloc(64); + if (scratch == NULL) + { + printf("fork_test: ERROR Child could not malloc()\n"); + return 1; + } + + strlcpy(scratch, "child", 64); + printf("fork_test: Child running independently (%s)\n", scratch); + free(scratch); + + /* Give the parent time to make its own writes, so that if the two shared + * memory we would see the parent's values below rather than our own. + */ + + usleep(200 * 1000); + + if (g_forkdata != 2 || g_forkbss != 2) + { + printf("fork_test: ERROR Child saw the parent's writes: " + "data=%d bss=%d\n", g_forkdata, g_forkbss); + return 1; + } + + if (g_forkheap[0] != FORK_CHILDMARK || + g_forkheap[FORK_HEAPSIZE - 1] != FORK_CHILDMARK) + { + printf("fork_test: ERROR Child saw the parent's heap writes\n"); + return 1; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fork_test + * + * Description: + * Verify the defining property of POSIX fork(): the child gets its own + * copy of the parent's memory, in both directions. That is the exact + * opposite of what task_fork_test() checks. Also verifies that none of + * vfork()'s restrictions apply. + * + ****************************************************************************/ + +int fork_test(void) +{ + pid_t pid; + int status = 0; + int ret = 0; + + /* volatile so it is really in memory and the compare is not folded. */ + + volatile int stackvar = FORK_STACKPARENT; + + printf("fork_test: Started\n"); + + /* Publish its address before forking. See g_forkstackptr. */ + + g_forkstackptr = &stackvar; + + g_forkdata = 1; + g_forkbss = 1; + + g_forkheap = malloc(FORK_HEAPSIZE); + if (g_forkheap == NULL) + { + printf("fork_test: ERROR Failed to allocate the heap probe\n"); + ASSERT(false); + return -1; + } + + memset(g_forkheap, FORK_PARENTMARK, FORK_HEAPSIZE); + + pid = fork(); + if (pid == 0) + { + /* The child's stack is at the parent's virtual addresses, so this + * pointer names the child's own live local. A relocated stack would + * write into the copy of the parent's instead. + */ + + *g_forkstackptr = FORK_STACKCHILD; + if (stackvar != FORK_STACKCHILD) + { + printf("fork_test: ERROR Child stack was relocated: wrote %d " + "through %p, local at %p reads %d\n", + FORK_STACKCHILD, g_forkstackptr, &stackvar, stackvar); + _exit(1); + } + + /* Returns from fork_child() and from this branch -- both illegal + * for a vfork() child. + */ + + _exit(fork_child()); + } + else if (pid < 0) + { + printf("fork_test: ERROR fork() failed: %d\n", errno); + free(g_forkheap); + ASSERT(false); + return -1; + } + + /* Parent runs concurrently: write now, check isolation afterwards. */ + + g_forkdata = 3; + g_forkbss = 3; + memset(g_forkheap, FORK_PARENTMARK, FORK_HEAPSIZE); + +#ifdef CONFIG_SCHED_WAITPID + /* Wait for the child to be done before comparing memory. waitpid() blocks + * on a child that is still alive whether or not its exit status will be + * retained, so this synchronises either way; ECHILD simply means the child + * had already finished, which is just as good. It is not a failure: + * ostest_main() sets SA_NOCLDWAIT on SIGCHLD for the whole run, so an + * exited child's status is not kept even where CONFIG_SCHED_CHILD_STATUS + * is enabled. + */ + + if (waitpid(pid, &status, 0) != pid && errno != ECHILD) + { + printf("fork_test: ERROR waitpid() failed: %d\n", errno); + free(g_forkheap); + ASSERT(false); + return -1; + } +#else + sleep(1); +#endif + + if (g_forkdata != 3 || g_forkbss != 3) + { + printf("fork_test: ERROR Parent saw the child's writes: " + "data=%d bss=%d (expected 3, 3)\n", g_forkdata, g_forkbss); + ret = -1; + } + + if (g_forkheap[0] != FORK_PARENTMARK || + g_forkheap[FORK_HEAPSIZE - 1] != FORK_PARENTMARK) + { + printf("fork_test: ERROR Parent saw the child's heap writes\n"); + ret = -1; + } + + /* The child wrote to the same stack address; the parent must not see it */ + + if (stackvar != FORK_STACKPARENT) + { + printf("fork_test: ERROR Parent saw the child's stack write: " + "%d (expected %d)\n", stackvar, FORK_STACKPARENT); + ret = -1; + } + +#ifdef CONFIG_SCHED_WAITPID + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + { + printf("fork_test: ERROR Child reported failure, status 0x%04x\n", + status); + ret = -1; + } +#endif + + free(g_forkheap); + g_forkheap = NULL; + + if (ret < 0) + { + ASSERT(false); + return ret; + } + + printf("fork_test: Parent and child had independent memory\n"); + return 0; +} + +#endif /* OSTEST_HAVE_FORK */ diff --git a/testing/ostest/ostest.h b/testing/ostest/ostest.h index 5e01626c0e2..6ee7e9f7641 100644 --- a/testing/ostest/ostest.h +++ b/testing/ostest/ostest.h @@ -282,12 +282,50 @@ void priority_inheritance(void); void sched_lock_test(void); +/* The fork family **********************************************************/ + +/* Until nuttx has the three separate primitives, ARCH_HAVE_FORK stands in + * for the first two: today's fork() is task_fork(), and today's vfork() is + * that plus a waitpid(). fork_test() is deliberately not mapped -- the copy + * semantics it checks are what does not exist yet, and ARCH_HAVE_VFORK is + * the evidence that they do. These blocks come out with the split. + */ + +#if defined(CONFIG_ARCH_HAVE_TASK_FORK) || defined(CONFIG_ARCH_HAVE_FORK) +# define OSTEST_HAVE_TASK_FORK 1 +#endif + +#if defined(CONFIG_ARCH_HAVE_VFORK) || \ + (defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID)) +# define OSTEST_HAVE_VFORK 1 +#endif + +#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_ARCH_HAVE_VFORK) +# define OSTEST_HAVE_FORK 1 +#endif + +#if defined(OSTEST_HAVE_TASK_FORK) && !defined(CONFIG_ARCH_HAVE_TASK_FORK) +# define task_fork() fork() +#endif + +/* task_fork.c **************************************************************/ + +#ifdef OSTEST_HAVE_TASK_FORK +int task_fork_test(void); +#endif + /* vfork.c ******************************************************************/ -#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID) +#ifdef OSTEST_HAVE_VFORK int vfork_test(void); #endif +/* fork.c *******************************************************************/ + +#ifdef OSTEST_HAVE_FORK +int fork_test(void); +#endif + /* setjmp.c *****************************************************************/ void setjmp_test(void); diff --git a/testing/ostest/ostest_main.c b/testing/ostest/ostest_main.c index 92224701f9b..e717b291d7e 100644 --- a/testing/ostest/ostest_main.c +++ b/testing/ostest/ostest_main.c @@ -224,6 +224,29 @@ static int user_main(int argc, char *argv[]) g_mmbefore = mallinfo(); g_mmprevious = g_mmbefore; + /* Run the three fork primitives first. They exercise the lowest-level + * machinery in the suite, so a fault takes the process down rather than + * reporting a failure -- better to learn that in seconds. + */ + +#ifdef OSTEST_HAVE_TASK_FORK + printf("\nuser_main: task_fork() test\n"); + task_fork_test(); + check_test_memory_usage(); +#endif + +#ifdef OSTEST_HAVE_VFORK + printf("\nuser_main: vfork() test\n"); + vfork_test(); + check_test_memory_usage(); +#endif + +#ifdef OSTEST_HAVE_FORK + printf("\nuser_main: fork() test\n"); + fork_test(); + check_test_memory_usage(); +#endif + printf("\nuser_main: Begin argument test\n"); printf("user_main: Started with argc=%d\n", argc); @@ -637,12 +660,6 @@ static int user_main(int argc, char *argv[]) check_test_memory_usage(); #endif -#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID) && \ - !defined(CONFIG_ARCH_SIM) - printf("\nuser_main: vfork() test\n"); - vfork_test(); -#endif - #if defined(CONFIG_SMP) && defined(CONFIG_BUILD_FLAT) printf("\nuser_main: smp call test\n"); smp_call_test(); diff --git a/testing/ostest/task_fork.c b/testing/ostest/task_fork.c new file mode 100644 index 00000000000..2bd00ee7698 --- /dev/null +++ b/testing/ostest/task_fork.c @@ -0,0 +1,98 @@ +/**************************************************************************** + * apps/testing/ostest/task_fork.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ostest.h" + +#ifdef OSTEST_HAVE_TASK_FORK + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static volatile bool g_taskforkchild; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: task_fork_test + * + * Description: + * Verify the defining property of task_fork(): the child shares the + * parent's memory, so the parent observes the child's write. This is the + * test that used to be called the "vfork" test. A real fork() child's + * write would be invisible here; see fork_test(). + * + ****************************************************************************/ + +int task_fork_test(void) +{ + pid_t pid; + + printf("task_fork_test: Started\n"); + + g_taskforkchild = false; + pid = task_fork(); + if (pid == 0) + { + /* Shared memory, so the parent sees this. Runs concurrently. */ + + g_taskforkchild = true; + exit(0); + } + else if (pid < 0) + { + printf("task_fork_test: ERROR task_fork() failed: %d\n", errno); + ASSERT(false); + return -1; + } + + sleep(1); + + if (!g_taskforkchild) + { + printf("task_fork_test: ERROR Child %d did not run, or its write to " + "shared memory was not visible\n", pid); + ASSERT(false); + return -1; + } + + printf("task_fork_test: Child %d ran successfully\n", pid); + return 0; +} + +#endif /* OSTEST_HAVE_TASK_FORK */ diff --git a/testing/ostest/vfork.c b/testing/ostest/vfork.c index bd6cfabadb9..d5cdb169a89 100644 --- a/testing/ostest/vfork.c +++ b/testing/ostest/vfork.c @@ -31,36 +31,51 @@ #include #include #include +#include #include #include "ostest.h" -#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID) +#ifdef OSTEST_HAVE_VFORK /**************************************************************************** * Private Data ****************************************************************************/ -static volatile bool g_vforkchild; +/* Set and cleared by the parent only: a vfork() child may not write it. */ + +static volatile bool g_vforkrunning; /**************************************************************************** * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: vfork_test + * + * Description: + * Verify the defining property of vfork(): the parent is suspended until + * the child _exit()s or exec()s. The child does only what POSIX + * permits -- _exit(), not exit(), which would flush stdio in the parent's + * address space. Since the child may not write memory, the observable is + * its exit status: an unsuspended parent would reach waitpid() first. + * + ****************************************************************************/ + int vfork_test(void) { pid_t pid; - g_vforkchild = false; + printf("vfork_test: Started\n"); + + g_vforkrunning = true; + pid = vfork(); if (pid == 0) { - /* There is not very much that the child is permitted to do. Perhaps - * it can just set g_vforkchild. - */ + /* The only thing a vfork() child may do is leave. */ - g_vforkchild = true; - exit(0); + _exit(42); } else if (pid < 0) { @@ -68,22 +83,48 @@ int vfork_test(void) ASSERT(false); return -1; } - else + + /* Reached only once the child has exited or exec'ed. */ + + g_vforkrunning = false; + +#ifdef CONFIG_SCHED_WAITPID { - sleep(1); - if (g_vforkchild) + int status = 0; + pid_t ret; + + ret = waitpid(pid, &status, 0); + + /* Two answers are correct, and which one comes back is a property of + * the configuration: a retained status must be exit(42), and ECHILD + * is equally good evidence -- it says the child was already gone when + * we asked. ostest_main() sets SA_NOCLDWAIT for the whole run, so + * testing CONFIG_SCHED_CHILD_STATUS alone is not enough. + */ + + if (ret == pid) { - printf("vfork_test: Child %d ran successfully\n", pid); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) + { + printf("vfork_test: ERROR Child %d status 0x%04x, expected " + "exit(42)\n", pid, status); + ASSERT(false); + return -1; + } } - else + else if (ret >= 0 || errno != ECHILD) { - printf("vfork_test: ERROR Child %d did not run\n", pid); + printf("vfork_test: ERROR waitpid() returned %d (%d), expected " + "the child's status or ECHILD\n", ret, errno); ASSERT(false); return -1; } } +#endif + printf("vfork_test: Child %d ran and exited before the parent resumed\n", + pid); return 0; } -#endif /* CONFIG_ARCH_HAVE_FORK && CONFIG_SCHED_WAITPID */ +#endif /* OSTEST_HAVE_VFORK */