diff --git a/NEWS b/NEWS index b5f7bba579..e7aaa12ca0 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ CHANGES WITH 246 in spe: their first output column with --no-legend. To hide the first column, use --plain. + * The service manager gained basic support for cgroup v2 freezer. Units + can now be suspended or resumed either using new systemctl verbs, + freeze and thaw respectively, or via D-Bus. + CHANGES WITH 245: * A new tool "systemd-repart" has been added, that operates as an diff --git a/man/systemctl.xml b/man/systemctl.xml index 30880b4110..570c1a5505 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -303,6 +303,30 @@ Sun 2017-02-26 20:57:49 EST 2h 3min left Sun 2017-02-26 11:56:36 EST 6h ago generally redundant and reproducible on the next invocation of the unit). + + freeze PATTERN + + + Freeze one or more units specified on the + command line using cgroup freezer + + Freezing the unit will cause all processes contained within the cgroup corresponding to the unit + to be suspended. Being suspended means that unit's processes won't be scheduled to run on CPU until thawed. + Note that this command is supported only on systems that use unified cgroup hierarchy. Unit is automatically + thawed just before we execute a job against the unit, e.g. before the unit is stopped. + + + + thaw PATTERN + + + Thaw (unfreeze) one or more units specified on the + command line. + + This is the inverse operation to the freeze command and resumes the execution of + processes in the unit's cgroup. + + is-active PATTERN diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 33575e65ef..e94fcfad02 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -149,6 +149,17 @@ bool cg_ns_supported(void) { return enabled; } +bool cg_freezer_supported(void) { + static thread_local int supported = -1; + + if (supported >= 0) + return supported; + + supported = cg_all_unified() > 0 && access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) == 0; + + return supported; +} + int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { _cleanup_free_ char *fs = NULL; int r; @@ -1684,12 +1695,13 @@ int cg_get_attribute_as_uint64(const char *controller, const char *path, const c return 0; } -int cg_get_keyed_attribute( +int cg_get_keyed_attribute_full( const char *controller, const char *path, const char *attribute, char **keys, - char **ret_values) { + char **ret_values, + CGroupKeyMode mode) { _cleanup_free_ char *filename = NULL, *contents = NULL; const char *p; @@ -1701,7 +1713,8 @@ int cg_get_keyed_attribute( * all keys to retrieve. The 'ret_values' parameter should be passed as string size with the same number of * entries as 'keys'. On success each entry will be set to the value of the matching key. * - * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. */ + * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. If mode + * is set to GG_KEY_MODE_GRACEFUL we ignore missing keys and return those that were parsed successfully. */ r = cg_get_path(controller, path, attribute, &filename); if (r < 0) @@ -1749,6 +1762,9 @@ int cg_get_keyed_attribute( p += strspn(p, NEWLINE); } + if (mode & CG_KEY_MODE_GRACEFUL) + goto done; + r = -ENXIO; fail: @@ -1759,6 +1775,9 @@ fail: done: memcpy(ret_values, v, sizeof(char*) * n); + if (mode & CG_KEY_MODE_GRACEFUL) + return n_done; + return 0; } diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 237139fad0..2b88571bc1 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -180,9 +180,31 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path); int cg_rmdir(const char *controller, const char *path); +typedef enum { + CG_KEY_MODE_GRACEFUL = 1 << 0, +} CGroupKeyMode; + int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); -int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, char **keys, char **values); +int cg_get_keyed_attribute_full(const char *controller, const char *path, const char *attribute, char **keys, char **values, CGroupKeyMode mode); + +static inline int cg_get_keyed_attribute( + const char *controller, + const char *path, + const char *attribute, + char **keys, + char **ret_values) { + return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, 0); +} + +static inline int cg_get_keyed_attribute_graceful( + const char *controller, + const char *path, + const char *attribute, + char **keys, + char **ret_values) { + return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, CG_KEY_MODE_GRACEFUL); +} int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret); @@ -238,6 +260,7 @@ int cg_mask_to_string(CGroupMask mask, char **ret); int cg_kernel_controllers(Set **controllers); bool cg_ns_supported(void); +bool cg_freezer_supported(void); int cg_all_unified(void); int cg_hybrid_unified(void); diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index dba218b388..64b2b2dd7e 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -108,6 +108,15 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); +static const char* const freezer_state_table[_FREEZER_STATE_MAX] = { + [FREEZER_RUNNING] = "running", + [FREEZER_FREEZING] = "freezing", + [FREEZER_FROZEN] = "frozen", + [FREEZER_THAWING] = "thawing", +}; + +DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState); + static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { [AUTOMOUNT_DEAD] = "dead", [AUTOMOUNT_WAITING] = "waiting", diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index edb3ff8fe8..a7d6781988 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -48,6 +48,15 @@ typedef enum UnitActiveState { _UNIT_ACTIVE_STATE_INVALID = -1 } UnitActiveState; +typedef enum FreezerState { + FREEZER_RUNNING, + FREEZER_FREEZING, + FREEZER_FROZEN, + FREEZER_THAWING, + _FREEZER_STATE_MAX, + _FREEZER_STATE_INVALID = -1 +} FreezerState; + typedef enum AutomountState { AUTOMOUNT_DEAD, AUTOMOUNT_WAITING, @@ -253,6 +262,9 @@ UnitLoadState unit_load_state_from_string(const char *s) _pure_; const char *unit_active_state_to_string(UnitActiveState i) _const_; UnitActiveState unit_active_state_from_string(const char *s) _pure_; +const char *freezer_state_to_string(FreezerState i) _const_; +FreezerState freezer_state_from_string(const char *s) _pure_; + const char* automount_state_to_string(AutomountState i) _const_; AutomountState automount_state_from_string(const char *s) _pure_; diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 5e4fe600a2..56598d3baa 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -16,7 +16,9 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "io-util.h" #include "limits-util.h" +#include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -2661,6 +2663,16 @@ void unit_add_to_cgroup_empty_queue(Unit *u) { log_debug_errno(r, "Failed to enable cgroup empty event source: %m"); } +static void unit_remove_from_cgroup_empty_queue(Unit *u) { + assert(u); + + if (!u->in_cgroup_empty_queue) + return; + + LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u); + u->in_cgroup_empty_queue = false; +} + int unit_check_oom(Unit *u) { _cleanup_free_ char *oom_kill = NULL; bool increased; @@ -2761,6 +2773,41 @@ static void unit_add_to_cgroup_oom_queue(Unit *u) { log_error_errno(r, "Failed to enable cgroup oom event source: %m"); } +static int unit_check_cgroup_events(Unit *u) { + char *values[2] = {}; + int r; + + assert(u); + + r = cg_get_keyed_attribute_graceful(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", + STRV_MAKE("populated", "frozen"), values); + if (r < 0) + return r; + + /* The cgroup.events notifications can be merged together so act as we saw the given state for the + * first time. The functions we call to handle given state are idempotent, which makes them + * effectively remember the previous state. */ + if (values[0]) { + if (streq(values[0], "1")) + unit_remove_from_cgroup_empty_queue(u); + else + unit_add_to_cgroup_empty_queue(u); + } + + /* Disregard freezer state changes due to operations not initiated by us */ + if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) { + if (streq(values[1], "0")) + unit_thawed(u); + else + unit_frozen(u); + } + + free(values[0]); + free(values[1]); + + return 0; +} + static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { Manager *m = userdata; @@ -2797,7 +2844,7 @@ static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, u = hashmap_get(m->cgroup_control_inotify_wd_unit, INT_TO_PTR(e->wd)); if (u) - unit_add_to_cgroup_empty_queue(u); + unit_check_cgroup_events(u); u = hashmap_get(m->cgroup_memory_inotify_wd_unit, INT_TO_PTR(e->wd)); if (u) @@ -3550,6 +3597,46 @@ int compare_job_priority(const void *a, const void *b) { return strcmp(x->unit->id, y->unit->id); } +int unit_cgroup_freezer_action(Unit *u, FreezerAction action) { + _cleanup_free_ char *path = NULL; + FreezerState target, kernel = _FREEZER_STATE_INVALID; + int r; + + assert(u); + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); + + if (!u->cgroup_realized) + return -EBUSY; + + target = action == FREEZER_FREEZE ? FREEZER_FROZEN : FREEZER_RUNNING; + + r = unit_freezer_state_kernel(u, &kernel); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to obtain cgroup freezer state: %m"); + + if (target == kernel) { + u->freezer_state = target; + return 0; + } + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.freeze", &path); + if (r < 0) + return r; + + log_unit_debug(u, "%s unit.", action == FREEZER_FREEZE ? "Freezing" : "Thawing"); + + if (action == FREEZER_FREEZE) + u->freezer_state = FREEZER_FREEZING; + else + u->freezer_state = FREEZER_THAWING; + + r = write_string_file(path, one_zero(action == FREEZER_FREEZE), WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return r; + + return 0; +} + static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { [CGROUP_DEVICE_POLICY_AUTO] = "auto", [CGROUP_DEVICE_POLICY_CLOSED] = "closed", @@ -3585,3 +3672,10 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) { } DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); + +static const char* const freezer_action_table[_FREEZER_ACTION_MAX] = { + [FREEZER_FREEZE] = "freeze", + [FREEZER_THAW] = "thaw", +}; + +DEFINE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction); diff --git a/src/core/cgroup.h b/src/core/cgroup.h index b6bd4e0de5..52d028e740 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -47,6 +47,14 @@ typedef enum CGroupDevicePolicy { _CGROUP_DEVICE_POLICY_INVALID = -1 } CGroupDevicePolicy; +typedef enum FreezerAction { + FREEZER_FREEZE, + FREEZER_THAW, + + _FREEZER_ACTION_MAX, + _FREEZER_ACTION_INVALID = -1, +} FreezerAction; + struct CGroupDeviceAllow { LIST_FIELDS(CGroupDeviceAllow, device_allow); char *path; @@ -274,3 +282,7 @@ bool unit_cgroup_delegate(Unit *u); int compare_job_priority(const void *a, const void *b); int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name); +int unit_cgroup_freezer_action(Unit *u, FreezerAction action); + +const char* freezer_action_to_string(FreezerAction a) _const_; +FreezerAction freezer_action_from_string(const char *s) _pure_; diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5f862e47fd..f8a13bd637 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -620,6 +620,14 @@ static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_err return method_generic_unit_operation(message, userdata, error, bus_unit_method_clean, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED); } +static int method_freeze_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_generic_unit_operation(message, userdata, error, bus_unit_method_freeze, 0); +} + +static int method_thaw_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_generic_unit_operation(message, userdata, error, bus_unit_method_thaw, 0); +} + static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { /* Don't load the unit (because unloaded units can't be in failed state), and don't insist on the * unit to be loaded properly (since a failed unit might have its unit file disappeared) */ @@ -2584,6 +2592,18 @@ const sd_bus_vtable bus_manager_vtable[] = { NULL,, method_clean_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("FreezeUnit", + "s", + SD_BUS_PARAM(name), + NULL,, + method_freeze_unit, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ThawUnit", + "s", + SD_BUS_PARAM(name), + NULL,, + method_thaw_unit, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ResetFailedUnit", "s", SD_BUS_PARAM(name), diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 7a1f5041f3..75e9060649 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -46,12 +46,14 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction); static BUS_DEFINE_PROPERTY_GET(property_get_description, "s", Unit, unit_description); static BUS_DEFINE_PROPERTY_GET2(property_get_active_state, "s", Unit, unit_active_state, unit_active_state_to_string); +static BUS_DEFINE_PROPERTY_GET2(property_get_freezer_state, "s", Unit, unit_freezer_state, freezer_state_to_string); static BUS_DEFINE_PROPERTY_GET(property_get_sub_state, "s", Unit, unit_sub_state_to_string); static BUS_DEFINE_PROPERTY_GET2(property_get_unit_file_state, "s", Unit, unit_get_unit_file_state, unit_file_state_to_string); static BUS_DEFINE_PROPERTY_GET(property_get_can_reload, "b", Unit, unit_can_reload); static BUS_DEFINE_PROPERTY_GET(property_get_can_start, "b", Unit, unit_can_start_refuse_manual); static BUS_DEFINE_PROPERTY_GET(property_get_can_stop, "b", Unit, unit_can_stop_refuse_manual); static BUS_DEFINE_PROPERTY_GET(property_get_can_isolate, "b", Unit, unit_can_isolate_refuse_manual); +static BUS_DEFINE_PROPERTY_GET(property_get_can_freeze, "b", Unit, unit_can_freeze); static BUS_DEFINE_PROPERTY_GET(property_get_need_daemon_reload, "b", Unit, unit_need_daemon_reload); static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_strv, "as", 0); @@ -724,6 +726,79 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error return sd_bus_reply_method_return(message, NULL); } +static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userdata, sd_bus_error *error, FreezerAction action) { + const char* perm; + int (*method)(Unit*); + Unit *u = userdata; + bool reply_no_delay = false; + int r; + + assert(message); + assert(u); + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); + + if (action == FREEZER_FREEZE) { + perm = "stop"; + method = unit_freeze; + } else { + perm = "start"; + method = unit_thaw; + } + + r = mac_selinux_unit_access_check(u, message, perm, error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async_full( + u, + perm, + CAP_SYS_ADMIN, + N_("Authentication is required to freeze or thaw the processes of '$(unit)' unit."), + true, + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = method(u); + if (r == -EOPNOTSUPP) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not support freezing.", u->id); + if (r == -EBUSY) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_BUSY, "Unit has a pending job."); + if (r == -EHOSTDOWN) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_INACTIVE, "Unit is inactive."); + if (r == -EALREADY) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Previously requested freezer operation for unit '%s' is still in progress.", u->id); + if (r < 0) + return r; + if (r == 0) + reply_no_delay = true; + + assert(!u->pending_freezer_message); + + r = sd_bus_message_new_method_return(message, &u->pending_freezer_message); + if (r < 0) + return r; + + if (reply_no_delay) { + r = bus_unit_send_pending_freezer_message(u); + if (r < 0) + return r; + } + + return 1; +} + +int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_THAW); +} + +int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_FREEZE); +} + static int property_get_refs( sd_bus *bus, const char *path, @@ -793,6 +868,7 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("FreezerState", "s", property_get_freezer_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST), @@ -809,6 +885,7 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CanClean", "as", property_get_can_clean, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanFreeze", "b", property_get_can_freeze, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Job", "(uo)", property_get_job, offsetof(Unit, job), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST), @@ -940,6 +1017,16 @@ const sd_bus_vtable bus_unit_vtable[] = { NULL,, bus_unit_method_clean, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Freeze", + NULL, + NULL, + bus_unit_method_freeze, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Thaw", + NULL, + NULL, + bus_unit_method_thaw, + SD_BUS_VTABLE_UNPRIVILEGED), /* For dependency types we don't support anymore always return an empty array */ SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_empty_strv, 0, SD_BUS_VTABLE_HIDDEN), @@ -1566,6 +1653,23 @@ void bus_unit_send_pending_change_signal(Unit *u, bool including_new) { bus_unit_send_change_signal(u); } +int bus_unit_send_pending_freezer_message(Unit *u) { + int r; + + assert(u); + + if (!u->pending_freezer_message) + return 0; + + r = sd_bus_send(NULL, u->pending_freezer_message, NULL); + if (r < 0) + log_warning_errno(r, "Failed to send queued message, ignoring: %m"); + + u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message); + + return 0; +} + static int send_removed_signal(sd_bus *bus, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *p = NULL; diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h index 91711311a7..30c86ecb14 100644 --- a/src/core/dbus-unit.h +++ b/src/core/dbus-unit.h @@ -11,6 +11,7 @@ extern const sd_bus_vtable bus_unit_cgroup_vtable[]; void bus_unit_send_change_signal(Unit *u); void bus_unit_send_pending_change_signal(Unit *u, bool including_new); +int bus_unit_send_pending_freezer_message(Unit *u); void bus_unit_send_removed_signal(Unit *u); int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error); @@ -25,6 +26,8 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error); typedef enum BusUnitQueueFlags { BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE = 1 << 0, diff --git a/src/core/dbus.c b/src/core/dbus.c index 8a586e1171..17c961edef 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -960,10 +960,15 @@ static void destroy_bus(Manager *m, sd_bus **bus) { if (j->bus_track && sd_bus_track_get_bus(j->bus_track) == *bus) j->bus_track = sd_bus_track_unref(j->bus_track); - HASHMAP_FOREACH(u, m->units, i) + HASHMAP_FOREACH(u, m->units, i) { if (u->bus_track && sd_bus_track_get_bus(u->bus_track) == *bus) u->bus_track = sd_bus_track_unref(u->bus_track); + /* Get rid of pending freezer messages on this bus */ + if (u->pending_freezer_message && sd_bus_message_get_bus(u->pending_freezer_message) == *bus) + u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message); + } + /* Get rid of queued message on this bus */ if (m->pending_reload_message && sd_bus_message_get_bus(m->pending_reload_message) == *bus) m->pending_reload_message = sd_bus_message_unref(m->pending_reload_message); diff --git a/src/core/scope.c b/src/core/scope.c index 76358c416a..e4a536d597 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -635,6 +635,9 @@ const UnitVTable scope_vtable = { .kill = scope_kill, + .freeze = unit_freeze_vtable_common, + .thaw = unit_thaw_vtable_common, + .get_timeout = scope_get_timeout, .serialize = scope_serialize, diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h index da2e6cbd74..58f737de09 100644 --- a/src/core/selinux-access.h +++ b/src/core/selinux-access.h @@ -7,17 +7,8 @@ int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, const char *permission, sd_bus_error *error); -#if HAVE_SELINUX - #define mac_selinux_access_check(message, permission, error) \ mac_selinux_generic_access_check((message), NULL, (permission), (error)) #define mac_selinux_unit_access_check(unit, message, permission, error) \ mac_selinux_generic_access_check((message), unit_label_path(unit), (permission), (error)) - -#else - -#define mac_selinux_access_check(message, permission, error) 0 -#define mac_selinux_unit_access_check(unit, message, permission, error) 0 - -#endif diff --git a/src/core/service.c b/src/core/service.c index 292df8d5e1..d97270598a 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -4458,6 +4458,9 @@ const UnitVTable service_vtable = { .clean = service_clean, .can_clean = service_can_clean, + .freeze = unit_freeze_vtable_common, + .thaw = unit_thaw_vtable_common, + .serialize = service_serialize, .deserialize_item = service_deserialize_item, diff --git a/src/core/slice.c b/src/core/slice.c index d97a262786..558545d310 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "dbus-slice.h" #include "dbus-unit.h" +#include "fd-util.h" #include "log.h" #include "serialize.h" #include "slice.h" @@ -347,6 +348,82 @@ static void slice_enumerate_perpetual(Manager *m) { (void) slice_make_perpetual(m, SPECIAL_SYSTEM_SLICE, NULL); } +static bool slice_freezer_action_supported_by_children(Unit *s) { + Unit *member; + void *v; + Iterator i; + + assert(s); + + HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) { + int r; + + if (UNIT_DEREF(member->slice) != s) + continue; + + if (member->type == UNIT_SLICE) { + r = slice_freezer_action_supported_by_children(member); + if (!r) + return r; + } + + if (!UNIT_VTABLE(member)->freeze) + return false; + } + + return true; +} + +static int slice_freezer_action(Unit *s, FreezerAction action) { + Unit *member; + void *v; + Iterator i; + int r; + + assert(s); + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); + + if (!slice_freezer_action_supported_by_children(s)) + return log_unit_warning(s, "Requested freezer operation is not supported by all children of the slice"); + + HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) { + if (UNIT_DEREF(member->slice) != s) + continue; + + if (action == FREEZER_FREEZE) + r = UNIT_VTABLE(member)->freeze(member); + else + r = UNIT_VTABLE(member)->thaw(member); + + if (r < 0) + return r; + } + + r = unit_cgroup_freezer_action(s, action); + if (r < 0) + return r; + + return 0; +} + +static int slice_freeze(Unit *s) { + assert(s); + + return slice_freezer_action(s, FREEZER_FREEZE); +} + +static int slice_thaw(Unit *s) { + assert(s); + + return slice_freezer_action(s, FREEZER_THAW); +} + +static bool slice_can_freeze(Unit *s) { + assert(s); + + return slice_freezer_action_supported_by_children(s); +} + const UnitVTable slice_vtable = { .object_size = sizeof(Slice), .cgroup_context_offset = offsetof(Slice, cgroup_context), @@ -371,6 +448,10 @@ const UnitVTable slice_vtable = { .kill = slice_kill, + .freeze = slice_freeze, + .thaw = slice_thaw, + .can_freeze = slice_can_freeze, + .serialize = slice_serialize, .deserialize_item = slice_deserialize_item, diff --git a/src/core/unit.c b/src/core/unit.c index 6a33657b88..8ef9e4fed5 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -628,6 +628,7 @@ void unit_free(Unit *u) { sd_bus_slot_unref(u->match_bus_slot); sd_bus_track_unref(u->bus_track); u->deserialized_refs = strv_free(u->deserialized_refs); + u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message); unit_free_requires_mounts_for(u); @@ -737,6 +738,38 @@ void unit_free(Unit *u) { free(u); } +FreezerState unit_freezer_state(Unit *u) { + assert(u); + + return u->freezer_state; +} + +int unit_freezer_state_kernel(Unit *u, FreezerState *ret) { + char *values[1] = {}; + int r; + + assert(u); + + r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", + STRV_MAKE("frozen"), values); + if (r < 0) + return r; + + r = _FREEZER_STATE_INVALID; + + if (values[0]) { + if (streq(values[0], "0")) + r = FREEZER_RUNNING; + else if (streq(values[0], "1")) + r = FREEZER_FROZEN; + } + + free(values[0]); + *ret = r; + + return 0; +} + UnitActiveState unit_active_state(Unit *u) { assert(u); @@ -1846,6 +1879,7 @@ int unit_start(Unit *u) { * waits for a holdoff timer to elapse before it will start again. */ unit_add_to_dbus_queue(u); + unit_cgroup_freezer_action(u, FREEZER_THAW); return UNIT_VTABLE(u)->start(u); } @@ -1898,6 +1932,7 @@ int unit_stop(Unit *u) { return -EBADR; unit_add_to_dbus_queue(u); + unit_cgroup_freezer_action(u, FREEZER_THAW); return UNIT_VTABLE(u)->stop(u); } @@ -1954,6 +1989,8 @@ int unit_reload(Unit *u) { return 0; } + unit_cgroup_freezer_action(u, FREEZER_THAW); + return UNIT_VTABLE(u)->reload(u); } @@ -3497,6 +3534,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { if (!sd_id128_is_null(u->invocation_id)) (void) serialize_item_format(f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id)); + (void) serialize_item_format(f, "freezer-state", "%s", freezer_state_to_string(unit_freezer_state(u))); + bus_track_serialize(u->bus_track, f, "ref"); for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { @@ -3805,6 +3844,16 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m"); } + continue; + } else if (streq(l, "freezer-state")) { + FreezerState s; + + s = freezer_state_from_string(v); + if (s < 0) + log_unit_debug(u, "Failed to deserialize freezer-state '%s', ignoring.", v); + else + u->freezer_state = s; + continue; } @@ -6076,6 +6125,80 @@ int unit_can_clean(Unit *u, ExecCleanMask *ret) { return UNIT_VTABLE(u)->can_clean(u, ret); } +bool unit_can_freeze(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->can_freeze) + return UNIT_VTABLE(u)->can_freeze(u); + + return UNIT_VTABLE(u)->freeze; +} + +void unit_frozen(Unit *u) { + assert(u); + + u->freezer_state = FREEZER_FROZEN; + + bus_unit_send_pending_freezer_message(u); +} + +void unit_thawed(Unit *u) { + assert(u); + + u->freezer_state = FREEZER_RUNNING; + + bus_unit_send_pending_freezer_message(u); +} + +static int unit_freezer_action(Unit *u, FreezerAction action) { + UnitActiveState s; + int (*method)(Unit*); + int r; + + assert(u); + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); + + method = action == FREEZER_FREEZE ? UNIT_VTABLE(u)->freeze : UNIT_VTABLE(u)->thaw; + if (!method || !cg_freezer_supported()) + return -EOPNOTSUPP; + + if (u->job) + return -EBUSY; + + if (u->load_state != UNIT_LOADED) + return -EHOSTDOWN; + + s = unit_active_state(u); + if (s != UNIT_ACTIVE) + return -EHOSTDOWN; + + if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) + return -EALREADY; + + r = method(u); + if (r <= 0) + return r; + + return 1; +} + +int unit_freeze(Unit *u) { + return unit_freezer_action(u, FREEZER_FREEZE); +} + +int unit_thaw(Unit *u) { + return unit_freezer_action(u, FREEZER_THAW); +} + +/* Wrappers around low-level cgroup freezer operations common for service and scope units */ +int unit_freeze_vtable_common(Unit *u) { + return unit_cgroup_freezer_action(u, FREEZER_FREEZE); +} + +int unit_thaw_vtable_common(Unit *u) { + return unit_cgroup_freezer_action(u, FREEZER_THAW); +} + static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { [COLLECT_INACTIVE] = "inactive", [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed", diff --git a/src/core/unit.h b/src/core/unit.h index e38871a97c..a05fd49e29 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -114,6 +114,9 @@ typedef struct Unit { UnitLoadState load_state; Unit *merged_into; + FreezerState freezer_state; + sd_bus_message *pending_freezer_message; + char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ char *instance; @@ -483,6 +486,11 @@ typedef struct UnitVTable { /* Clear out the various runtime/state/cache/logs/configuration data */ int (*clean)(Unit *u, ExecCleanMask m); + /* Freeze the unit */ + int (*freeze)(Unit *u); + int (*thaw)(Unit *u); + bool (*can_freeze)(Unit *u); + /* Return which kind of data can be cleaned */ int (*can_clean)(Unit *u, ExecCleanMask *ret); @@ -695,6 +703,8 @@ const char *unit_status_string(Unit *u) _pure_; bool unit_has_name(const Unit *u, const char *name); UnitActiveState unit_active_state(Unit *u); +FreezerState unit_freezer_state(Unit *u); +int unit_freezer_state_kernel(Unit *u, FreezerState *ret); const char* unit_sub_state_to_string(Unit *u); @@ -878,6 +888,16 @@ void unit_destroy_runtime_directory(Unit *u, const ExecContext *context); int unit_clean(Unit *u, ExecCleanMask mask); int unit_can_clean(Unit *u, ExecCleanMask *ret_mask); +bool unit_can_freeze(Unit *u); +int unit_freeze(Unit *u); +void unit_frozen(Unit *u); + +int unit_thaw(Unit *u); +void unit_thawed(Unit *u); + +int unit_freeze_vtable_common(Unit *u); +int unit_thaw_vtable_common(Unit *u); + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full(unit, level, error, ...) \ diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index e5f92b9ec2..dc58f88bbd 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -29,6 +29,7 @@ #define BUS_ERROR_DISK_FULL "org.freedesktop.systemd1.DiskFull" #define BUS_ERROR_NOTHING_TO_CLEAN "org.freedesktop.systemd1.NothingToClean" #define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy" +#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive" #define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" #define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index d319d5d375..33f960bd93 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3834,11 +3834,12 @@ static int kill_unit(int argc, char *argv[], void *userdata) { return r; } -static int clean_unit(int argc, char *argv[], void *userdata) { +static int clean_or_freeze_unit(int argc, char *argv[], void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; _cleanup_strv_free_ char **names = NULL; int r, ret = EXIT_SUCCESS; char **name; + const char *method; sd_bus *bus; r = acquire_bus(BUS_FULL, &bus); @@ -3863,6 +3864,13 @@ static int clean_unit(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to allocate unit waiter: %m"); } + if (streq(argv[0], "clean")) + method = "CleanUnit"; + else if (streq(argv[0], "freeze")) + method = "FreezeUnit"; + else if (streq(argv[0], "thaw")) + method = "ThawUnit"; + STRV_FOREACH(name, names) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; @@ -3892,7 +3900,7 @@ static int clean_unit(int argc, char *argv[], void *userdata) { "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "CleanUnit"); + method); if (r < 0) return bus_log_create_error(r); @@ -3900,13 +3908,15 @@ static int clean_unit(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append_strv(m, arg_clean_what); - if (r < 0) - return bus_log_create_error(r); + if (streq(method, "CleanUnit")) { + r = sd_bus_message_append_strv(m, arg_clean_what); + if (r < 0) + return bus_log_create_error(r); + } r = sd_bus_call(bus, m, 0, &error, NULL); if (r < 0) { - log_error_errno(r, "Failed to clean unit %s: %s", *name, bus_error_message(&error, r)); + log_error_errno(r, "Failed to %s unit %s: %s", argv[0], *name, bus_error_message(&error, r)); if (ret == EXIT_SUCCESS) { ret = r; continue; @@ -4046,6 +4056,7 @@ typedef struct UnitStatusInfo { const char *id; const char *load_state; const char *active_state; + const char *freezer_state; const char *sub_state; const char *unit_file_state; const char *unit_file_preset; @@ -4182,7 +4193,7 @@ static void print_status_info( bool *ellipsized) { char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; - const char *s1, *s2, *active_on, *active_off, *on, *off, *ss; + const char *s1, *s2, *active_on, *active_off, *on, *off, *ss, *fs; _cleanup_free_ char *formatted_path = NULL; ExecStatusInfo *p; usec_t timestamp; @@ -4276,12 +4287,16 @@ static void print_status_info( ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; if (ss) - printf(" Active: %s%s (%s)%s", + printf(" Active: %s%s (%s)%s", active_on, strna(i->active_state), ss, active_off); else printf(" Active: %s%s%s", active_on, strna(i->active_state), active_off); + fs = !isempty(i->freezer_state) && !streq(i->freezer_state, "running") ? i->freezer_state : NULL; + if (fs) + printf(" %s(%s)%s", ansi_highlight_yellow(), fs, active_off); + if (!isempty(i->result) && !streq(i->result, "success")) printf(" (Result: %s)", i->result); @@ -5539,12 +5554,14 @@ static int show_one( static const struct bus_properties_map property_map[] = { { "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) }, { "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) }, + { "FreezerState", "s", NULL, offsetof(UnitStatusInfo, freezer_state) }, { "Documentation", "as", NULL, offsetof(UnitStatusInfo, documentation) }, {} }, status_map[] = { { "Id", "s", NULL, offsetof(UnitStatusInfo, id) }, { "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) }, { "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) }, + { "FreezerState", "s", NULL, offsetof(UnitStatusInfo, freezer_state) }, { "SubState", "s", NULL, offsetof(UnitStatusInfo, sub_state) }, { "UnitFileState", "s", NULL, offsetof(UnitStatusInfo, unit_file_state) }, { "UnitFilePreset", "s", NULL, offsetof(UnitStatusInfo, unit_file_preset) }, @@ -7887,6 +7904,8 @@ static int systemctl_help(void) { " kill UNIT... Send signal to processes of a unit\n" " clean UNIT... Clean runtime, cache, state, logs or\n" " configuration of unit\n" + " freeze PATTERN... Freeze execution of unit processes\n" + " thaw PATTERN... Resume execution of a frozen unit\n" " is-active PATTERN... Check whether units are active\n" " is-failed PATTERN... Check whether units are failed\n" " status [PATTERN...|PID...] Show runtime status of one or more units\n" @@ -9160,7 +9179,9 @@ static int systemctl_main(int argc, char *argv[]) { { "condrestart", 2, VERB_ANY, VERB_ONLINE_ONLY, start_unit }, /* For compatibility with RH */ { "isolate", 2, 2, VERB_ONLINE_ONLY, start_unit }, { "kill", 2, VERB_ANY, VERB_ONLINE_ONLY, kill_unit }, - { "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_unit }, + { "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_or_freeze_unit }, + { "freeze", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_or_freeze_unit }, + { "thaw", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_or_freeze_unit }, { "is-active", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active }, { "check", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active }, /* deprecated alias of is-active */ { "is-failed", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_failed }, diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c index daaffb5a06..5826d6ec28 100644 --- a/src/test/test-cgroup-util.c +++ b/src/test/test-cgroup-util.c @@ -384,22 +384,42 @@ static void test_cg_get_keyed_attribute(void) { } assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == -ENXIO); + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == 0); assert_se(val == NULL); assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 0); + free(val); + + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 1); log_info("cpu /init.scope cpu.stat [usage_usec] → \"%s\"", val); assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == -ENXIO); + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == 1); + assert(vals3[0] && !vals3[1]); + free(vals3[0]); assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == -ENXIO); + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == 1); + assert(vals3[0] && !vals3[1]); + free(vals3[0]); assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 0); + for (i = 0; i < 3; i++) + free(vals3[i]); + + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 3); log_info("cpu /init.scope cpu.stat [usage_usec user_usec system_usec] → \"%s\", \"%s\", \"%s\"", vals3[0], vals3[1], vals3[2]); assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 0); + for (i = 0; i < 3; i++) + free(vals3a[i]); + + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 3); log_info("cpu /init.scope cpu.stat [system_usec user_usec usage_usec] → \"%s\", \"%s\", \"%s\"", vals3a[0], vals3a[1], vals3a[2]); diff --git a/test/TEST-38-FREEZER/Makefile b/test/TEST-38-FREEZER/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-38-FREEZER/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-38-FREEZER/test.sh b/test/TEST-38-FREEZER/test.sh new file mode 100755 index 0000000000..3821db9f00 --- /dev/null +++ b/test/TEST-38-FREEZER/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +TEST_DESCRIPTION="test unit freezing and thawing via DBus and systemctl" +TEST_NO_NSPAWN=1 +. $TEST_BASE_DIR/test-functions + +do_test "$@" 38 diff --git a/test/test-functions b/test/test-functions index 0b56eb226f..79130c3984 100644 --- a/test/test-functions +++ b/test/test-functions @@ -102,6 +102,7 @@ BASICTOOLS=( tar tee test + timeout touch tr true diff --git a/test/units/testsuite-38-sleep.service b/test/units/testsuite-38-sleep.service new file mode 100644 index 0000000000..859f97b360 --- /dev/null +++ b/test/units/testsuite-38-sleep.service @@ -0,0 +1,2 @@ +[Service] +ExecStart=/bin/sleep 3600 diff --git a/test/units/testsuite-38.service b/test/units/testsuite-38.service new file mode 100644 index 0000000000..c848840ba0 --- /dev/null +++ b/test/units/testsuite-38.service @@ -0,0 +1,6 @@ +[Unit] +Description=TEST-38-FREEZER + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-38.sh b/test/units/testsuite-38.sh new file mode 100755 index 0000000000..6fcadb8f8e --- /dev/null +++ b/test/units/testsuite-38.sh @@ -0,0 +1,293 @@ +#!/usr/bin/env bash + +set -ex +set -o pipefail + +systemd-analyze log-level debug +systemd-analyze log-target console + +unit=testsuite-38-sleep.service + +start_test_service() { + systemctl daemon-reload + systemctl start "${unit}" +} + +dbus_freeze() { + local suffix= + suffix="${1##*.}" + + local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" + local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" + + busctl call \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + Freeze +} + +dbus_thaw() { + local suffix= + suffix="${1##*.}" + + local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" + local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" + + busctl call \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + Thaw +} + +dbus_freeze_unit() { + busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + FreezeUnit \ + s \ + "$1" +} + +dbus_thaw_unit() { + busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + ThawUnit \ + s \ + "$1" +} + +dbus_can_freeze() { + local suffix= + suffix="${1##*.}" + + local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" + local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" + + busctl get-property \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + CanFreeze +} + +check_freezer_state() { + local suffix= + suffix="${1##*.}" + + local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" + local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" + + state=$(busctl get-property \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + FreezerState | cut -d " " -f2 | tr -d '"') + + [ "$state" = "$2" ] || { + echo "error: unexpected freezer state, expected: $2, actual: $state" >&2 + exit 1 + } +} + +check_cgroup_state() { + grep -q "frozen $2" /sys/fs/cgroup/system.slice/"$1"/cgroup.events +} + +test_dbus_api() { + echo "Test that DBus API works:" + echo -n " - Freeze(): " + dbus_freeze "${unit}" + check_freezer_state "${unit}" "frozen" + check_cgroup_state "$unit" 1 + echo "[ OK ]" + + echo -n " - Thaw(): " + dbus_thaw "${unit}" + check_freezer_state "${unit}" "running" + check_cgroup_state "$unit" 0 + echo "[ OK ]" + + echo -n " - FreezeUnit(): " + dbus_freeze_unit "${unit}" + check_freezer_state "${unit}" "frozen" + check_cgroup_state "$unit" 1 + echo "[ OK ]" + + echo -n " - ThawUnit(): " + dbus_thaw_unit "${unit}" + check_freezer_state "${unit}" "running" + check_cgroup_state "$unit" 0 + echo "[ OK ]" + + echo -n " - CanFreeze(): " + output=$(dbus_can_freeze "${unit}") + [ "$output" = "b true" ] + echo "[ OK ]" + + echo +} + +test_jobs() { + local pid_before= + local pid_after= + echo "Test that it is possible to apply jobs on frozen units:" + + systemctl start "${unit}" + dbus_freeze "${unit}" + check_freezer_state "${unit}" "frozen" + + echo -n " - restart: " + pid_before=$(systemctl show -p MainPID "${unit}" --value) + systemctl restart "${unit}" + pid_after=$(systemctl show -p MainPID "${unit}" --value) + [ "$pid_before" != "$pid_after" ] && echo "[ OK ]" + + dbus_freeze "${unit}" + check_freezer_state "${unit}" "frozen" + + echo -n " - stop: " + timeout 5s systemctl stop "${unit}" + echo "[ OK ]" + + echo +} + +test_systemctl() { + echo "Test that systemctl freeze/thaw verbs:" + + systemctl start "$unit" + + echo -n " - freeze: " + systemctl freeze "$unit" + check_freezer_state "${unit}" "frozen" + check_cgroup_state "$unit" 1 + # Freezing already frozen unit should be NOP and return quickly + timeout 3s systemctl freeze "$unit" + echo "[ OK ]" + + echo -n " - thaw: " + systemctl thaw "$unit" + check_freezer_state "${unit}" "running" + check_cgroup_state "$unit" 0 + # Likewise thawing already running unit shouldn't block + timeout 3s systemctl thaw "$unit" + echo "[ OK ]" + + systemctl stop "$unit" + + echo +} + +test_systemctl_show() { + echo "Test systemctl show integration:" + + systemctl start "$unit" + + echo -n " - FreezerState property: " + state=$(systemctl show -p FreezerState --value "$unit") + [ "$state" = "running" ] + systemctl freeze "$unit" + state=$(systemctl show -p FreezerState --value "$unit") + [ "$state" = "frozen" ] + systemctl thaw "$unit" + echo "[ OK ]" + + echo -n " - CanFreeze property: " + state=$(systemctl show -p CanFreeze --value "$unit") + [ "$state" = "yes" ] + echo "[ OK ]" + + systemctl stop "$unit" + echo +} + +test_recursive() { + local slice="bar.slice" + local unit="baz.service" + + systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1 + + echo "Test recursive freezing:" + + echo -n " - freeze: " + systemctl freeze "$slice" + check_freezer_state "${slice}" "frozen" + check_freezer_state "${unit}" "frozen" + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + echo -n " - thaw: " + systemctl thaw "$slice" + check_freezer_state "${unit}" "running" + check_freezer_state "${slice}" "running" + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + systemctl stop "$unit" + systemctl stop "$slice" + + echo +} + +test_preserve_state() { + local slice="bar.slice" + local unit="baz.service" + + systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1 + + echo "Test that freezer state is preserved when recursive freezing is initiated from outside (e.g. by manager up the tree):" + + echo -n " - freeze from outside: " + echo 1 > /sys/fs/cgroup/"${slice}"/cgroup.freeze + + # Our state should not be affected + check_freezer_state "${slice}" "running" + check_freezer_state "${unit}" "running" + + # However actual kernel state should be frozen + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + echo -n " - thaw from outside: " + echo 0 > /sys/fs/cgroup/"${slice}"/cgroup.freeze + check_freezer_state "${unit}" "running" + check_freezer_state "${slice}" "running" + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + echo -n " - thaw from outside while inner service is frozen: " + systemctl freeze "$unit" + check_freezer_state "${unit}" "frozen" + echo 1 > /sys/fs/cgroup/"${slice}"/cgroup.freeze + echo 0 > /sys/fs/cgroup/"${slice}"/cgroup.freeze + check_freezer_state "${slice}" "running" + check_freezer_state "${unit}" "frozen" + echo "[ OK ]" + + systemctl stop "$unit" + systemctl stop "$slice" + + echo +} + +test -e /sys/fs/cgroup/system.slice/cgroup.freeze && { + start_test_service + test_dbus_api + test_systemctl + test_systemctl_show + test_jobs + test_recursive + test_preserve_state +} + +echo OK > /testok +exit 0