tree-wide: introduce new safe_fork() helper and port everything over
This adds a new safe_fork() wrapper around fork() and makes use of it everywhere. The new wrapper does a couple of things we previously did manually and separately in a safer, more correct and automatic way: 1. Optionally resets signal handlers/mask in the child 2. Sets a name on all processes we fork off right after forking off (and the patch assigns useful names for all processes we fork off now, following a systematic naming scheme: always enclosed in () – in order to indicate that these are not proper, exec()ed processes, but only forked off children, and if the process is long-running with only our own code, without execve()'ing something else, it gets am "sd-" prefix.) 3. Optionally closes all file descriptors in the child 4. Optionally sets a PR_SET_DEATHSIG to SIGTERM in the child, in a safe way so that the parent dying before this happens being handled safely. 5. Optionally reopens the logs 6. Optionally connects stdin/stdout/stderr to /dev/null 7. Debug logs about the forked off processes.
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
#include "stat-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-util.h"
|
||||
#include "util.h"
|
||||
|
||||
@@ -1134,6 +1135,142 @@ int must_be_root(void) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
int safe_fork_full(
|
||||
const char *name,
|
||||
const int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
pid_t *ret_pid) {
|
||||
|
||||
pid_t original_pid, pid;
|
||||
sigset_t saved_ss;
|
||||
bool block_signals;
|
||||
int r;
|
||||
|
||||
/* 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. */
|
||||
|
||||
original_pid = getpid_cached();
|
||||
|
||||
block_signals = flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG);
|
||||
|
||||
if (block_signals) {
|
||||
sigset_t ss;
|
||||
|
||||
/* We temporarily block all signals, so that the new child has them blocked initially. This way, we can be sure
|
||||
* that SIGTERMs are not lost we might send to the child. */
|
||||
if (sigfillset(&ss) < 0)
|
||||
return log_debug_errno(errno, "Failed to reset signal set: %m");
|
||||
|
||||
if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0)
|
||||
return log_debug_errno(errno, "Failed to reset signal mask: %m");
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
r = -errno;
|
||||
|
||||
if (block_signals) /* undo what we did above */
|
||||
(void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
|
||||
|
||||
return log_debug_errno(r, "Failed to fork: %m");
|
||||
}
|
||||
if (pid > 0) {
|
||||
/* We are in the parent process */
|
||||
|
||||
if (block_signals) /* undo what we did above */
|
||||
(void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
|
||||
|
||||
log_debug("Sucessfully forked off '%s' as PID " PID_FMT ".", strna(name), pid);
|
||||
|
||||
if (ret_pid)
|
||||
*ret_pid = pid;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* We are in the child process */
|
||||
|
||||
if (flags & FORK_REOPEN_LOG) {
|
||||
/* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
|
||||
log_close();
|
||||
log_set_open_when_needed(true);
|
||||
}
|
||||
|
||||
if (name) {
|
||||
r = rename_process(name);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to rename process, ignoring: %m");
|
||||
}
|
||||
|
||||
if (flags & FORK_DEATHSIG)
|
||||
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) {
|
||||
log_debug_errno(errno, "Failed to set death signal: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (flags & FORK_RESET_SIGNALS) {
|
||||
r = reset_all_signal_handlers();
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to reset signal handlers: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* This implicitly undoes the signal mask stuff we did before the fork()ing above */
|
||||
r = reset_signal_mask();
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to reset signal mask: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (block_signals) { /* undo what we did above */
|
||||
if (sigprocmask(SIG_SETMASK, &saved_ss, NULL) < 0) {
|
||||
log_debug_errno(errno, "Failed to restore signal mask: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FORK_DEATHSIG) {
|
||||
/* Let's see if the parent PID is still the one we started from? If not, then the parent
|
||||
* already died by the time we set PR_SET_PDEATHSIG, hence let's emulate the effect */
|
||||
|
||||
if (getppid() != original_pid) {
|
||||
log_debug("Parent died early, raising SIGTERM.");
|
||||
(void) raise(SIGTERM);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FORK_CLOSE_ALL_FDS) {
|
||||
/* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
|
||||
log_close();
|
||||
|
||||
r = close_all_fds(except_fds, n_except_fds);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to close all file descriptors: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* When we were asked to reopen the logs, do so again now */
|
||||
if (flags & FORK_REOPEN_LOG) {
|
||||
log_open();
|
||||
log_set_open_when_needed(false);
|
||||
}
|
||||
|
||||
if (flags & FORK_NULL_STDIO) {
|
||||
r = make_null_stdio();
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to connect stdin/stdout to /dev/null: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret_pid)
|
||||
*ret_pid = getpid_cached();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *const ioprio_class_table[] = {
|
||||
[IOPRIO_CLASS_NONE] = "none",
|
||||
[IOPRIO_CLASS_RT] = "realtime",
|
||||
|
||||
Reference in New Issue
Block a user