diff options
author | Lennart Poettering <lennart@poettering.net> | 2023-04-13 07:16:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-13 07:16:24 +0200 |
commit | 5d0fd36495772f86de9c3b902a97602eb49b8415 (patch) | |
tree | 1fa908da7a8ab08998d6b7961d01f33a6fe40178 /src | |
parent | 47226e893b24f1aa84caaa6be266fb3c03442904 (diff) | |
parent | 3540ce8587cbd21ce9c2dbec72ea7fa3d1b38a5f (diff) | |
download | systemd-5d0fd36495772f86de9c3b902a97602eb49b8415.tar.gz |
Merge pull request #27135 from poettering/pin-fdstore
Allow the per-service fdstore to be "pinned", i.e. preserved as long as the unit info remains in memory
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/unit-def.c | 1 | ||||
-rw-r--r-- | src/basic/unit-def.h | 1 | ||||
-rw-r--r-- | src/core/dbus-execute.c | 8 | ||||
-rw-r--r-- | src/core/dbus-execute.h | 2 | ||||
-rw-r--r-- | src/core/dbus-service.c | 4 | ||||
-rw-r--r-- | src/core/dbus-unit.c | 21 | ||||
-rw-r--r-- | src/core/execute.c | 17 | ||||
-rw-r--r-- | src/core/execute.h | 5 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.in | 3 | ||||
-rw-r--r-- | src/core/load-fragment.c | 2 | ||||
-rw-r--r-- | src/core/load-fragment.h | 2 | ||||
-rw-r--r-- | src/core/manager.c | 23 | ||||
-rw-r--r-- | src/core/manager.h | 3 | ||||
-rw-r--r-- | src/core/service.c | 167 | ||||
-rw-r--r-- | src/core/service.h | 3 | ||||
-rw-r--r-- | src/core/socket.c | 2 | ||||
-rw-r--r-- | src/core/timer.c | 1 | ||||
-rw-r--r-- | src/core/unit.c | 99 | ||||
-rw-r--r-- | src/core/unit.h | 7 | ||||
-rw-r--r-- | src/shared/bus-unit-util.c | 3 | ||||
-rw-r--r-- | src/systemctl/systemctl-clean-or-freeze.c | 2 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 3 |
22 files changed, 289 insertions, 90 deletions
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index a0fab46a19..86b66e2be0 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -201,6 +201,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = { [SERVICE_FAILED] = "failed", [SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart", [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart", + [SERVICE_DEAD_RESOURCES_PINNED] = "dead-resources-pinned", [SERVICE_AUTO_RESTART] = "auto-restart", [SERVICE_CLEANING] = "cleaning", }; diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index 2fab42e9c7..169e1f719e 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -146,6 +146,7 @@ typedef enum ServiceState { SERVICE_FAILED, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, + SERVICE_DEAD_RESOURCES_PINNED, /* Like SERVICE_DEAD, but with pinned resources */ SERVICE_AUTO_RESTART, SERVICE_CLEANING, _SERVICE_STATE_MAX, diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index d77842bdab..ce20183a70 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -49,7 +49,7 @@ BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode); +BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_proc, protect_proc, ProtectProc); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_proc_subset, proc_subset, ProcSubset); @@ -1318,7 +1318,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", bus_property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1695,7 +1695,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_home, ProtectHome, protect_home_fr static BUS_DEFINE_SET_TRANSIENT_PARSE(keyring_mode, ExecKeyringMode, exec_keyring_mode_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_proc, ProtectProc, protect_proc_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(proc_subset, ProcSubset, proc_subset_from_string); -static BUS_DEFINE_SET_TRANSIENT_PARSE(preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string); +BUS_DEFINE_SET_TRANSIENT_PARSE(exec_preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint64_t, "%" PRIu64, capability_set_to_string); @@ -2047,7 +2047,7 @@ int bus_exec_context_set_transient_property( return bus_set_transient_proc_subset(u, name, &c->proc_subset, message, flags, error); if (streq(name, "RuntimeDirectoryPreserve")) - return bus_set_transient_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error); + return bus_set_transient_exec_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error); if (streq(name, "UMask")) return bus_set_transient_mode_t(u, name, &c->umask, message, flags, error); diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h index c53834140e..5926bdb4b1 100644 --- a/src/core/dbus-execute.h +++ b/src/core/dbus-execute.h @@ -28,6 +28,8 @@ int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *inte int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); int bus_property_get_exec_ex_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +int bus_property_get_exec_preserve_mode(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_exec_command(Unit *u, const char *name, ExecCommand **exec_command, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); +int bus_set_transient_exec_preserve_mode(Unit *u, const char *name, ExecPreserveMode *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index a563624fce..ecab3f479d 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -349,6 +349,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NFileDescriptorStore", "u", property_get_size_as_uint32, offsetof(Service, n_fd_store), 0), + SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0), SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -599,6 +600,9 @@ static int bus_service_set_transient_property( if (streq(name, "FileDescriptorStoreMax")) return bus_set_transient_unsigned(u, name, &s->n_fd_store_max, message, flags, error); + if (streq(name, "FileDescriptorStorePreserve")) + return bus_set_transient_exec_preserve_mode(u, name, &s->fd_store_preserve_mode, message, flags, error); + if (streq(name, "NotifyAccess")) return bus_set_transient_notify_access(u, name, &s->notify_access, message, flags, error); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 3f083a8174..c01f41cb44 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -90,6 +90,12 @@ static int property_get_can_clean( return r; } + if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) { + r = sd_bus_message_append(reply, "s", "fdstore"); + if (r < 0) + return r; + } + return sd_bus_message_close_container(reply); } @@ -696,6 +702,7 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error return r; for (;;) { + ExecCleanMask m; const char *i; r = sd_bus_message_read(message, "s", &i); @@ -704,17 +711,11 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error if (r == 0) break; - if (streq(i, "all")) - mask |= EXEC_CLEAN_ALL; - else { - ExecDirectoryType t; - - t = exec_resource_type_from_string(i); - if (t < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i); + m = exec_clean_mask_from_string(i); + if (m < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i); - mask |= 1U << t; - } + mask |= m; } r = sd_bus_message_exit_container(message); diff --git a/src/core/execute.c b/src/core/execute.c index 8b09794089..879f1345b0 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -7582,6 +7582,23 @@ void exec_directory_sort(ExecDirectory *d) { } } +ExecCleanMask exec_clean_mask_from_string(const char *s) { + ExecDirectoryType t; + + assert(s); + + if (streq(s, "all")) + return EXEC_CLEAN_ALL; + if (streq(s, "fdstore")) + return EXEC_CLEAN_FDSTORE; + + t = exec_resource_type_from_string(s); + if (t < 0) + return (ExecCleanMask) t; + + return 1U << t; +} + DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free); DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free); diff --git a/src/core/execute.h b/src/core/execute.h index 123fc1ec60..d2f5507405 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -161,8 +161,9 @@ typedef enum ExecCleanMask { EXEC_CLEAN_CACHE = 1U << EXEC_DIRECTORY_CACHE, EXEC_CLEAN_LOGS = 1U << EXEC_DIRECTORY_LOGS, EXEC_CLEAN_CONFIGURATION = 1U << EXEC_DIRECTORY_CONFIGURATION, + EXEC_CLEAN_FDSTORE = 1U << _EXEC_DIRECTORY_TYPE_MAX, EXEC_CLEAN_NONE = 0, - EXEC_CLEAN_ALL = (1U << _EXEC_DIRECTORY_TYPE_MAX) - 1, + EXEC_CLEAN_ALL = (1U << (_EXEC_DIRECTORY_TYPE_MAX+1)) - 1, _EXEC_CLEAN_MASK_INVALID = -EINVAL, } ExecCleanMask; @@ -520,6 +521,8 @@ void exec_directory_done(ExecDirectory *d); int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink); void exec_directory_sort(ExecDirectory *d); +ExecCleanMask exec_clean_mask_from_string(const char *s); + extern const struct hash_ops exec_set_credential_hash_ops; extern const struct hash_ops exec_load_credential_hash_ops; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index f35c743655..ce0e2f0c5c 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -136,7 +136,7 @@ {{type}}.MountFlags, config_parse_exec_mount_propagation_flag, 0, offsetof({{type}}, exec_context.mount_propagation_flag) {{type}}.MountAPIVFS, config_parse_exec_mount_apivfs, 0, offsetof({{type}}, exec_context) {{type}}.Personality, config_parse_personality, 0, offsetof({{type}}, exec_context.personality) -{{type}}.RuntimeDirectoryPreserve, config_parse_runtime_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode) +{{type}}.RuntimeDirectoryPreserve, config_parse_exec_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode) {{type}}.RuntimeDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode) {{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME]) {{type}}.StateDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode) @@ -433,6 +433,7 @@ Service.SysVStartPriority, config_parse_warn_compat, Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking) Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name) Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max) +Service.FileDescriptorStorePreserve, config_parse_exec_preserve_mode, 0, offsetof(Service, fd_store_preserve_mode) Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) Service.Sockets, config_parse_service_sockets, 0, 0 Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0 diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 686a72402d..cf0096263a 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -138,7 +138,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to pa DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome, "Failed to parse protect home value"); DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem, "Failed to parse protect system value"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse runtime directory preserve mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse resource preserve mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_exit_type, service_exit_type, ServiceExitType, "Failed to parse service exit type"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index ab682ee23e..98adf5ae05 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -101,7 +101,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_selinux_context); CONFIG_PARSER_PROTOTYPE(config_parse_exec_apparmor_profile); CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label); CONFIG_PARSER_PROTOTYPE(config_parse_address_families); -CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode); +CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode); CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories); CONFIG_PARSER_PROTOTYPE(config_parse_set_credential); CONFIG_PARSER_PROTOTYPE(config_parse_load_credential); diff --git a/src/core/manager.c b/src/core/manager.c index 47a502df7f..b859ce58af 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1250,6 +1250,26 @@ static unsigned manager_dispatch_cleanup_queue(Manager *m) { return n; } +static unsigned manager_dispatch_release_resources_queue(Manager *m) { + unsigned n = 0; + Unit *u; + + assert(m); + + while ((u = m->release_resources_queue)) { + assert(u->in_release_resources_queue); + + LIST_REMOVE(release_resources_queue, m->release_resources_queue, u); + u->in_release_resources_queue = false; + + n++; + + unit_release_resources(u); + } + + return n; +} + enum { GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */ GC_OFFSET_UNSURE, /* No clue */ @@ -3158,6 +3178,9 @@ int manager_loop(Manager *m) { if (manager_dispatch_stop_when_unneeded_queue(m) > 0) continue; + if (manager_dispatch_release_resources_queue(m) > 0) + continue; + if (manager_dispatch_dbus_queue(m) > 0) continue; diff --git a/src/core/manager.h b/src/core/manager.h index fc95b75120..486eda11b6 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -195,6 +195,9 @@ struct Manager { /* Units that have BindsTo= another unit, and might need to be shutdown because the bound unit is not active. */ LIST_HEAD(Unit, stop_when_bound_queue); + /* Units that have resources open, and where it might be good to check if they can be released now */ + LIST_HEAD(Unit, release_resources_queue); + sd_event *event; /* This maps PIDs we care about to units that are interested in. We allow multiple units to be interested in diff --git a/src/core/service.c b/src/core/service.c index 650741cc7c..1c31782fab 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -69,6 +69,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { [SERVICE_FAILED] = UNIT_FAILED, [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE, [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED, + [SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE, [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; @@ -97,6 +98,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = [SERVICE_FAILED] = UNIT_FAILED, [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE, [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED, + [SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE, [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; @@ -139,6 +141,8 @@ static void service_init(Unit *u) { s->oom_policy = _OOM_POLICY_INVALID; s->reload_begin_usec = USEC_INFINITY; s->reload_signal = SIGHUP; + + s->fd_store_preserve_mode = EXEC_PRESERVE_RESTART; } static void service_unwatch_control_pid(Service *s) { @@ -196,9 +200,14 @@ static int service_set_main_pid(Service *s, pid_t pid) { return 0; } -void service_close_socket_fd(Service *s) { +void service_release_socket_fd(Service *s) { assert(s); + if (s->socket_fd < 0 && !UNIT_ISSET(s->accept_socket) && !s->socket_peer) + return; + + log_unit_debug(UNIT(s), "Closing connection socket."); + /* Undo the effect of service_set_socket_fd(). */ s->socket_fd = asynchronous_close(s->socket_fd); @@ -380,41 +389,36 @@ static void service_fd_store_unlink(ServiceFDStore *fs) { sd_event_source_disable_unref(fs->event_source); free(fs->fdname); - safe_close(fs->fd); + asynchronous_close(fs->fd); free(fs); } static void service_release_fd_store(Service *s) { assert(s); + if (!s->fd_store) + return; + log_unit_debug(UNIT(s), "Releasing all stored fds"); + while (s->fd_store) service_fd_store_unlink(s->fd_store); assert(s->n_fd_store == 0); } -static void service_release_resources(Unit *u) { - Service *s = SERVICE(u); - +static void service_release_stdio_fd(Service *s) { assert(s); - /* Don't release resources if this is a transitionary failed/dead state */ - if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART)) - return; - - if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) + if (s->stdin_fd < 0 && s->stdout_fd < 0 && s->stdout_fd < 0) return; - log_unit_debug(u, "Releasing resources."); + log_unit_debug(UNIT(s), "Releasing stdin/stdout/stderr file descriptors."); - s->stdin_fd = safe_close(s->stdin_fd); - s->stdout_fd = safe_close(s->stdout_fd); - s->stderr_fd = safe_close(s->stderr_fd); - - service_release_fd_store(s); + s->stdin_fd = asynchronous_close(s->stdin_fd); + s->stdout_fd = asynchronous_close(s->stdout_fd); + s->stderr_fd = asynchronous_close(s->stderr_fd); } - static void service_done(Unit *u) { Service *s = SERVICE(u); @@ -450,8 +454,6 @@ static void service_done(Unit *u) { s->usb_function_descriptors = mfree(s->usb_function_descriptors); s->usb_function_strings = mfree(s->usb_function_strings); - service_close_socket_fd(s); - service_stop_watchdog(s); s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source); @@ -459,7 +461,9 @@ static void service_done(Unit *u) { s->bus_name_pid_lookup_slot = sd_bus_slot_unref(s->bus_name_pid_lookup_slot); - service_release_resources(u); + service_release_socket_fd(s); + service_release_stdio_fd(s); + service_release_fd_store(s); } static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { @@ -477,6 +481,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us } static int service_add_fd_store(Service *s, int fd, const char *name, bool do_poll) { + struct stat st; ServiceFDStore *fs; int r; @@ -485,17 +490,23 @@ static int service_add_fd_store(Service *s, int fd, const char *name, bool do_po assert(s); assert(fd >= 0); + if (fstat(fd, &st) < 0) + return -errno; + + log_unit_debug(UNIT(s), "Trying to stash fd for dev=" DEVNUM_FORMAT_STR "/inode=%" PRIu64, DEVNUM_FORMAT_VAL(st.st_dev), (uint64_t) st.st_ino); + if (s->n_fd_store >= s->n_fd_store_max) - return -EXFULL; /* Our store is full. - * Use this errno rather than E[NM]FILE to distinguish from - * the case where systemd itself hits the file limit. */ + /* Our store is full. Use this errno rather than E[NM]FILE to distinguish from the case + * where systemd itself hits the file limit. */ + return log_unit_debug_errno(UNIT(s), SYNTHETIC_ERRNO(EXFULL), "Hit fd store limit."); LIST_FOREACH(fd_store, i, s->fd_store) { r = same_fd(i->fd, fd); if (r < 0) return r; if (r > 0) { - safe_close(fd); + log_unit_debug(UNIT(s), "Suppressing duplicate fd in fd store."); + asynchronous_close(fd); return 0; /* fd already included */ } } @@ -538,7 +549,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo assert(s); while (fdset_size(fds) > 0) { - _cleanup_close_ int fd = -EBADF; + _cleanup_(asynchronous_closep) int fd = -EBADF; fd = fdset_steal_first(fds); if (fd < 0) @@ -553,7 +564,8 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m"); if (r > 0) log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fd, strna(name)); - fd = -EBADF; + + TAKE_FD(fd); } return 0; @@ -1030,8 +1042,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { if (s->n_fd_store_max > 0) fprintf(f, "%sFile Descriptor Store Max: %u\n" + "%sFile Descriptor Store Pin: %s\n" "%sFile Descriptor Store Current: %zu\n", prefix, s->n_fd_store_max, + prefix, exec_preserve_mode_to_string(s->fd_store_preserve_mode), prefix, s->n_fd_store); service_dump_fdstore(s, f, prefix); @@ -1243,20 +1257,12 @@ static void service_set_state(Service *s, ServiceState state) { if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, - SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART)) { + SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART, + SERVICE_DEAD_RESOURCES_PINNED)) { unit_unwatch_all_pids(UNIT(s)); unit_dequeue_rewatch_pids(UNIT(s)); } - if (!IN_SET(state, - SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, - SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, - SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && - !(state == SERVICE_DEAD && UNIT(s)->job)) - service_close_socket_fd(s); - if (state != SERVICE_START) s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source); @@ -1359,7 +1365,8 @@ static int service_coldplug(Unit *u) { if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART, - SERVICE_CLEANING)) { + SERVICE_CLEANING, + SERVICE_DEAD_RESOURCES_PINNED)) { (void) unit_enqueue_rewatch_pids(u); (void) unit_setup_exec_runtime(u); } @@ -1947,6 +1954,12 @@ static bool service_will_restart(Unit *u) { return unit_will_restart_default(u); } +static ServiceState service_determine_dead_state(Service *s) { + assert(s); + + return s->fd_store && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; +} + static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { ServiceState end_state, restart_state; int r; @@ -1963,11 +1976,11 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) if (s->result == SERVICE_SUCCESS) { unit_log_success(UNIT(s)); - end_state = SERVICE_DEAD; + end_state = service_determine_dead_state(s); restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART; } else if (s->result == SERVICE_SKIP_CONDITION) { unit_log_skip(UNIT(s), service_result_to_string(s->result)); - end_state = SERVICE_DEAD; + end_state = service_determine_dead_state(s); restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART; } else { unit_log_failure(UNIT(s), service_result_to_string(s->result)); @@ -2031,6 +2044,10 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) /* Also, remove the runtime directory */ unit_destroy_runtime_data(UNIT(s), &s->exec_context); + /* Also get rid of the fd store, if that's configured. */ + if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO) + service_release_fd_store(s); + /* Get rid of the IPC bits of the user */ unit_unref_uid_gid(UNIT(s), true); @@ -2709,7 +2726,7 @@ static int service_start(Unit *u) { if (IN_SET(s->state, SERVICE_AUTO_RESTART, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART)) return -EAGAIN; - assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)); + assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED)); r = unit_acquire_invocation_id(u); if (r < 0) @@ -2768,7 +2785,7 @@ static int service_stop(Unit *u) { case SERVICE_AUTO_RESTART: /* A restart will be scheduled or is in progress. */ - service_set_state(s, SERVICE_DEAD); + service_set_state(s, service_determine_dead_state(s)); return 0; case SERVICE_CONDITION: @@ -2797,6 +2814,7 @@ static int service_stop(Unit *u) { case SERVICE_FAILED_BEFORE_AUTO_RESTART: case SERVICE_DEAD: case SERVICE_FAILED: + case SERVICE_DEAD_RESOURCES_PINNED: default: /* Unknown state, or unit_stop() should already have handled these */ assert_not_reached(); @@ -3405,7 +3423,7 @@ static bool service_may_gc(Unit *u) { /* Only allow collection of actually dead services, i.e. not those that are in the transitionary * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */ - if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)) + if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED)) return false; return true; @@ -3632,6 +3650,7 @@ static void service_notify_cgroup_empty_event(Unit *u) { case SERVICE_DEAD_BEFORE_AUTO_RESTART: case SERVICE_FAILED_BEFORE_AUTO_RESTART: case SERVICE_AUTO_RESTART: + case SERVICE_DEAD_RESOURCES_PINNED: unit_prune_cgroup(u); break; @@ -4720,7 +4739,7 @@ int service_set_socket_fd( assert(!s->socket_peer); - if (s->state != SERVICE_DEAD) + if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED)) return -EAGAIN; if (getpeername_pretty(fd, true, &peer_text) >= 0) { @@ -4757,7 +4776,7 @@ static void service_reset_failed(Unit *u) { assert(s); if (s->state == SERVICE_FAILED) - service_set_state(s, SERVICE_DEAD); + service_set_state(s, service_determine_dead_state(s)); s->result = SERVICE_SUCCESS; s->reload_result = SERVICE_SUCCESS; @@ -4845,22 +4864,39 @@ static const char* service_status_text(Unit *u) { static int service_clean(Unit *u, ExecCleanMask mask) { _cleanup_strv_free_ char **l = NULL; + bool may_clean_fdstore = false; Service *s = SERVICE(u); int r; assert(s); assert(mask != 0); - if (s->state != SERVICE_DEAD) + if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED)) return -EBUSY; + /* Determine if there's anything we could potentially clean */ r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l); if (r < 0) return r; - if (strv_isempty(l)) - return -EUNATCH; + if (mask & EXEC_CLEAN_FDSTORE) + may_clean_fdstore = s->n_fd_store > 0 || s->n_fd_store_max > 0; + + if (strv_isempty(l) && !may_clean_fdstore) + return -EUNATCH; /* Nothing to potentially clean */ + + /* Let's clean the stuff we can clean quickly */ + if (may_clean_fdstore) + service_release_fd_store(s); + /* If we are done, leave quickly */ + if (strv_isempty(l)) { + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + service_set_state(s, SERVICE_DEAD); + return 0; + } + + /* We need to clean disk stuff. This is slow, hence do it out of process, and change state */ service_unwatch_control_pid(s); s->clean_result = SERVICE_SUCCESS; s->control_command = NULL; @@ -4887,10 +4923,21 @@ fail: static int service_can_clean(Unit *u, ExecCleanMask *ret) { Service *s = SERVICE(u); + ExecCleanMask mask = 0; + int r; assert(s); + assert(ret); + + r = exec_context_get_clean_mask(&s->exec_context, &mask); + if (r < 0) + return r; - return exec_context_get_clean_mask(&s->exec_context, ret); + if (s->n_fd_store_max > 0) + mask |= EXEC_CLEAN_FDSTORE; + + *ret = mask; + return 0; } static const char *service_finished_job(Unit *u, JobType t, JobResult result) { @@ -4919,6 +4966,30 @@ static int service_can_start(Unit *u) { return 1; } +static void service_release_resources(Unit *u) { + Service *s = SERVICE(ASSERT_PTR(u)); + + /* Invoked by the unit state engine, whenever it realizes that unit is dead and there's no job + * anymore for it, and it hence is a good idea to release resources */ + + /* Don't release resources if this is a transitionary failed/dead state + * (i.e. SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART), insist on a permanent + * failure state. */ + if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED)) + return; + + log_unit_debug(u, "Releasing resources..."); + + service_release_socket_fd(s); + service_release_stdio_fd(s); + + if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) + service_release_fd_store(s); + + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + service_set_state(s, SERVICE_DEAD); +} + static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { [SERVICE_RESTART_NO] = "no", [SERVICE_RESTART_ON_SUCCESS] = "on-success", diff --git a/src/core/service.h b/src/core/service.h index 03edb18e31..5b7f67457e 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -206,6 +206,7 @@ struct Service { ServiceFDStore *fd_store; size_t n_fd_store; unsigned n_fd_store_max; + ExecPreserveMode fd_store_preserve_mode; char *usb_function_descriptors; char *usb_function_strings; @@ -243,7 +244,7 @@ static inline usec_t service_get_watchdog_usec(Service *s) { extern const UnitVTable service_vtable; int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net); -void service_close_socket_fd(Service *s); +void service_release_socket_fd(Service *s); usec_t service_restart_usec_next(Service *s); diff --git a/src/core/socket.c b/src/core/socket.c index c26daced1a..891276fd50 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2393,7 +2393,7 @@ static void socket_enter_running(Socket *s, int cfd_in) { if (r < 0) { /* We failed to activate the new service, but it still exists. Let's make sure the * service closes and forgets the connection fd again, immediately. */ - service_close_socket_fd(SERVICE(service)); + service_release_socket_fd(SERVICE(service)); goto fail; } diff --git a/src/core/timer.c b/src/core/timer.c index 371e42e0aa..419416b325 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -884,6 +884,7 @@ static int timer_can_clean(Unit *u, ExecCleanMask *ret) { Timer *t = TIMER(u); assert(t); + assert(ret); *ret = t->persistent ? EXEC_CLEAN_STATE : 0; return 0; diff --git a/src/core/unit.c b/src/core/unit.c index 409801aed2..d632c34f98 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -394,37 +394,59 @@ static bool unit_success_failure_handler_has_jobs(Unit *unit) { return false; } +void unit_release_resources(Unit *u) { + UnitActiveState state; + ExecContext *ec; + + assert(u); + + if (u->job || u->nop_job) + return; + + if (u->perpetual) + return; + + state = unit_active_state(u); + if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED)) + return; + + if (unit_will_restart(u)) + return; + + ec = unit_get_exec_context(u); + if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART) + exec_context_destroy_runtime_directory(ec, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]); + + if (UNIT_VTABLE(u)->release_resources) + UNIT_VTABLE(u)->release_resources(u); +} + bool unit_may_gc(Unit *u) { UnitActiveState state; int r; assert(u); - /* Checks whether the unit is ready to be unloaded for garbage collection. - * Returns true when the unit may be collected, and false if there's some - * reason to keep it loaded. + /* Checks whether the unit is ready to be unloaded for garbage collection. Returns true when the + * unit may be collected, and false if there's some reason to keep it loaded. * - * References from other units are *not* checked here. Instead, this is done - * in unit_gc_sweep(), but using markers to properly collect dependency loops. + * References from other units are *not* checked here. Instead, this is done in unit_gc_sweep(), but + * using markers to properly collect dependency loops. */ if (u->job || u->nop_job) return false; - state = unit_active_state(u); - - /* If the unit is inactive and failed and no job is queued for it, then release its runtime resources */ - if (UNIT_IS_INACTIVE_OR_FAILED(state) && - UNIT_VTABLE(u)->release_resources) - UNIT_VTABLE(u)->release_resources(u); - if (u->perpetual) return false; if (sd_bus_track_count(u->bus_track) > 0) return false; - /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */ + state = unit_active_state(u); + + /* But we keep the unit object around for longer when it is referenced or configured to not be + * gc'ed */ switch (u->collect_mode) { case COLLECT_INACTIVE: @@ -458,10 +480,10 @@ bool unit_may_gc(Unit *u) { return false; } - if (UNIT_VTABLE(u)->may_gc && !UNIT_VTABLE(u)->may_gc(u)) - return false; + if (!UNIT_VTABLE(u)->may_gc) + return true; - return true; + return UNIT_VTABLE(u)->may_gc(u); } void unit_add_to_load_queue(Unit *u) { @@ -565,6 +587,40 @@ void unit_submit_to_stop_when_bound_queue(Unit *u) { u->in_stop_when_bound_queue = true; } +static bool unit_can_release_resources(Unit *u) { + ExecContext *ec; + + assert(u); + + if (UNIT_VTABLE(u)->release_resources) + return true; + + ec = unit_get_exec_context(u); + if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART) + return true; + + return false; +} + +void unit_submit_to_release_resources_queue(Unit *u) { + assert(u); + + if (u->in_release_resources_queue) + return; + + if (u->job || u->nop_job) + return; + + if (u->perpetual) + return; + + if (!unit_can_release_resources(u)) + return; + + LIST_PREPEND(release_resources_queue, u->manager->release_resources_queue, u); + u->in_release_resources_queue = true; +} + static void unit_clear_dependencies(Unit *u) { assert(u); @@ -781,6 +837,9 @@ Unit* unit_free(Unit *u) { if (u->in_stop_when_bound_queue) LIST_REMOVE(stop_when_bound_queue, u->manager->stop_when_bound_queue, u); + if (u->in_release_resources_queue) + LIST_REMOVE(release_resources_queue, u->manager->release_resources_queue, u); + bpf_firewall_close(u); hashmap_free(u->bpf_foreign_by_key); @@ -2567,7 +2626,6 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags) assert(j); if (j->state == JOB_WAITING) - /* So we reached a different state for this job. Let's see if we can run it now if it failed previously * due to EAGAIN. */ job_add_to_run_queue(j); @@ -2771,6 +2829,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag /* Maybe the unit should be GC'ed now? */ unit_add_to_gc_queue(u); + + /* Maybe we can release some resources now? */ + unit_submit_to_release_resources_queue(u); } if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { @@ -5822,8 +5883,8 @@ void unit_destroy_runtime_data(Unit *u, const ExecContext *context) { assert(u); assert(context); - if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO || - (context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u))) + /* EXEC_PRESERVE_RESTART is handled via unit_release_resources()! */ + if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO) exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]); exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id); diff --git a/src/core/unit.h b/src/core/unit.h index 420405b2b7..b13ac33018 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -317,6 +317,9 @@ typedef struct Unit { /* Queue of units that have a BindTo= dependency on some other unit, and should possibly be shut down */ LIST_FIELDS(Unit, stop_when_bound_queue); + /* Queue of units that should be checked if they can release resources now */ + LIST_FIELDS(Unit, release_resources_queue); + /* PIDs we keep an eye on. Note that a unit might have many * more, but these are the ones we care enough about to * process SIGCHLD for */ @@ -479,6 +482,7 @@ typedef struct Unit { bool in_stop_when_unneeded_queue:1; bool in_start_when_upheld_queue:1; bool in_stop_when_bound_queue:1; + bool in_release_resources_queue:1; bool sent_dbus_new_signal:1; @@ -832,6 +836,8 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c); int unit_choose_id(Unit *u, const char *name); int unit_set_description(Unit *u, const char *description); +void unit_release_resources(Unit *u); + bool unit_may_gc(Unit *u); static inline bool unit_is_extrinsic(Unit *u) { @@ -853,6 +859,7 @@ void unit_add_to_target_deps_queue(Unit *u); void unit_submit_to_stop_when_unneeded_queue(Unit *u); void unit_submit_to_start_when_upheld_queue(Unit *u); void unit_submit_to_stop_when_bound_queue(Unit *u); +void unit_submit_to_release_resources_queue(Unit *u); int unit_merge(Unit *u, Unit *other); int unit_merge_by_name(Unit *u, const char *other); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 1c991ae54f..ebbd1f7f28 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2198,7 +2198,8 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con "USBFunctionStrings", "OOMPolicy", "TimeoutStartFailureMode", - "TimeoutStopFailureMode")) + "TimeoutStopFailureMode", + "FileDescriptorStorePreserve")) return bus_append_string(m, field, eq); if (STR_IN_SET(field, "PermissionsStartOnly", diff --git a/src/systemctl/systemctl-clean-or-freeze.c b/src/systemctl/systemctl-clean-or-freeze.c index 5c15a9fba0..40d5f6d557 100644 --- a/src/systemctl/systemctl-clean-or-freeze.c +++ b/src/systemctl/systemctl-clean-or-freeze.c @@ -21,7 +21,7 @@ int verb_clean_or_freeze(int argc, char *argv[], void *userdata) { polkit_agent_open_maybe(); if (!arg_clean_what) { - arg_clean_what = strv_new("cache", "runtime"); + arg_clean_what = strv_new("cache", "runtime", "fdstore"); if (!arg_clean_what) return log_oom(); } diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index df4cf68435..21e09536a6 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -937,7 +937,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { "state\n" "cache\n" "logs\n" - "configuration"); + "configuration\n" + "fdstore"); return 0; } |