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/man/sd_event_set_exit_on_idle.xml b/man/sd_event_set_exit_on_idle.xml new file mode 100644 index 0000000000..1dd1042ad4 --- /dev/null +++ b/man/sd_event_set_exit_on_idle.xml @@ -0,0 +1,116 @@ + + + + + + + + sd_event_set_exit_on_idle + systemd + + + + sd_event_set_exit_on_idle + 3 + + + + sd_event_set_exit_on_idle + sd_event_get_exit_on_idle + + Enable event loop exit-on-idle support + + + + + #include <systemd/sd-event.h> + + + int sd_event_set_exit_on_idle + sd_event *event + int b + + + + int sd_event_get_exit_on_idle + sd_event *event + + + + + + + Description + + sd_event_set_exit_on_idle() may be used to + enable or disable the exit-on-idle support in the + event loop object specified in the event + parameter. If enabled, the event loop will exit with a zero exit code + there are no more enabled (SD_EVENT_ON, SD_EVENT_ONESHOT), + non-exit, non-post event sources. + + sd_event_get_exit_on_idle() may be used to + determine whether exit-on-idle support was previously requested by a + call to sd_event_set_exit_on_idle() with a true + b parameter and successfully enabled. + + + + Return Value + + On success, sd_event_set_exit_on_idle() and + sd_event_get_exit_on_idle() return a non-zero positive integer if the exit-on-idle + support was successfully enabled. They return zero if the exit-on-idle support was explicitly disabled + with a false b parameter. On failure, they return a negative errno-style error + code. + + + Errors + + Returned errors may indicate the following problems: + + + + + -ECHILD + + The event loop has been created in a different process, library or module instance. + + + + -EINVAL + + The passed event loop object was invalid. + + + + + + + + + + History + sd_event_set_exit_on_idle() and + sd_event_get_exit_on_idle() were added in version 259. + + + + See Also + + + systemd1 + sd-event3 + sd_event_new3 + sd_event_add_io3 + sd_event_add_time3 + sd_event_add_signal3 + sd_event_add_child3 + sd_event_add_inotify3 + sd_event_add_defer3 + systemd.service5 + + + + diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index dae8788140..0f8c3ea06c 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -94,6 +94,7 @@ typedef enum TimestampStyle TimestampStyle; typedef enum UnitActiveState UnitActiveState; typedef enum UnitDependency UnitDependency; typedef enum UnitType UnitType; +typedef enum WaitFlags WaitFlags; typedef struct Hashmap Hashmap; typedef struct HashmapBase HashmapBase; diff --git a/src/basic/log-context.c b/src/basic/log-context.c index 27bce6f1f6..334fe95fc3 100644 --- a/src/basic/log-context.c +++ b/src/basic/log-context.c @@ -58,7 +58,9 @@ static LogContext* log_context_detach(LogContext *c) { LogContext* log_context_new(const char *key, const char *value) { assert(key); assert(endswith(key, "=")); - assert(value); + + if (!value) + return NULL; LIST_FOREACH(ll, i, _log_context) if (i->key == key && i->value == value) diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index d0e000bd0c..a1fa794264 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1081,5 +1081,7 @@ global: LIBSYSTEMD_259 { global: + sd_event_set_exit_on_idle; + sd_event_get_exit_on_idle; sd_varlink_is_connected; } LIBSYSTEMD_258; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index d8cd1ba7df..94b178eafd 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) { @@ -157,6 +157,7 @@ struct sd_event { bool need_process_child:1; bool watchdog:1; bool profile_delays:1; + bool exit_on_idle:1; int exit_code; @@ -1583,7 +1584,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 +1676,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); @@ -2987,9 +2988,11 @@ static int event_source_online( break; case SOURCE_MEMORY_PRESSURE: - r = source_memory_pressure_register(s, enabled); - if (r < 0) - return r; + if (s->memory_pressure.write_buffer_size == 0) { + r = source_memory_pressure_register(s, enabled); + if (r < 0) + return r; + } break; @@ -3042,16 +3045,8 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { if (m == SD_EVENT_OFF) r = event_source_offline(s, m, s->ratelimited); - else { - if (s->enabled != SD_EVENT_OFF) { - /* Switching from "on" to "oneshot" or back? If that's the case, we can take a shortcut, the - * event source is already enabled after all. */ - s->enabled = m; - return 0; - } - + else r = event_source_online(s, m, s->ratelimited); - } if (r < 0) return r; @@ -3695,7 +3690,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) { @@ -3743,7 +3738,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) @@ -4083,6 +4077,22 @@ static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { return 0; /* go on, dispatch to user callback */ } +static int mark_post_sources_pending(sd_event *e) { + sd_event_source *z; + int r; + + SET_FOREACH(z, e->post_sources) { + if (event_source_is_offline(z)) + continue; + + r = source_set_pending(z, true); + if (r < 0) + return r; + } + + return 0; +} + static int source_dispatch(sd_event_source *s) { EventSourceType saved_type; sd_event *saved_event; @@ -4110,25 +4120,23 @@ static int source_dispatch(sd_event_source *s) { return 1; } - if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + if (IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + /* Make sure this event source is moved to the end of the priority list now. We do this here + * because defer and exit event sources are always pending from the moment they're added so + * the same logic in source_set_pending() is never triggered. */ + s->pending_iteration = s->event->iteration; + event_source_pp_prioq_reshuffle(s); + } else { r = source_set_pending(s, false); if (r < 0) return r; } if (s->type != SOURCE_POST) { - sd_event_source *z; - /* If we execute a non-post source, let's mark all post sources as pending. */ - - SET_FOREACH(z, s->event->post_sources) { - if (event_source_is_offline(z)) - continue; - - r = source_set_pending(z, true); - if (r < 0) - return r; - } + r = mark_post_sources_pending(s->event); + if (r < 0) + return r; } if (s->type == SOURCE_MEMORY_PRESSURE) { @@ -4172,10 +4180,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; @@ -4237,6 +4246,14 @@ static int source_dispatch(sd_event_source *s) { s->dispatching = false; + if (saved_type != SOURCE_POST) { + /* More post sources might have been added while executing the callback, let's make sure + * those are marked pending as well. */ + r = mark_post_sources_pending(saved_event); + if (r < 0) + return r; + } + finish: if (r < 0) { log_debug_errno(r, "Event source %s (type %s) returned error, %s: %m", @@ -4412,6 +4429,28 @@ static int event_memory_pressure_write_list(sd_event *e) { return 0; } +static bool event_loop_idle(sd_event *e) { + assert(e); + + LIST_FOREACH(sources, s, e->sources) { + /* Exit sources only trigger on exit, so whether they're enabled or not doesn't matter when + * we're deciding if the event loop is idle or not. */ + if (s->type == SOURCE_EXIT) + continue; + + if (s->enabled == SD_EVENT_OFF) + continue; + + /* Post event sources always need another active event source to become pending. */ + if (s->type == SOURCE_POST && !s->pending) + continue; + + return false; + } + + return true; +} + _public_ int sd_event_prepare(sd_event *e) { int r; @@ -4429,6 +4468,9 @@ _public_ int sd_event_prepare(sd_event *e) { /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ PROTECT_EVENT(e); + if (!e->exit_requested && e->exit_on_idle && event_loop_idle(e)) + (void) sd_event_exit(e, 0); + if (e->exit_requested) goto pending; @@ -5233,6 +5275,22 @@ _public_ int sd_event_set_signal_exit(sd_event *e, int b) { return change; } +_public_ int sd_event_set_exit_on_idle(sd_event *e, int b) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_origin_changed(e), -ECHILD); + + return e->exit_on_idle = b; +} + +_public_ int sd_event_get_exit_on_idle(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_origin_changed(e), -ECHILD); + + return e->exit_on_idle; +} + _public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index 77cf7af541..eb986c9e08 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -946,4 +946,225 @@ TEST(leave_ratelimit) { ASSERT_TRUE(manually_left_ratelimit); } +static int defer_post_handler(sd_event_source *s, void *userdata) { + bool *dispatched_post = ASSERT_PTR(userdata); + + *dispatched_post = true; + + return 0; +} + +static int defer_adds_post_handler(sd_event_source *s, void *userdata) { + sd_event *e = sd_event_source_get_event(s); + + /* Add a post event source from within the defer handler */ + ASSERT_OK(sd_event_add_post(e, NULL, defer_post_handler, userdata)); + + return 0; +} + +TEST(defer_add_post) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + bool dispatched_post = false; + + ASSERT_OK(sd_event_default(&e)); + + /* Add a oneshot defer event source that will add a post event source */ + ASSERT_OK(sd_event_add_defer(e, NULL, defer_adds_post_handler, &dispatched_post)); + + /* Run one iteration - this should dispatch the defer handler */ + ASSERT_OK_POSITIVE(sd_event_run(e, UINT64_MAX)); + + /* The post handler should have been added but not yet dispatched */ + ASSERT_FALSE(dispatched_post); + + /* Run another iteration - this should dispatch the post handler */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Now the post handler should have been dispatched */ + 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); +} + +static int exit_on_idle_defer_handler(sd_event_source *s, void *userdata) { + unsigned *c = ASSERT_PTR(userdata); + + /* Should not be reached on third call because the event loop should exit before */ + ASSERT_LT(*c, 2u); + + (*c)++; + + /* Disable ourselves, which should trigger exit-on-idle after the second iteration */ + if (*c == 2) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + + return 0; +} + +static int exit_on_idle_post_handler(sd_event_source *s, void *userdata) { + unsigned *c = ASSERT_PTR(userdata); + + /* Should not be reached on third call because the event loop should exit before */ + ASSERT_LT(*c, 2u); + + (*c)++; + return 0; +} + +static int exit_on_idle_exit_handler(sd_event_source *s, void *userdata) { + return 0; +} + +TEST(exit_on_idle) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + ASSERT_OK_POSITIVE(sd_event_get_exit_on_idle(e)); + + /* Create a recurring defer event source. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *d = NULL; + unsigned dc = 0; + ASSERT_OK(sd_event_add_defer(e, &d, exit_on_idle_defer_handler, &dc)); + ASSERT_OK(sd_event_source_set_enabled(d, SD_EVENT_ON)); + + /* This post event source should not keep the event loop running after the defer source is disabled. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *p = NULL; + unsigned pc = 0; + ASSERT_OK(sd_event_add_post(e, &p, exit_on_idle_post_handler, &pc)); + ASSERT_OK(sd_event_source_set_enabled(p, SD_EVENT_ON)); + ASSERT_OK(sd_event_source_set_priority(p, SD_EVENT_PRIORITY_IMPORTANT)); + + /* And neither should this exit event source. */ + ASSERT_OK(sd_event_add_exit(e, NULL, exit_on_idle_exit_handler, NULL)); + + /* Run the event loop - it should exit after we disable the event source */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(dc, 2u); + ASSERT_EQ(pc, 2u); +} + +TEST(exit_on_idle_no_sources) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Running loop with no sources should return immediately with success */ + ASSERT_OK(sd_event_loop(e)); +} + +static int defer_fair_handler(sd_event_source *s, void *userdata) { + unsigned *counter = ASSERT_PTR(userdata); + + /* If we're about to increment above 5, exit the event loop */ + if (*counter >= 5) + return sd_event_exit(sd_event_source_get_event(s), 0); + + (*counter)++; + + return 0; +} + +TEST(defer_fair_scheduling) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + sd_event_source *sources[5] = {}; + unsigned counters[5] = {}; + + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create 5 defer sources with equal priority */ + for (unsigned i = 0; i < 5; i++) { + ASSERT_OK(sd_event_add_defer(e, &sources[i], defer_fair_handler, &counters[i])); + ASSERT_OK(sd_event_source_set_enabled(sources[i], SD_EVENT_ON)); + } + + /* Run the event loop until one of the handlers exits */ + ASSERT_OK(sd_event_loop(e)); + + /* All counters should be equal to 5, demonstrating fair scheduling */ + for (unsigned i = 0; i < 5; i++) { + ASSERT_EQ(counters[i], 5u); + sd_event_source_unref(sources[i]); + } +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index ac98b67da4..7f0c444cfa 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -116,6 +116,8 @@ int sd_event_set_watchdog(sd_event *e, int b); int sd_event_get_watchdog(sd_event *e); int sd_event_get_iteration(sd_event *e, uint64_t *ret); int sd_event_set_signal_exit(sd_event *e, int b); +int sd_event_set_exit_on_idle(sd_event *e, int b); +int sd_event_get_exit_on_idle(sd_event *e); sd_event_source* sd_event_source_ref(sd_event_source *s); sd_event_source* sd_event_source_unref(sd_event_source *s); diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c index fea0733173..997bcf2215 100644 --- a/src/test/test-cgroup-util.c +++ b/src/test/test-cgroup-util.c @@ -527,7 +527,9 @@ TEST(cgroupid) { fd2 = cg_cgroupid_open(fd, id); - if (ERRNO_IS_NEG_PRIVILEGE(fd2)) + /* The kernel converts a bunch of errors to ESTALE in the open_by_handle_at() codepath so we treat + * it as missing privs but it could be absolutely anything really. */ + if (ERRNO_IS_NEG_PRIVILEGE(fd2) || fd2 == -ESTALE) log_notice("Skipping open-by-cgroup-id test because lacking privs."); else if (ERRNO_IS_NEG_NOT_SUPPORTED(fd2)) log_notice("Skipping open-by-cgroup-id test because syscall is missing or blocked."); diff --git a/src/test/test-id128.c b/src/test/test-id128.c index 0ecdf76e80..9c28e82f5c 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -8,6 +8,7 @@ #include "sd-id128.h" #include "alloc-util.h" +#include "capability-util.h" #include "fd-util.h" #include "id128-util.h" #include "path-util.h" @@ -278,7 +279,7 @@ TEST(id128_at) { ASSERT_OK(sd_id128_randomize(&id)); ASSERT_OK(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id)); - if (geteuid() == 0) + if (have_effective_cap(CAP_DAC_OVERRIDE)) ASSERT_OK(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id)); else ASSERT_ERROR(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id), EACCES); diff --git a/src/test/test-rm-rf.c b/src/test/test-rm-rf.c index af369d81f7..870a8b9a4e 100644 --- a/src/test/test-rm-rf.c +++ b/src/test/test-rm-rf.c @@ -3,6 +3,7 @@ #include #include +#include "capability-util.h" #include "process-util.h" #include "rm-rf.h" #include "string-util.h" @@ -29,7 +30,8 @@ static void test_rm_rf_chmod_inner(void) { ASSERT_OK_ERRNO(chmod(x, 0500)); ASSERT_OK_ERRNO(chmod(d, 0500)); - ASSERT_ERROR(rm_rf(d, REMOVE_PHYSICAL), EACCES); + if (!have_effective_cap(CAP_DAC_OVERRIDE)) + ASSERT_ERROR(rm_rf(d, REMOVE_PHYSICAL), EACCES); ASSERT_OK_ERRNO(access(d, F_OK)); ASSERT_OK_ERRNO(access(x, F_OK));