]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: introduce support for cgroup freezer
authorMichal Sekletár <msekleta@redhat.com>
Wed, 29 Apr 2020 15:53:43 +0000 (17:53 +0200)
committerMichal Sekletár <msekleta@redhat.com>
Thu, 30 Apr 2020 17:02:51 +0000 (19:02 +0200)
With cgroup v2 the cgroup freezer is implemented as a cgroup
attribute called cgroup.freeze. cgroup can be frozen by writing "1"
to the file and kernel will send us a notification through
"cgroup.events" after the operation is finished and processes in the
cgroup entered quiescent state, i.e. they are not scheduled to
run. Writing "0" to the attribute file does the inverse and process
execution is resumed.

This commit exposes above low-level functionality through systemd's DBus
API. Each unit type must provide specialized implementation for these
methods, otherwise, we return an error. So far only service, scope, and
slice unit types provide the support. It is possible to check if a
given unit has the support using CanFreeze() DBus property.

Note that DBus API has a synchronous behavior and we dispatch the reply
to freeze/thaw requests only after the kernel has notified us that
requested operation was completed.

19 files changed:
NEWS
man/systemctl.xml
src/basic/cgroup-util.c
src/basic/cgroup-util.h
src/basic/unit-def.c
src/basic/unit-def.h
src/core/cgroup.c
src/core/cgroup.h
src/core/dbus-manager.c
src/core/dbus-unit.c
src/core/dbus-unit.h
src/core/dbus.c
src/core/scope.c
src/core/service.c
src/core/slice.c
src/core/unit.c
src/core/unit.h
src/libsystemd/sd-bus/bus-common-errors.h
src/systemctl/systemctl.c

diff --git a/NEWS b/NEWS
index b5f7bba579c5133f3d49d06ca84a8bf90474dd8e..e7aaa12ca095448a91063aaa0069a54af5a1d060 100644 (file)
--- 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
index 30880b41102f3e109f51794689729e8388bba91c..570c1a5505de879fa94ec467192c3d9dcf6b9eac 100644 (file)
@@ -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).</para>
           </listitem>
         </varlistentry>
+        <varlistentry>
+          <term><command>freeze <replaceable>PATTERN</replaceable>…</command></term>
+
+          <listitem>
+            <para>Freeze one or more units specified on the
+            command line using cgroup freezer</para>
+
+            <para>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.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><command>thaw <replaceable>PATTERN</replaceable>…</command></term>
+
+          <listitem>
+            <para>Thaw (unfreeze) one or more units specified on the
+            command line.</para>
+
+            <para>This is the inverse operation to the <command>freeze</command> command and resumes the execution of
+            processes in the unit's cgroup.</para>
+          </listitem>
+        </varlistentry>
         <varlistentry>
           <term><command>is-active <replaceable>PATTERN</replaceable>…</command></term>
 
index da4bd33d18edb9529a5b00ce498d387f4275aac1..e94fcfad022c6fa9a56afe0709dc1137e900bb59 100644 (file)
@@ -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;
@@ -1702,7 +1713,8 @@ int cg_get_keyed_attribute_full(
          * 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)
@@ -1752,8 +1764,8 @@ int cg_get_keyed_attribute_full(
 
         if (mode & CG_KEY_MODE_GRACEFUL)
                 goto done;
-        else
-                r = -ENXIO;
+
+        r = -ENXIO;
 
 fail:
         for (i = 0; i < n; i++)
index 6e05a8a5a114519b4b16253cc112fe8bb67e27b7..2b88571bc1c7767d57cc9de9d0a417fe2e0cbb82 100644 (file)
@@ -260,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);
index dba218b38869f755bc3f49a67f663cc834f7d200..64b2b2dd7e115ab98a6d5632a42eb06a3d08a23a 100644 (file)
@@ -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",
index edb3ff8fe8aed01db5e3da73e23159c380f625a3..a7d67819884f3785ad883b935ab5634fde88326d 100644 (file)
@@ -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_;
 
index 5e4fe600a2974e788f8409bb1774ca0aaf920ccc..56598d3baad77c4cc54361724d7df4559d00c4a4 100644 (file)
@@ -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);
index b6bd4e0de5312d067204d8305d5ded5c0f75da93..52d028e740ff38e7c29a07be98f32c1e8c15adca 100644 (file)
@@ -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_;
index 5f862e47fda38fb2901c7eae535353e96d314eef..f8a13bd6375081631e15f288a1258e9dead3a79d 100644 (file)
@@ -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),
index 7a1f5041f3f94e0fe2dc439d73e24f930886b4ad..75e906064952c7b4e2db34946c70f4b4579dac3b 100644 (file)
@@ -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;
index 91711311a7836b9f7aae865dd7a798a41b3c0569..30c86ecb14b0bd9c362012bc6a86583b32c6b4f5 100644 (file)
@@ -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,
index 8a586e11710765a11b64611406589d4d3b2d16af..17c961edef5094c16400fd32b7df3f7ca68d0210 100644 (file)
@@ -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);
index 76358c416ad1422930faced879b95c2b8523231f..e4a536d59761f4ef67f6ca85ba31e6eb31232a13 100644 (file)
@@ -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,
index 7d5928e455ce369ef1695b1903bcc539a8ccc6d7..2c1c64067c9ed6e5f30038907fb69e982c344622 100644 (file)
@@ -4449,6 +4449,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,
 
index d97a262786be3d0ad873cd1615256b96bedd83d5..558545d31040ea4e528a94a718cd06ab2eeab291 100644 (file)
@@ -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,
 
index 6a33657b8875119275191af5c22fdfd6e8ac381c..8ef9e4fed58569ed092bfca137ce57744f65e123 100644 (file)
@@ -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",
index e38871a97c0d02599b3e2bd55304f15f43a992a9..a05fd49e29c9ea8e4982670abe8175c8aa4de138 100644 (file)
@@ -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, ...)                          \
index e5f92b9ec261e3c669cf502348ffc6b2625a9967..dc58f88bbd5cc667712e295dea9d2bf170e4f87c 100644 (file)
@@ -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"
index d319d5d375488b3aa510aa6aeb8b721c635fada5..33f960bd93866e3462db80407698f201b955c5fe 100644 (file)
@@ -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       },