diff --git a/man/rules/meson.build b/man/rules/meson.build index 6bd54739af..d496ebc747 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -633,6 +633,7 @@ manpages = [ '3', ['sd_event_source_get_ratelimit', 'sd_event_source_is_ratelimited', + 'sd_event_source_leave_ratelimit', 'sd_event_source_set_ratelimit_expire_callback'], ''], ['sd_event_source_set_userdata', '3', ['sd_event_source_get_userdata'], ''], diff --git a/man/sd_event_source_set_ratelimit.xml b/man/sd_event_source_set_ratelimit.xml index 07ac18b791..89eb34fa9b 100644 --- a/man/sd_event_source_set_ratelimit.xml +++ b/man/sd_event_source_set_ratelimit.xml @@ -20,6 +20,7 @@ sd_event_source_get_ratelimit sd_event_source_is_ratelimited sd_event_source_set_ratelimit_expire_callback + sd_event_source_leave_ratelimit Configure rate limiting on event sources @@ -53,6 +54,10 @@ sd_event_handler_tcallback + + int sd_event_source_leave_ratelimit + sd_event_source *source + @@ -85,10 +90,14 @@ is currently affected by rate limiting, i.e. it has recently hit the rate limit and is currently temporarily disabled due to that. - sd_event_source_set_ratelimit_expire_callback may be used to set a callback + sd_event_source_set_ratelimit_expire_callback() may be used to set a callback function that is invoked every time the event source leaves rate limited state. Note that function is called in the same event loop iteration in which state transition occurred. + sd_event_source_leave_ratelimit() may be used to immediately reenable an event + source that was temporarily disabled due to rate limiting. This will reset the ratelimit counters for the + current time interval. + Rate limiting is currently implemented for I/O, timer, signal, defer and inotify event sources. @@ -98,10 +107,12 @@ On success, sd_event_source_set_ratelimit(), sd_event_source_set_ratelimit_expire_callback and - sd_event_source_get_ratelimit() return a non-negative integer. On failure, they return - a negative errno-style error code. sd_event_source_is_ratelimited returns zero if rate - limiting is currently not in effect and greater than zero if it is in effect; it returns a negative - errno-style error code on failure. + sd_event_source_get_ratelimit() return a non-negative integer. On failure, they + return a negative errno-style error code. sd_event_source_is_ratelimited() returns + zero if rate limiting is currently not in effect and greater than zero if it is in effect; it returns a + negative errno-style error code on failure. sd_event_source_leave_ratelimit() + returns zero if rate limiting wasn't in effect on the specified event source, and positive if it was and + rate limiting is now turned off again; it returns a negative errno-style error code on failure. Errors diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 86b66e2be0..bfd748ada4 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -152,6 +152,7 @@ static const char* const mount_state_table[_MOUNT_STATE_MAX] = { [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", + [MOUNT_UNMOUNTING_CATCHUP] = "unmounting-catchup", [MOUNT_FAILED] = "failed", [MOUNT_CLEANING] = "cleaning", }; diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index 169e1f719e..7bd86cb1d2 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -97,6 +97,7 @@ typedef enum MountState { MOUNT_REMOUNTING_SIGKILL, MOUNT_UNMOUNTING_SIGTERM, MOUNT_UNMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_CATCHUP, MOUNT_FAILED, MOUNT_CLEANING, _MOUNT_STATE_MAX, diff --git a/src/core/execute.c b/src/core/execute.c index ca89e3b003..5e327465da 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1539,7 +1539,7 @@ static bool context_has_no_new_privileges(const ExecContext *c) { context_has_syscall_logs(c); } -static bool exec_context_has_credentials(const ExecContext *context) { +bool exec_context_has_credentials(const ExecContext *context) { assert(context); @@ -2787,7 +2787,7 @@ static char **credential_search_path( if (DEBUG_LOGGING) { _cleanup_free_ char *t = strv_join(l, ":"); - log_debug("Credential search path is: %s", t); + log_debug("Credential search path is: %s", strempty(t)); } return TAKE_PTR(l); diff --git a/src/core/execute.h b/src/core/execute.h index c2c983d0c3..e46f31037e 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -476,6 +476,7 @@ const char* exec_context_fdname(const ExecContext *c, int fd_index); bool exec_context_may_touch_console(const ExecContext *c); bool exec_context_maintains_privileges(const ExecContext *c); bool exec_context_has_encrypted_credentials(ExecContext *c); +bool exec_context_has_credentials(const ExecContext *context); int exec_context_get_effective_ioprio(const ExecContext *c); bool exec_context_get_effective_mount_apivfs(const ExecContext *c); diff --git a/src/core/mount.c b/src/core/mount.c index 8f02bd29d8..36ea9bbefb 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -48,6 +48,7 @@ static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_CATCHUP] = UNIT_DEACTIVATING, [MOUNT_FAILED] = UNIT_FAILED, [MOUNT_CLEANING] = UNIT_MAINTENANCE, }; @@ -473,6 +474,22 @@ static bool mount_is_extrinsic(Unit *u) { return false; } +static bool mount_is_credentials(Mount *m) { + const char *e; + + assert(m); + + /* Returns true if this is a credentials mount. We don't want automatic dependencies on credential + * mounts, since they are managed by us for even the earliest services, and we never want anything to + * be ordered before them hence. */ + + e = path_startswith(m->where, UNIT(m)->manager->prefix[EXEC_DIRECTORY_RUNTIME]); + if (!e) + return false; + + return !isempty(path_startswith(e, "credentials")); +} + static int mount_add_default_ordering_dependencies(Mount *m, MountParameters *p, UnitDependencyMask mask) { const char *after, *before, *e; int r; @@ -495,7 +512,10 @@ static int mount_add_default_ordering_dependencies(Mount *m, MountParameters *p, after = SPECIAL_LOCAL_FS_PRE_TARGET; before = SPECIAL_INITRD_USR_FS_TARGET; - } else if (mount_is_network(p)) { + } else if (mount_is_credentials(m)) + after = before = NULL; + + else if (mount_is_network(p)) { after = SPECIAL_REMOTE_FS_PRE_TARGET; before = SPECIAL_REMOTE_FS_TARGET; @@ -504,17 +524,56 @@ static int mount_add_default_ordering_dependencies(Mount *m, MountParameters *p, before = SPECIAL_LOCAL_FS_TARGET; } - if (!mount_is_nofail(m)) { + if (before && !mount_is_nofail(m)) { r = unit_add_dependency_by_name(UNIT(m), UNIT_BEFORE, before, /* add_reference= */ true, mask); if (r < 0) return r; } - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, /* add_reference= */ true, mask); + if (after) { + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, /* add_reference= */ true, mask); + if (r < 0) + return r; + } + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, + /* add_reference= */ true, mask); if (r < 0) return r; - return unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, + /* If this is a tmpfs mount then we have to unmount it before we try to deactivate swaps */ + if (streq_ptr(p->fstype, "tmpfs") && !mount_is_credentials(m)) { + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_SWAP_TARGET, + /* add_reference= */ true, mask); + if (r < 0) + return r; + } + + return 0; +} + +static int mount_add_default_network_dependencies(Mount *m, MountParameters *p, UnitDependencyMask mask) { + int r; + + assert(m); + + if (!mount_is_network(p)) + return 0; + + /* We order ourselves after network.target. This is primarily useful at shutdown: services that take + * down the network should order themselves before network.target, so that they are shut down only + * after this mount unit is stopped. */ + + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_NETWORK_TARGET, + /* add_reference= */ true, mask); + if (r < 0) + return r; + + /* We pull in network-online.target, and order ourselves after it. This is useful at start-up to + * actively pull in tools that want to be started before we start mounting network file systems, and + * whose purpose it is to delay this until the network is "up". */ + + return unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, /* add_reference= */ true, mask); } @@ -545,35 +604,9 @@ static int mount_add_default_dependencies(Mount *m) { if (r < 0) return r; - if (mount_is_network(p)) { - /* We order ourselves after network.target. This is primarily useful at shutdown: - * services that take down the network should order themselves before - * network.target, so that they are shut down only after this mount unit is - * stopped. */ - - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_NETWORK_TARGET, - /* add_reference= */ true, mask); - if (r < 0) - return r; - - /* We pull in network-online.target, and order ourselves after it. This is useful - * at start-up to actively pull in tools that want to be started before we start - * mounting network file systems, and whose purpose it is to delay this until the - * network is "up". */ - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, - /* add_reference= */ true, mask); - if (r < 0) - return r; - } - - /* If this is a tmpfs mount then we have to unmount it before we try to deactivate swaps */ - if (streq_ptr(p->fstype, "tmpfs")) { - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_SWAP_TARGET, - /* add_reference= */ true, mask); - if (r < 0) - return r; - } + r = mount_add_default_network_dependencies(m, p, mask); + if (r < 0) + return r; return exec_context_add_default_dependencies(UNIT(m), &m->exec_context); } @@ -1037,11 +1070,37 @@ static void mount_enter_unmounting(Mount *m) { assert(m); + r = path_is_mount_point(m->where, NULL, 0); + if (IN_SET(r, 0, -ENOENT)) { + /* Not a mount point anymore ? Then we raced against something else which unmounted it? Let's + * handle this gracefully, and wait until we get the kernel notification about it. */ + + log_unit_debug(UNIT(m), "Path '%s' is not a mount point anymore, waiting for mount table refresh.", m->where); + + /* Apparently our idea of the kernel mount table is out of date. Make sure we re-read it + * again, soon, so that we don't delay mount handling unnecessarily. */ + (void) sd_event_source_leave_ratelimit(UNIT(m)->manager->mount_event_source); + + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + mount_unwatch_control_pid(m); + + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); + if (r < 0) + goto fail; + + /* Let's enter a distinct state where we just wait for the mount table to catch up */ + mount_set_state(m, MOUNT_UNMOUNTING_CATCHUP); + return; + } + if (r < 0) + log_unit_debug_errno(UNIT(m), r, "Unable to determine if '%s' is a mount point, ignoring: %m", m->where); + /* Start counting our attempts */ if (!IN_SET(m->state, MOUNT_UNMOUNTING, MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL)) + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_CATCHUP)) m->n_retry_umount = 0; m->control_command_id = MOUNT_EXEC_UNMOUNT; @@ -1062,7 +1121,6 @@ static void mount_enter_unmounting(Mount *m) { goto fail; mount_set_state(m, MOUNT_UNMOUNTING); - return; fail: @@ -1235,6 +1293,7 @@ static int mount_start(Unit *u) { MOUNT_UNMOUNTING, MOUNT_UNMOUNTING_SIGTERM, MOUNT_UNMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_CATCHUP, MOUNT_CLEANING)) return -EAGAIN; @@ -1264,6 +1323,7 @@ static int mount_stop(Unit *u) { case MOUNT_UNMOUNTING: case MOUNT_UNMOUNTING_SIGKILL: case MOUNT_UNMOUNTING_SIGTERM: + case MOUNT_UNMOUNTING_CATCHUP: /* Already on it */ return 0; @@ -1529,6 +1589,10 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { mount_enter_dead_or_mounted(m, f); break; + case MOUNT_UNMOUNTING_CATCHUP: + log_unit_debug(u, "Was notified about control process death, but wasn't expecting it. Ignoring."); + break; + case MOUNT_CLEANING: if (m->clean_result == MOUNT_SUCCESS) m->clean_result = f; @@ -1603,6 +1667,11 @@ static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *user mount_enter_dead_or_mounted(m, MOUNT_FAILURE_TIMEOUT); break; + case MOUNT_UNMOUNTING_CATCHUP: + log_unit_warning(UNIT(m), "Waiting for unmount notification timed out. Giving up."); + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_TIMEOUT); + break; + case MOUNT_CLEANING: log_unit_warning(UNIT(m), "Cleaning timed out. killing."); @@ -2044,8 +2113,7 @@ static int mount_process_proc_self_mountinfo(Manager *m) { if (!mount_is_mounted(mount)) { - /* A mount point is not around right now. It - * might be gone, or might never have + /* A mount point is not around right now. It might be gone, or might never have * existed. */ if (mount->from_proc_self_mountinfo && @@ -2060,6 +2128,7 @@ static int mount_process_proc_self_mountinfo(Manager *m) { switch (mount->state) { case MOUNT_MOUNTED: + case MOUNT_UNMOUNTING_CATCHUP: /* This has just been unmounted by somebody else, follow the state change. */ mount_enter_dead(mount, MOUNT_SUCCESS); break; @@ -2098,11 +2167,9 @@ static int mount_process_proc_self_mountinfo(Manager *m) { break; default: - /* Nothing really changed, but let's - * issue an notification call - * nonetheless, in case somebody is - * waiting for this. (e.g. file system - * ro/rw remounts.) */ + /* Nothing really changed, but let's issue an notification call nonetheless, + * in case somebody is waiting for this. (e.g. file system ro/rw + * remounts.) */ mount_set_state(mount, mount->state); break; } diff --git a/src/core/unit.c b/src/core/unit.c index f7b8cf0abc..6c2682f0aa 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1411,6 +1411,26 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { if (r < 0) return r; + if (exec_context_has_credentials(c) && u->manager->prefix[EXEC_DIRECTORY_RUNTIME]) { + _cleanup_free_ char *p = NULL, *m = NULL; + + /* Let's make sure the credentials directory of this service is unmounted *after* the service + * itself shuts down. This only matters if mount namespacing is not used for the service, and + * hence the credentials mount appears on the host. */ + + p = path_join(u->manager->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id); + if (!p) + return -ENOMEM; + + r = unit_name_from_path(p, ".mount", &m); + if (r < 0) + return r; + + r = unit_add_dependency_by_name(u, UNIT_AFTER, m, /* add_reference= */ true, UNIT_DEPENDENCY_FILE); + if (r < 0) + return r; + } + return 0; } diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 8b99d0a7aa..35352dc832 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -824,4 +824,5 @@ global: sd_event_source_set_memory_pressure_period; sd_event_trim_memory; sd_pid_notify_barrier; + sd_event_source_leave_ratelimit; } LIBSYSTEMD_253; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index f4ede985de..9c624ab56f 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -5193,6 +5193,27 @@ _public_ int sd_event_source_is_ratelimited(sd_event_source *s) { return s->ratelimited; } +_public_ int sd_event_source_leave_ratelimit(sd_event_source *s) { + int r; + + assert_return(s, -EINVAL); + + if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type)) + return 0; + + if (!ratelimit_configured(&s->rate_limit)) + return 0; + + if (!s->ratelimited) + return 0; + + r = event_source_leave_ratelimit(s, /* run_callback */ false); + if (r < 0) + return r; + + return 1; /* tell caller that we indeed just left the ratelimit state */ +} + _public_ int sd_event_set_signal_exit(sd_event *e, int b) { bool change = false; int r; diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index 7892f2bc2e..c5497e5148 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -828,4 +828,75 @@ TEST(fork) { assert_se(r >= 0); } +static int hup_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + unsigned *c = userdata; + + assert_se(revents == EPOLLHUP); + + (*c)++; + return 0; +} + +TEST(leave_ratelimit) { + bool expect_ratelimit = false, manually_left_ratelimit = false; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(close_pairp) int pfd[2] = PIPE_EBADF; + unsigned c = 0; + int r; + + assert_se(sd_event_default(&e) >= 0); + + /* Create an event source that will continously fire by creating a pipe whose write side is closed, + * and which hence will only see EOF and constant EPOLLHUP */ + assert_se(pipe2(pfd, O_CLOEXEC) >= 0); + assert_se(sd_event_add_io(e, &s, pfd[0], EPOLLIN, hup_callback, &c) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + assert_se(sd_event_source_set_ratelimit(s, 5*USEC_PER_MINUTE, 5) >= 0); + + pfd[0] = -EBADF; + pfd[1] = safe_close(pfd[1]); /* Trigger continous EOF */ + + for (;;) { + r = sd_event_prepare(e); + assert_se(r >= 0); + + if (r == 0) { + r = sd_event_wait(e, UINT64_MAX); + assert_se(r > 0); + } + + r = sd_event_dispatch(e); + assert_se(r > 0); + + r = sd_event_source_is_ratelimited(s); + assert_se(r >= 0); + + if (c < 5) + /* First four dispatches should just work */ + assert_se(!r); + else if (c == 5) { + /* The fifth dispatch should still work, but we now expect the ratelimit to be hit subsequently */ + if (!expect_ratelimit) { + assert_se(!r); + assert_se(sd_event_source_leave_ratelimit(s) == 0); /* this should be a NOP, and return 0 hence */ + expect_ratelimit = true; + } else { + /* We expected the ratelimit, let's leave it manually, and verify it */ + assert_se(r); + assert_se(sd_event_source_leave_ratelimit(s) > 0); /* we are ratelimited, hence should return > 0 */ + assert_se(sd_event_source_is_ratelimited(s) == 0); + + manually_left_ratelimit = true; + } + + } else if (c == 6) + /* On the sixth iteration let's just exit */ + break; + } + + /* Verify we definitely hit the ratelimit and left it manually again */ + assert_se(manually_left_ratelimit); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index 5ca8528895..49d6975967 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -173,6 +173,7 @@ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, un int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst); int sd_event_source_is_ratelimited(sd_event_source *s); int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback); +int sd_event_source_leave_ratelimit(sd_event_source *s); int sd_event_trim_memory(void);