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:
parent
f48103ea61
commit
dc2f960b78
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user