Merge pull request #28123 from poettering/threads-no-more

get rid of any threads in PID 1
This commit is contained in:
Luca Boccassi
2023-06-23 13:24:41 +01:00
committed by GitHub
17 changed files with 484 additions and 212 deletions
-107
View File
@@ -1,107 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <pthread.h>
#include <stddef.h>
#include <unistd.h>
#include "async.h"
#include "errno-util.h"
#include "fd-util.h"
#include "log.h"
#include "macro.h"
#include "process-util.h"
#include "signal-util.h"
int asynchronous_job(void* (*func)(void *p), void *arg) {
sigset_t ss, saved_ss;
pthread_attr_t a;
pthread_t t;
int r, k;
/* It kinda sucks that we have to resort to threads to implement an asynchronous close(), but well, such is
* life. */
r = pthread_attr_init(&a);
if (r > 0)
return -r;
r = pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
if (r > 0) {
r = -r;
goto finish;
}
assert_se(sigfillset(&ss) >= 0);
/* Block all signals before forking off the thread, so that the new thread is started with all signals
* blocked. This way the existence of the new thread won't affect signal handling in other threads. */
r = pthread_sigmask(SIG_BLOCK, &ss, &saved_ss);
if (r > 0) {
r = -r;
goto finish;
}
r = pthread_create(&t, &a, func, arg);
k = pthread_sigmask(SIG_SETMASK, &saved_ss, NULL);
if (r > 0)
r = -r;
else if (k > 0)
r = -k;
else
r = 0;
finish:
pthread_attr_destroy(&a);
return r;
}
int asynchronous_sync(pid_t *ret_pid) {
int r;
/* This forks off an invocation of fork() as a child process, in order to initiate synchronization to
* disk. Note that we implement this as helper process rather than thread as we don't want the sync() to hang our
* original process ever, and a thread would do that as the process can't exit with threads hanging in blocking
* syscalls. */
r = safe_fork("(sd-sync)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS, ret_pid);
if (r < 0)
return r;
if (r == 0) {
/* Child process */
sync();
_exit(EXIT_SUCCESS);
}
return 0;
}
static void *close_thread(void *p) {
(void) pthread_setname_np(pthread_self(), "close");
assert_se(close_nointr(PTR_TO_FD(p)) != -EBADF);
return NULL;
}
int asynchronous_close(int fd) {
int r;
/* This is supposed to behave similar to safe_close(), but
* actually invoke close() asynchronously, so that it will
* never block. Ideally the kernel would have an API for this,
* but it doesn't, so we work around it, and hide this as a
* far away as we can. */
if (fd >= 0) {
PROTECT_ERRNO;
r = asynchronous_job(close_thread, FD_TO_PTR(fd));
if (r < 0)
assert_se(close_nointr(fd) != -EBADF);
}
return -EBADF;
}
-13
View File
@@ -1,13 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "macro.h"
int asynchronous_job(void* (*func)(void *p), void *arg);
int asynchronous_sync(pid_t *ret_pid);
int asynchronous_close(int fd);
DEFINE_TRIVIAL_CLEANUP_FUNC(int, asynchronous_close);
-1
View File
@@ -7,7 +7,6 @@ basic_sources = files(
'architecture.c',
'argv-util.c',
'arphrd-util.c',
'async.c',
'audit-util.c',
'build.c',
'bus-label.c',
+102 -2
View File
@@ -1149,6 +1149,41 @@ static void restore_sigsetp(sigset_t **ssp) {
(void) sigprocmask(SIG_SETMASK, *ssp, NULL);
}
pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) {
size_t ps;
pid_t pid;
void *mystack;
/* A wrapper around glibc's clone() call that automatically sets up a "nested" stack. Only supports
* invocations without CLONE_VM, so that we can continue to use the parent's stack mapping.
*
* Note: glibc's clone() wrapper does not synchronize malloc() locks. This means that if the parent
* is threaded these locks will be in an undefined state in the child, and hence memory allocations
* are likely going to run into deadlocks. Hence: if you use this function make sure your parent is
* strictly single-threaded or your child never calls malloc(). */
assert((flags & (CLONE_VM|CLONE_PARENT_SETTID|CLONE_CHILD_SETTID|
CLONE_CHILD_CLEARTID|CLONE_SETTLS)) == 0);
/* We allocate some space on the stack to use as the stack for the child (hence "nested"). Note that
* the net effect is that the child will have the start of its stack inside the stack of the parent,
* but since they are a CoW copy of each other that's fine. We allocate one page-aligned page. But
* since we don't want to deal with differences between systems where the stack grows backwards or
* forwards we'll allocate one more and place the stack address in the middle. Except that we also
* want it page aligned, hence we'll allocate one page more. Makes 3. */
ps = page_size();
mystack = alloca(ps*3);
mystack = (uint8_t*) mystack + ps; /* move pointer one page ahead since stacks usually grow backwards */
mystack = (void*) ALIGN_TO((uintptr_t) mystack, ps); /* align to page size (moving things further ahead) */
pid = clone(fn, mystack, flags, userdata);
if (pid < 0)
return -errno;
return pid;
}
int safe_fork_full(
const char *name,
const int stdio_fds[3],
@@ -1160,9 +1195,12 @@ int safe_fork_full(
pid_t original_pid, pid;
sigset_t saved_ss, ss;
_unused_ _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL;
bool block_signals = false, block_all = false;
bool block_signals = false, block_all = false, intermediary = false;
int prio, r;
assert(!FLAGS_SET(flags, FORK_DETACH) || !ret_pid);
assert(!FLAGS_SET(flags, FORK_DETACH|FORK_WAIT));
/* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always
* returns the child's PID in *ret_pid. Returns == 0 in the child, and > 0 in the parent. */
@@ -1196,6 +1234,31 @@ int safe_fork_full(
saved_ssp = &saved_ss;
}
if (FLAGS_SET(flags, FORK_DETACH)) {
assert(!FLAGS_SET(flags, FORK_WAIT));
assert(!ret_pid);
/* Fork off intermediary child if needed */
r = is_reaper_process();
if (r < 0)
return log_full_errno(prio, r, "Failed to determine if we are a reaper process: %m");
if (!r) {
/* Not a reaper process, hence do a double fork() so we are reparented to one */
pid = fork();
if (pid < 0)
return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name));
if (pid > 0) {
log_debug("Successfully forked off intermediary '%s' as PID " PID_FMT ".", strna(name), pid);
return 1; /* return in the parent */
}
intermediary = true;
}
}
if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS)) != 0)
pid = raw_clone(SIGCHLD|
(FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) |
@@ -1205,8 +1268,12 @@ int safe_fork_full(
if (pid < 0)
return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name));
if (pid > 0) {
/* We are in the parent process */
/* If we are in the intermediary process, exit now */
if (intermediary)
_exit(EXIT_SUCCESS);
/* We are in the parent process */
log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid);
if (flags & FORK_WAIT) {
@@ -1623,6 +1690,39 @@ int get_process_threads(pid_t pid) {
return n;
}
int is_reaper_process(void) {
int b = 0;
/* Checks if we are running in a reaper process, i.e. if we are expected to deal with processes
* reparented to us. This simply checks if we are PID 1 or if PR_SET_CHILD_SUBREAPER was called. */
if (getpid_cached() == 1)
return true;
if (prctl(PR_GET_CHILD_SUBREAPER, (unsigned long) &b, 0UL, 0UL, 0UL) < 0)
return -errno;
return b != 0;
}
int make_reaper_process(bool b) {
if (getpid_cached() == 1) {
if (!b)
return -EINVAL;
return 0;
}
/* Some prctl()s insist that all 5 arguments are specified, others do not. Let's always specify all,
* to avoid any ambiguities */
if (prctl(PR_SET_CHILD_SUBREAPER, (unsigned long) b, 0UL, 0UL, 0UL) < 0)
return -errno;
return 0;
}
static const char *const sigchld_code_table[] = {
[CLD_EXITED] = "exited",
[CLD_KILLED] = "killed",
+13 -2
View File
@@ -139,6 +139,13 @@ void reset_cached_pid(void);
int must_be_root(void);
pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata);
/* 💣 Note that FORK_NEW_USERNS + FORK_NEW_MOUNTNS should not be called in threaded programs, because they
* cause us to use raw_clone() which does not synchronize the glibc malloc() locks, and thus will cause
* deadlocks if the parent uses threads and the child does memory allocations. Hence: if the parent is
* threaded these flags may not be used. These flags cannot be used if the parent uses threads or the child
* uses malloc(). 💣 */
typedef enum ForkFlags {
FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */
FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */
@@ -148,15 +155,16 @@ typedef enum ForkFlags {
FORK_REOPEN_LOG = 1 << 5, /* Reopen log connection */
FORK_LOG = 1 << 6, /* Log above LOG_DEBUG log level about failures */
FORK_WAIT = 1 << 7, /* Wait until child exited */
FORK_NEW_MOUNTNS = 1 << 8, /* Run child in its own mount namespace */
FORK_NEW_MOUNTNS = 1 << 8, /* Run child in its own mount namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
FORK_MOUNTNS_SLAVE = 1 << 9, /* Make child's mount namespace MS_SLAVE */
FORK_PRIVATE_TMP = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */
FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
FORK_STDOUT_TO_STDERR = 1 << 12, /* Make stdout a copy of stderr */
FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */
FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace */
FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
FORK_CLOEXEC_OFF = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
FORK_DETACH = 1 << 17, /* Double fork if needed to ensure PID1/subreaper is parent */
} ForkFlags;
int safe_fork_full(
@@ -199,3 +207,6 @@ int setpriority_closest(int priority);
_noreturn_ void freeze(void);
int get_process_threads(pid_t pid);
int is_reaper_process(void);
int make_reaper_process(bool b);
-1
View File
@@ -4,7 +4,6 @@
#include <errno.h>
#include <fcntl.h>
#include <linux/random.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+5
View File
@@ -27,6 +27,11 @@
* Additionally, as this function does not pass the ptid, newtls and ctid parameters to the kernel, flags must not
* contain CLONE_PARENT_SETTID, CLONE_CHILD_SETTID, CLONE_CHILD_CLEARTID or CLONE_SETTLS.
*
* WARNING: 💣 this call (just like glibc's own clone() wrapper) will not synchronize on glibc's malloc
* locks, which means they will be in an undefined state in the child if the parent is
* threaded. This means: the parent must either never use threads, or the child cannot use memory
* allocation itself. This is a major pitfall, hence be careful! 💣
*
* Returns: 0 in the child process and the child process id in the parent.
*/
static inline pid_t raw_clone(unsigned long flags) {