* The bind(AF_UNSPEC) construct (for resetting sockets to their initial state)
should be blocked in many cases because it punches holes in many sandboxes.
-* find a nice way to opt-in into auto-masking SIGCHLD on first
- sd_event_add_child(), and then get rid of many more explicit sigprocmask()
- calls.
-
* introduce new structure Tpm2CombinedPolicy, that combines the various TPm2
policy bits into one structure, i.e. public key info, pcr masks, pcrlock
stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal().
pid_t pid = fork();
assert(pid >= 0);
- /* SIGCHLD signal must be blocked for sd_event_add_child to work */
- sigset_t ss;
- sigemptyset(&ss);
- sigaddset(&ss, SIGCHLD);
- sigprocmask(SIG_BLOCK, &ss, NULL);
-
if (pid == 0) /* child */
sleep(1);
<constant>SD_EVENT_OFF</constant> with
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
- <para>The <constant>SIGCHLD</constant> signal must be blocked in all threads before this function is
- called (using <citerefentry
- project='man-pages'><refentrytitle>sigprocmask</refentrytitle><manvolnum>2</manvolnum></citerefentry> or
- <citerefentry
- project='man-pages'><refentrytitle>pthread_sigmask</refentrytitle><manvolnum>3</manvolnum></citerefentry>).</para>
+ <para>The kernel autoreaping logic must be disabled for this function to work as expected (see
+ <citerefentry project='man-pages'><refentrytitle>signal</refentrytitle><manvolnum>7</manvolnum></citerefentry>).</para>
+
+ <para>When watching for <constant>WSTOPPED</constant> or <constant>WCONTINUED</constant>, the
+ <constant>SIGCHLD</constant> signal must be blocked in all threads before this function is called (using
+ <citerefentry project='man-pages'><refentrytitle>sigprocmask</refentrytitle><manvolnum>2</manvolnum></citerefentry> or
+ <citerefentry project='man-pages'><refentrytitle>pthread_sigmask</refentrytitle><manvolnum>3</manvolnum></citerefentry>).</para>
<para>If the second parameter of <function>sd_event_add_child()</function> is passed as
<constant>NULL</constant> no reference to the event source object is returned. In this case, the event
regardless which of <function>sd_event_add_child()</function> and
<function>sd_event_add_child_pidfd()</function> is used for allocating an event source, the watched
process has to be a direct child process of the invoking process. Also in both cases
- <constant>SIGCHLD</constant> has to be blocked in the invoking process.</para>
+ <constant>SIGCHLD</constant> has to be blocked in the invoking process when watching for
+ <constant>WSTOPPED</constant> or <constant>WCONTINUED</constant> and the kernel autoreaping logic has to
+ be disabled.</para>
<para><function>sd_event_source_get_child_pid()</function>
retrieves the configured PID of a child process state change event
return RET_NERRNO(sigismember(&ss, sig));
}
+int autoreaping_enabled(void) {
+ struct sigaction sa;
+
+ if (sigaction(SIGCHLD, /* __act= */ NULL, &sa) < 0)
+ return -errno;
+
+ return sa.sa_handler == SIG_IGN || FLAGS_SET(sa.sa_flags, SA_NOCLDWAIT);
+}
+
int pop_pending_signal_internal(int sig, ...) {
sigset_t ss;
va_list ap;
int signal_is_blocked(int sig);
+int autoreaping_enabled(void);
+
int pop_pending_signal_internal(int sig, ...);
#define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1)
return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
}
+static int verify_sigchld(int options) {
+ int r;
+
+ if ((options & (WSTOPPED|WCONTINUED)) != 0) {
+ /* Caller must block SIGCHLD before using us to watch for WSTOPPED or WCONTINUED. */
+
+ r = signal_is_blocked(SIGCHLD);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EBUSY;
+ }
+
+ /* We don't want the Linux autoreaping logic to take effect when we're watching for process exit, so
+ * check if it is enabled. */
+
+ if (options & WEXITED) {
+ r = autoreaping_enabled();
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
_public_ int sd_event_add_child(
sd_event *e,
sd_event_source **ret,
if (!callback)
callback = child_exit_callback;
+ /* As an optimization we only do these checks on the first child event source created. */
if (e->n_online_child_sources == 0) {
- /* Caller must block SIGCHLD before using us to watch children, even if pidfd is available,
- * for compatibility with pre-pidfd and because we don't want the reap the child processes
- * ourselves, i.e. call waitid(), and don't want Linux' default internal logic for that to
- * take effect.
- *
- * (As an optimization we only do this check on the first child event source created.) */
- r = signal_is_blocked(SIGCHLD);
+ r = verify_sigchld(options);
if (r < 0)
return r;
- if (r == 0)
- return -EBUSY;
}
r = hashmap_ensure_allocated(&e->child_sources, NULL);
if (!s)
return -ENOMEM;
- /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we
- * pin the PID, and make regular waitid() handling race-free. */
+ /* We always take a pidfd here, even if we wait for anything else than WEXITED, so that we pin the
+ * PID, and make regular waitid() handling race-free. */
s->child.pidfd = pidfd_open(pid, 0);
if (s->child.pidfd < 0)
callback = child_exit_callback;
if (e->n_online_child_sources == 0) {
- r = signal_is_blocked(SIGCHLD);
+ r = verify_sigchld(options);
if (r < 0)
return r;
- if (r == 0)
- return -EBUSY;
}
r = hashmap_ensure_allocated(&e->child_sources, NULL);
#include "sd-event.h"
#include "alloc-util.h"
+#include "event-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "log.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidfd-util.h"
+#include "pidref.h"
#include "process-util.h"
#include "random-util.h"
#include "rm-rf.h"
}
}
+TEST(child_autoreap_ebusy) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
+
+ /* Test that sd_event_add_child() fails with EBUSY when kernel autoreaping is enabled
+ * by setting SIGCHLD disposition to SIG_IGN */
+
+ ASSERT_OK(sd_event_new(&e));
+
+ /* First, verify that adding a child source works with default signal disposition */
+ ASSERT_OK_POSITIVE(pidref_safe_fork("(child-autoreaping-ebusy)", FORK_DEATHSIG_SIGKILL|FORK_FREEZE, &pidref));
+
+ ASSERT_OK(event_add_child_pidref(e, &s, &pidref, WEXITED, NULL, NULL));
+ s = sd_event_source_unref(s);
+
+ /* Now set SIGCHLD to SIG_IGN to enable kernel autoreaping */
+ struct sigaction old_sa, new_sa = {};
+ new_sa.sa_handler = SIG_IGN;
+ ASSERT_OK_ERRNO(sigaction(SIGCHLD, &new_sa, &old_sa));
+
+ ASSERT_ERROR(event_add_child_pidref(e, &s, &pidref, WEXITED, NULL, NULL), EBUSY);
+
+ /* Restore original SIGCHLD disposition */
+ ASSERT_OK_ERRNO(sigaction(SIGCHLD, &old_sa, NULL));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);