diff --git a/lib/libc/stdlib/quick_exit.c b/lib/libc/stdlib/quick_exit.c index 05db690cb6e7..4dee7b20bd2b 100644 --- a/lib/libc/stdlib/quick_exit.c +++ b/lib/libc/stdlib/quick_exit.c @@ -1,85 +1,77 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 David Chisnall + * Copyright (c) 2023 Klara, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include -#include + +#include #include -#include /** - * Linked list of quick exit handlers. This is simpler than the atexit() - * version, because it is not required to support C++ destructors or - * DSO-specific cleanups. + * Linked list of quick exit handlers. These will be invoked in reverse + * order of insertion when quick_exit() is called. This is simpler than + * the atexit() version, because it is not required to support C++ + * destructors or DSO-specific cleanups. */ struct quick_exit_handler { struct quick_exit_handler *next; void (*cleanup)(void); }; -/** - * Lock protecting the handlers list. - */ -static pthread_mutex_t atexit_mutex = PTHREAD_MUTEX_INITIALIZER; -/** - * Stack of cleanup handlers. These will be invoked in reverse order when - */ -static struct quick_exit_handler *handlers; +static _Atomic(struct quick_exit_handler *) handlers; int at_quick_exit(void (*func)(void)) { struct quick_exit_handler *h; - - h = malloc(sizeof(*h)); - if (NULL == h) - return (1); + if ((h = calloc(1, sizeof(*h))) == NULL) { + return (-1); + } h->cleanup = func; - pthread_mutex_lock(&atexit_mutex); - h->next = handlers; - __compiler_membar(); - handlers = h; - pthread_mutex_unlock(&atexit_mutex); + while (!atomic_compare_exchange_strong(&handlers, &h->next, h)) { + /* nothing */ ; + } return (0); } void quick_exit(int status) { struct quick_exit_handler *h; /* * XXX: The C++ spec requires us to call std::terminate if there is an * exception here. */ - for (h = handlers; NULL != h; h = h->next) { - __compiler_membar(); + for (h = atomic_load_explicit(&handlers, memory_order_acquire); + h != NULL; h = h->next) { h->cleanup(); } _Exit(status); } diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile index a2a6420aba41..860e530389df 100644 --- a/lib/libc/tests/stdlib/Makefile +++ b/lib/libc/tests/stdlib/Makefile @@ -1,82 +1,83 @@ .include ATF_TESTS_C+= clearenv_test ATF_TESTS_C+= dynthr_test ATF_TESTS_C+= heapsort_test ATF_TESTS_C+= mergesort_test ATF_TESTS_C+= qsort_test .if ${COMPILER_TYPE} == "clang" ATF_TESTS_C+= qsort_b_test .endif ATF_TESTS_C+= qsort_r_compat_test ATF_TESTS_C+= qsort_r_test ATF_TESTS_C+= qsort_s_test +ATF_TESTS_C+= quick_exit_test ATF_TESTS_C+= set_constraint_handler_s_test ATF_TESTS_C+= strfmon_test ATF_TESTS_C+= tsearch_test ATF_TESTS_CXX+= cxa_thread_atexit_test ATF_TESTS_CXX+= cxa_thread_atexit_nothr_test # All architectures on FreeBSD have fenv.h CFLAGS+= -D__HAVE_FENV # Define __HAVE_LONG_DOUBLE for architectures whose long double has greater # precision than their double. .if ${MACHINE_CPUARCH} == "aarch64" || \ ${MACHINE_CPUARCH} == "amd64" || \ ${MACHINE_CPUARCH} == "i386" || \ ${MACHINE_CPUARCH} == "riscv" CFLAGS+= -D__HAVE_LONG_DOUBLE .endif # TODO: t_getenv_thread, t_mi_vector_hash, t_strtoi NETBSD_ATF_TESTS_C+= abs_test NETBSD_ATF_TESTS_C+= atoi_test NETBSD_ATF_TESTS_C+= div_test NETBSD_ATF_TESTS_C+= getenv_test NETBSD_ATF_TESTS_C+= exit_test NETBSD_ATF_TESTS_C+= hsearch_test NETBSD_ATF_TESTS_C+= posix_memalign_test NETBSD_ATF_TESTS_C+= random_test NETBSD_ATF_TESTS_C+= strtod_test NETBSD_ATF_TESTS_C+= strtol_test NETBSD_ATF_TESTS_C+= system_test # TODO: need to come up with a correct explanation of what the patch pho does # with h_atexit #ATF_TESTS_SH= atexit_test NETBSD_ATF_TESTS_SH= getopt_test .include "../Makefile.netbsd-tests" BINDIR= ${TESTSDIR} # TODO: see comment above #PROGS+= h_atexit PROGS+= h_getopt h_getopt_long CFLAGS+= -I${.CURDIR} CXXSTD.cxa_thread_atexit_test= c++11 CXXSTD.cxa_thread_atexit_nothr_test= c++11 LIBADD.cxa_thread_atexit_test+= pthread # Tests that requires Blocks feature .for t in qsort_b_test CFLAGS.${t}.c+= -fblocks LIBADD.${t}+= BlocksRuntime .endfor .for t in h_getopt h_getopt_long CFLAGS.$t+= -I${LIBNETBSD_SRCDIR} -I${SRCTOP}/contrib/netbsd-tests LDFLAGS.$t+= -L${LIBNETBSD_OBJDIR} LIBADD.${t}+= netbsd util .endfor LIBADD.strtod_test+= m SUBDIR+= dynthr_mod .include diff --git a/lib/libc/tests/stdlib/quick_exit_test.c b/lib/libc/tests/stdlib/quick_exit_test.c new file mode 100644 index 000000000000..9feed8a6fa63 --- /dev/null +++ b/lib/libc/tests/stdlib/quick_exit_test.c @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2023 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include + +#include + +static void func_a(void) +{ + if (write(STDOUT_FILENO, "a", 1) != 1) + _Exit(1); +} + +static void func_b(void) +{ + if (write(STDOUT_FILENO, "b", 1) != 1) + _Exit(1); +} + +static void func_c(void) +{ + if (write(STDOUT_FILENO, "c", 1) != 1) + _Exit(1); +} + +static void child(void) +{ + // this will be received by the parent + printf("hello, "); + fflush(stdout); + // this won't, because quick_exit() does not flush + printf("world"); + // these will be called in reverse order, producing "abc" + if (at_quick_exit(func_c) != 0 || + at_quick_exit(func_b) != 0 || + at_quick_exit(func_a) != 0) + _Exit(1); + quick_exit(0); +} + +ATF_TC_WITHOUT_HEAD(quick_exit); +ATF_TC_BODY(quick_exit, tc) +{ + char buf[100] = ""; + ssize_t len; + int p[2], wstatus = 0; + pid_t pid; + + ATF_REQUIRE(pipe(p) == 0); + pid = fork(); + if (pid == 0) { + if (dup2(p[1], STDOUT_FILENO) < 0) + _Exit(1); + (void)close(p[1]); + (void)close(p[0]); + child(); + _Exit(1); + } + ATF_REQUIRE_MSG(pid > 0, + "expect fork() to succeed"); + ATF_CHECK_EQ_MSG(pid, waitpid(pid, &wstatus, 0), + "expect to collect child process"); + ATF_CHECK_EQ_MSG(0, wstatus, + "expect child to exit cleanly"); + ATF_CHECK_MSG((len = read(p[0], buf, sizeof(buf))) > 0, + "expect to receive output from child"); + ATF_CHECK_STREQ("hello, abc", buf); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, quick_exit); + return (atf_no_error()); +}