From: Daan De Meyer Date: Wed, 19 Feb 2025 19:38:14 +0000 (+0100) Subject: process-util: Allow setting ret_pid with FORK_DETACH in safe_fork() X-Git-Tag: v258-rc1~1281^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=dc2f960b78e07b995e1e01202b0ca3f25868c724;p=thirdparty%2Fsystemd.git process-util: Allow setting ret_pid with FORK_DETACH in safe_fork() Let's allow getting the pid even if the caller sets FORK_DETACH. We do this via a socketpair() over which we send the inner child pid. --- diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 6a55cca6254..7d86c868c3b 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -36,6 +36,7 @@ #include "fs-util.h" #include "hostname-util.h" #include "io-util.h" +#include "iovec-util.h" #include "locale-util.h" #include "log.h" #include "macro.h" @@ -53,6 +54,7 @@ #include "raw-clone.h" #include "rlimit-util.h" #include "signal-util.h" +#include "socket-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" @@ -1537,10 +1539,11 @@ int pidref_safe_fork_full( sigset_t saved_ss, ss; _unused_ _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; bool block_signals = false, block_all = false, intermediary = false; + _cleanup_close_pair_ int pidref_transport_fds[2] = EBADF_PAIR; int prio, r; assert(!FLAGS_SET(flags, FORK_DETACH) || - (!ret_pid && (flags & (FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL)) == 0)); + (flags & (FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL)) == 0); /* A wrapper around fork(), that does a couple of important initializations in addition to mere * forking. If provided, ret_pid is initialized in both the parent and the child process, both times @@ -1587,14 +1590,43 @@ int pidref_safe_fork_full( if (!r) { /* Not a reaper process, hence do a double fork() so we are reparented to one */ + if (ret_pid && socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pidref_transport_fds) < 0) + return log_full_errno(prio, errno, "Failed to allocate pidref socket: %m"); + 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); + + pidref_transport_fds[1] = safe_close(pidref_transport_fds[1]); + + if (pidref_transport_fds[0] >= 0) { + /* Wait for the intermediary child to exit so the caller can be certain the actual child + * process has been reparented by the time this function returns. */ + r = wait_for_terminate_and_check(name, pid, FLAGS_SET(flags, FORK_LOG) ? WAIT_LOG : 0); + if (r < 0) + return log_full_errno(prio, r, "Failed to wait for intermediary process: %m"); + if (r != EXIT_SUCCESS) /* exit status > 0 should be treated as failure, too */ + return -EPROTO; + + int pidfd; + ssize_t n = receive_one_fd_iov( + pidref_transport_fds[0], + &IOVEC_MAKE(&pid, sizeof(pid)), + /* iovlen= */ 1, + /* flags= */ 0, + &pidfd); + if (n < 0) + return log_full_errno(prio, n, "Failed to receive child pidref: %m"); + + *ret_pid = (PidRef) { .pid = pid, .fd = pidfd }; + } + return 1; /* return in the parent */ } + pidref_transport_fds[0] = safe_close(pidref_transport_fds[0]); intermediary = true; } } @@ -1612,8 +1644,30 @@ int pidref_safe_fork_full( if (pid > 0) { /* If we are in the intermediary process, exit now */ - if (intermediary) + if (intermediary) { + if (pidref_transport_fds[1] >= 0) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + r = pidref_set_pid(&pidref, pid); + if (r < 0) { + log_full_errno(prio, r, "Failed to open reference to PID "PID_FMT": %m", pid); + _exit(EXIT_FAILURE); + } + + r = send_one_fd_iov( + pidref_transport_fds[1], + pidref.fd, + &IOVEC_MAKE(&pidref.pid, sizeof(pidref.pid)), + /* iovlen= */ 1, + /* flags= */ 0); + if (r < 0) { + log_full_errno(prio, r, "Failed to send child pidref: %m"); + _exit(EXIT_FAILURE); + } + } + _exit(EXIT_SUCCESS); + } /* We are in the parent process */ log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid); @@ -1654,6 +1708,8 @@ int pidref_safe_fork_full( /* We are in the child process */ + pidref_transport_fds[1] = safe_close(pidref_transport_fds[1]); + /* Restore signal mask manually */ saved_ssp = NULL; diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 0db49fe3a8f..7bd416f8d0f 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -659,6 +659,24 @@ TEST(safe_fork) { ASSERT_OK(wait_for_terminate(pid, &status)); ASSERT_EQ(status.si_code, CLD_EXITED); ASSERT_EQ(status.si_status, 88); + + _cleanup_(pidref_done) PidRef child = PIDREF_NULL; + r = pidref_safe_fork("(test-child)", FORK_DETACH, &child); + if (r == 0) { + /* Don't freeze so this doesn't linger around forever in case something goes wrong. */ + usleep_safe(100 * USEC_PER_SEC); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK_POSITIVE(r); + ASSERT_GT(child.pid, 0); + ASSERT_OK(pidref_get_ppid(&child, &pid)); + ASSERT_OK(pidref_kill(&child, SIGKILL)); + + if (is_reaper_process()) + ASSERT_EQ(pid, getpid_cached()); + else + ASSERT_NE(pid, getpid_cached()); } TEST(pid_to_ptr) {