#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"
#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"
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
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;
}
}
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);
/* We are in the child process */
+ pidref_transport_fds[1] = safe_close(pidref_transport_fds[1]);
+
/* Restore signal mask manually */
saved_ssp = NULL;
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) {