1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-14 04:58:28 +03:00

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.
This commit is contained in:
Daan De Meyer 2025-02-19 20:38:14 +01:00
parent f48103ea61
commit dc2f960b78
2 changed files with 76 additions and 2 deletions

View 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;

View 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) {