diff --git a/man/sd_event_add_child.xml b/man/sd_event_add_child.xml
index 4bf07baf59..28ff624516 100644
--- a/man/sd_event_add_child.xml
+++ b/man/sd_event_add_child.xml
@@ -116,8 +116,9 @@
parameter specifies the PID of the process to watch, which must be a direct child process of the invoking
process. The options parameter determines which state changes will be watched for.
It must contain an OR-ed mask of WEXITED (watch for the child process terminating),
- WSTOPPED (watch for the child process being stopped by a signal), and
- WCONTINUED (watch for the child process being resumed by a signal). See
+ WSTOPPED (watch for the child process being stopped by a signal),
+ WCONTINUED (watch for the child process being resumed by a signal) and
+ WNOWAIT (Do not reap the child process after it exits). See
waitid2
for further information.
diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c
index fbe7656925..5794b63aaa 100644
--- a/src/libsystemd/sd-event/sd-event.c
+++ b/src/libsystemd/sd-event/sd-event.c
@@ -48,7 +48,7 @@ static bool EVENT_SOURCE_WATCH_PIDFD(const sd_event_source *s) {
/* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */
return s &&
s->type == SOURCE_CHILD &&
- s->child.options == WEXITED;
+ (s->child.options & ~WNOWAIT) == WEXITED;
}
static bool event_source_is_online(sd_event_source *s) {
@@ -1583,7 +1583,7 @@ _public_ int sd_event_add_child(
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(pid > 1, -EINVAL);
- assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+ assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED|WNOWAIT)), -EINVAL);
assert_return(options != 0, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_origin_changed(e), -ECHILD);
@@ -1675,7 +1675,7 @@ _public_ int sd_event_add_child_pidfd(
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(pidfd >= 0, -EBADF);
- assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+ assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED|WNOWAIT)), -EINVAL);
assert_return(options != 0, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_origin_changed(e), -ECHILD);
@@ -3689,7 +3689,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori
zero(s->child.siginfo);
if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo,
- WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0)
+ WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | (s->child.options & ~WNOWAIT)) < 0)
return negative_errno();
if (s->child.siginfo.si_pid != 0) {
@@ -3737,7 +3737,6 @@ static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) {
/* Note that pidfd would also generate EPOLLHUP when the process gets reaped. But at this point we
* only permit EPOLLIN, under the assumption that upon EPOLLHUP the child source should already
* be set to pending, and we would have returned early above. */
- assert(!s->child.exited);
zero(s->child.siginfo);
if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0)
@@ -4174,10 +4173,11 @@ static int source_dispatch(sd_event_source *s) {
r = s->child.callback(s, &s->child.siginfo, s->userdata);
- /* Now, reap the PID for good. */
+ /* Now, reap the PID for good (unless WNOWAIT was specified by the caller). */
if (zombie) {
- (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED);
- s->child.waited = true;
+ (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED|(s->child.options & WNOWAIT));
+ if (!FLAGS_SET(s->child.options, WNOWAIT))
+ s->child.waited = true;
}
break;
diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c
index 31931aea19..52bdcf7ae2 100644
--- a/src/libsystemd/sd-event/test-event.c
+++ b/src/libsystemd/sd-event/test-event.c
@@ -985,4 +985,84 @@ TEST(defer_add_post) {
ASSERT_TRUE(dispatched_post);
}
+static int child_handler_wnowait(sd_event_source *s, const siginfo_t *si, void *userdata) {
+ int *counter = ASSERT_PTR(userdata);
+
+ (*counter)++;
+
+ if (*counter == 5)
+ ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0));
+
+ return 0;
+}
+
+TEST(child_wnowait) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+ ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD));
+
+ ASSERT_OK(sd_event_default(&e));
+
+ /* Fork a subprocess */
+ pid_t pid;
+ ASSERT_OK_ERRNO(pid = fork());
+
+ if (pid == 0)
+ /* Child process - exit with a specific code */
+ _exit(42);
+
+ /* Add a child source with WNOWAIT flag */
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ int counter = 0;
+ ASSERT_OK(sd_event_add_child(e, &s, pid, WEXITED|WNOWAIT, child_handler_wnowait, &counter));
+ ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_ON));
+
+ /* Run the event loop - this should call the handler */
+ ASSERT_OK(sd_event_loop(e));
+ ASSERT_EQ(counter, 5);
+
+ /* Since we used WNOWAIT, the child should still be waitable */
+ siginfo_t si = {};
+ ASSERT_OK_ERRNO(waitid(P_PID, pid, &si, WEXITED|WNOHANG));
+ ASSERT_EQ(si.si_pid, pid);
+ ASSERT_EQ(si.si_code, CLD_EXITED);
+ ASSERT_EQ(si.si_status, 42);
+}
+
+TEST(child_pidfd_wnowait) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+ ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD));
+
+ ASSERT_OK(sd_event_default(&e));
+
+ /* Fork a subprocess */
+ pid_t pid;
+ ASSERT_OK_ERRNO(pid = fork());
+
+ if (pid == 0)
+ /* Child process - exit with a specific code */
+ _exit(42);
+
+ _cleanup_close_ int pidfd = -EBADF;
+ ASSERT_OK_ERRNO(pidfd = pidfd_open(pid, 0));
+
+ /* Add a child source with WNOWAIT flag */
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ int counter = 0;
+ ASSERT_OK(sd_event_add_child_pidfd(e, &s, pidfd, WEXITED|WNOWAIT, child_handler_wnowait, &counter));
+ ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_ON));
+
+ /* Run the event loop - this should call the handler */
+ ASSERT_OK(sd_event_loop(e));
+ ASSERT_EQ(counter, 5);
+
+ /* Since we used WNOWAIT, the child should still be waitable */
+ siginfo_t si = {};
+ ASSERT_OK_ERRNO(waitid(P_PIDFD, pidfd, &si, WEXITED|WNOHANG));
+ ASSERT_EQ(si.si_pid, pid);
+ ASSERT_EQ(si.si_code, CLD_EXITED);
+ ASSERT_EQ(si.si_status, 42);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);