diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -244,6 +244,7 @@ def PThreadAPI : PublicAPI<"pthread.h"> { let Types = [ + "__atfork_callback_t", "__pthread_once_func_t", "__pthread_start_t", "__pthread_tss_dtor_t", diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -305,6 +305,7 @@ libc.src.dirent.readdir # pthread.h entrypoints + libc.src.pthread.pthread_atfork libc.src.pthread.pthread_attr_destroy libc.src.pthread.pthread_attr_init libc.src.pthread.pthread_attr_getdetachstate diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -191,6 +191,7 @@ GEN_HDR pthread.h DEPENDS .llvm_libc_common_h + .llvm-libc-types.__atfork_callback_t .llvm-libc-types.__pthread_start_t .llvm-libc-types.__pthread_tss_dtor_t .llvm-libc-types.pthread_attr_t diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt --- a/libc/include/llvm-libc-types/CMakeLists.txt +++ b/libc/include/llvm-libc-types/CMakeLists.txt @@ -1,5 +1,6 @@ add_header(off64_t HDR off64_t.h) add_header(size_t HDR size_t.h) +add_header(__atfork_callback_t HDR __atfork_callback_t.h) add_header(__bsearchcompare_t HDR __bsearchcompare_t.h) add_header(__call_once_func_t HDR __call_once_func_t.h) add_header(__exec_argv_t HDR __exec_argv_t.h) diff --git a/libc/include/llvm-libc-types/__atfork_callback_t.h b/libc/include/llvm-libc-types/__atfork_callback_t.h new file mode 100644 --- /dev/null +++ b/libc/include/llvm-libc-types/__atfork_callback_t.h @@ -0,0 +1,14 @@ +//===-- Definition of type __atfork_callback_t ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef __LLVM_LIBC_TYPES_ATFORK_CALLBACK_T_H__ +#define __LLVM_LIBC_TYPES_ATFORK_CALLBACK_T_H__ + +typedef void (*__atfork_callback_t)(void); + +#endif // __LLVM_LIBC_TYPES_ATFORK_CALLBACK_T_H__ diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -48,6 +48,8 @@ def ExecArgvT : NamedType<"__exec_argv_t">; def ExecEnvpT : NamedType<"__exec_envp_t">; +def AtForkCallbackT : NamedType<"__atfork_callback_t">; + def POSIX : StandardSpec<"POSIX"> { PtrType CharPtr = PtrType; RestrictedPtrType RestrictedCharPtr = RestrictedPtrType; @@ -718,6 +720,7 @@ "pthread.h", [], // Macros [ + AtForkCallbackT, PThreadAttrTType, PThreadKeyT, PThreadMutexAttrTType, @@ -730,6 +733,11 @@ ], // Types [], // Enumerations [ + FunctionSpec< + "pthread_atfork", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, FunctionSpec< "pthread_attr_init", RetValSpec, diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt --- a/libc/src/__support/CMakeLists.txt +++ b/libc/src/__support/CMakeLists.txt @@ -83,6 +83,16 @@ libc.src.errno.errno ) +add_object_library( + fork_callbacks + SRCS + fork_callbacks.cpp + HDRS + fork_callbacks.h + DEPENDS + .threads.mutex +) + add_header_library( integer_operations HDRS diff --git a/libc/src/__support/fork_callbacks.h b/libc/src/__support/fork_callbacks.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/fork_callbacks.h @@ -0,0 +1,19 @@ +//===-- At-fork callback helpers -------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +namespace __llvm_libc { + +using ForkCallback = void(void); + +bool register_atfork_callbacks(ForkCallback *prepare_cd, + ForkCallback *parent_cb, ForkCallback *child_cb); +void invoke_prepare_callbacks(); +void invoke_parent_callbacks(); +void invoke_child_callbacks(); + +} // namespace __llvm_libc diff --git a/libc/src/__support/fork_callbacks.cpp b/libc/src/__support/fork_callbacks.cpp new file mode 100644 --- /dev/null +++ b/libc/src/__support/fork_callbacks.cpp @@ -0,0 +1,90 @@ +//===-- Implementation of at-fork callback helpers -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "fork_callbacks.h" + +#include "src/__support/threads/mutex.h" + +#include // For size_t + +namespace __llvm_libc { + +namespace { + +struct ForkCallbackTriple { + ForkCallback *prepare = nullptr; + ForkCallback *parent = nullptr; + ForkCallback *child = nullptr; + constexpr ForkCallbackTriple() = default; +}; + +class AtForkCallbackManager { + static constexpr size_t CALLBACK_SIZE = 32; + // TODO: Replace this with block store when integration tests + // can use allocators. + ForkCallbackTriple list[CALLBACK_SIZE]; + Mutex mtx; + size_t next_index; + +public: + constexpr AtForkCallbackManager() : mtx(false, false, false), next_index(0) {} + + bool register_triple(const ForkCallbackTriple &triple) { + MutexLock lock(&mtx); + if (next_index >= CALLBACK_SIZE) + return false; + list[next_index] = triple; + ++next_index; + return true; + } + + void invoke_prepare() { + MutexLock lock(&mtx); + for (size_t i = 0; i < next_index; ++i) { + auto prepare = list[i].prepare; + if (prepare) + prepare(); + } + } + + void invoke_parent() { + MutexLock lock(&mtx); + for (size_t i = 0; i < next_index; ++i) { + auto parent = list[i].parent; + if (parent) + parent(); + } + } + + void invoke_child() { + MutexLock lock(&mtx); + for (size_t i = 0; i < next_index; ++i) { + auto child = list[i].child; + if (child) + child(); + } + } +}; + +AtForkCallbackManager cb_manager; + +} // Anonymous namespace + +bool register_atfork_callbacks(ForkCallback *prepare_cb, + ForkCallback *parent_cb, + ForkCallback *child_cb) { + return cb_manager.register_triple({prepare_cb, parent_cb, child_cb}); +} + +void invoke_child_callbacks() { cb_manager.invoke_child(); } + +void invoke_prepare_callbacks() { cb_manager.invoke_prepare(); } + +void invoke_parent_callbacks() { cb_manager.invoke_parent(); } + +} // namespace __llvm_libc diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt --- a/libc/src/pthread/CMakeLists.txt +++ b/libc/src/pthread/CMakeLists.txt @@ -397,3 +397,15 @@ libc.include.pthread libc.src.__support.threads.callonce ) + +add_entrypoint_object( + pthread_atfork + SRCS + pthread_atfork.cpp + HDRS + pthread_atfork.h + DEPENDS + libc.include.errno + libc.include.pthread + libc.src.__support.fork_callbacks +) diff --git a/libc/src/pthread/pthread_atfork.h b/libc/src/pthread/pthread_atfork.h new file mode 100644 --- /dev/null +++ b/libc/src/pthread/pthread_atfork.h @@ -0,0 +1,21 @@ +//===-- Implementation header for pthread_atfork function -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_THREADS_PTHREAD_ATFORK_H +#define LLVM_LIBC_SRC_THREADS_PTHREAD_ATFORK_H + +#include + +namespace __llvm_libc { + +int pthread_atfork(__atfork_callback_t prepare, __atfork_callback_t parent, + __atfork_callback_t child); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_PTHREAD_ATFORK_H diff --git a/libc/src/pthread/pthread_atfork.cpp b/libc/src/pthread/pthread_atfork.cpp new file mode 100644 --- /dev/null +++ b/libc/src/pthread/pthread_atfork.cpp @@ -0,0 +1,25 @@ +//===-- Linux implementation of the pthread_atfork function ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "pthread_atfork.h" + +#include "src/__support/common.h" +#include "src/__support/fork_callbacks.h" + +#include +#include // For pthread_* type definitions. + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, pthread_atfork, + (__atfork_callback_t prepare, __atfork_callback_t parent, + __atfork_callback_t child)) { + return register_atfork_callbacks(prepare, parent, child) ? 0 : ENOMEM; +} + +} // namespace __llvm_libc diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt --- a/libc/src/unistd/linux/CMakeLists.txt +++ b/libc/src/unistd/linux/CMakeLists.txt @@ -100,6 +100,7 @@ libc.include.errno libc.include.unistd libc.include.sys_syscall + libc.src.__support.fork_callbacks libc.src.__support.OSUtil.osutil libc.src.__support.threads.thread libc.src.errno.errno diff --git a/libc/src/unistd/linux/fork.cpp b/libc/src/unistd/linux/fork.cpp --- a/libc/src/unistd/linux/fork.cpp +++ b/libc/src/unistd/linux/fork.cpp @@ -10,6 +10,7 @@ #include "src/__support/OSUtil/syscall.h" // For internal syscall function. #include "src/__support/common.h" +#include "src/__support/fork_callbacks.h" #include "src/__support/threads/thread.h" // For thread self object #include @@ -21,6 +22,7 @@ // functionality and standard compliance in future. LLVM_LIBC_FUNCTION(pid_t, fork, (void)) { + invoke_prepare_callbacks(); #ifdef SYS_fork pid_t ret = __llvm_libc::syscall_impl(SYS_fork); #elif defined(SYS_clone) @@ -34,6 +36,7 @@ // copy of parent process' thread which called fork. So, we have to fix up // the child process' self object with the new process' tid. self.attrib->tid = __llvm_libc::syscall_impl(SYS_gettid); + invoke_child_callbacks(); return 0; } @@ -43,6 +46,7 @@ return -1; } + invoke_parent_callbacks(); return ret; } diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt --- a/libc/test/integration/src/unistd/CMakeLists.txt +++ b/libc/test/integration/src/unistd/CMakeLists.txt @@ -14,6 +14,7 @@ libc.include.signal libc.include.sys_wait libc.include.unistd + libc.src.pthread.pthread_atfork libc.src.signal.raise libc.src.sys.wait.wait libc.src.sys.wait.wait4 diff --git a/libc/test/integration/src/unistd/fork_test.cpp b/libc/test/integration/src/unistd/fork_test.cpp --- a/libc/test/integration/src/unistd/fork_test.cpp +++ b/libc/test/integration/src/unistd/fork_test.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "src/pthread/pthread_atfork.h" #include "src/signal/raise.h" #include "src/sys/wait/wait.h" #include "src/sys/wait/wait4.h" @@ -105,6 +106,39 @@ ASSERT_TRUE(WTERMSIG(status) == SIGUSR1); } +static int prepare = 0; +static int parent = 0; +static int child = 0; +static constexpr int DONE = 0x600D; + +static void prepare_cb() { prepare = DONE; } + +static void parent_cb() { parent = DONE; } + +static void child_cb() { child = DONE; } + +void fork_with_atfork_callbacks() { + ASSERT_EQ(__llvm_libc::pthread_atfork(&prepare_cb, &parent_cb, &child_cb), 0); + pid_t pid = __llvm_libc::fork(); + if (pid == 0) { + // Raise a signal from the child if unexpected at-fork + // behavior is observed. + if (child != DONE || prepare != DONE || parent == DONE) + __llvm_libc::raise(SIGUSR1); + return; + } + + ASSERT_TRUE(pid > 0); + int status; + pid_t cpid = __llvm_libc::waitpid(pid, &status, 0); + ASSERT_TRUE(cpid > 0); + ASSERT_EQ(cpid, pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(prepare, DONE); + ASSERT_EQ(parent, DONE); + ASSERT_NE(child, DONE); +} + TEST_MAIN(int argc, char **argv, char **envp) { fork_and_wait_normal_exit(); fork_and_wait4_normal_exit(); @@ -112,5 +146,6 @@ fork_and_wait_signal_exit(); fork_and_wait4_signal_exit(); fork_and_waitpid_signal_exit(); + fork_with_atfork_callbacks(); return 0; }