]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
process-util: Allow setting ret_pid with FORK_DETACH in safe_fork()
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 19 Feb 2025 19:38:14 +0000 (20:38 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 20 Feb 2025 20:00:52 +0000 (21:00 +0100)
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.

src/basic/process-util.c
src/test/test-process-util.c

index 6a55cca62540ba2c29eb776944e348be1a1f46ba..7d86c868c3b1f6805128c2d832774e3fbaa688f7 100644 (file)
@@ -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;
 
index 0db49fe3a8fc5822bf02b6e53413919f317162cb..7bd416f8d0f1211f26887bc115f79d7fda28189a 100644 (file)
@@ -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) {