c2dfb7
From 046ea98539eb8d7cef93d8062035fa6d9f58efea Mon Sep 17 00:00:00 2001
c2dfb7
From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
c2dfb7
Date: Wed, 29 Apr 2020 17:53:43 +0200
c2dfb7
Subject: [PATCH] core: introduce support for cgroup freezer
c2dfb7
c2dfb7
With cgroup v2 the cgroup freezer is implemented as a cgroup
c2dfb7
attribute called cgroup.freeze. cgroup can be frozen by writing "1"
c2dfb7
to the file and kernel will send us a notification through
c2dfb7
"cgroup.events" after the operation is finished and processes in the
c2dfb7
cgroup entered quiescent state, i.e. they are not scheduled to
c2dfb7
run. Writing "0" to the attribute file does the inverse and process
c2dfb7
execution is resumed.
c2dfb7
c2dfb7
This commit exposes above low-level functionality through systemd's DBus
c2dfb7
API. Each unit type must provide specialized implementation for these
c2dfb7
methods, otherwise, we return an error. So far only service, scope, and
c2dfb7
slice unit types provide the support. It is possible to check if a
c2dfb7
given unit has the support using CanFreeze() DBus property.
c2dfb7
c2dfb7
Note that DBus API has a synchronous behavior and we dispatch the reply
c2dfb7
to freeze/thaw requests only after the kernel has notified us that
c2dfb7
requested operation was completed.
c2dfb7
c2dfb7
(cherry picked from commit d9e45bc3abb8adf5a1cb20816ba8f2d2aa65b17e)
c2dfb7
c2dfb7
Resolves: #1830861
c2dfb7
---
c2dfb7
 man/systemctl.xml                         |  24 +++++
c2dfb7
 src/basic/cgroup-util.c                   |  18 +++-
c2dfb7
 src/basic/cgroup-util.h                   |   1 +
c2dfb7
 src/basic/unit-def.c                      |   9 ++
c2dfb7
 src/basic/unit-def.h                      |  12 +++
c2dfb7
 src/core/cgroup.c                         | 105 ++++++++++++++++--
c2dfb7
 src/core/cgroup.h                         |  12 +++
c2dfb7
 src/core/dbus-manager.c                   |  50 +++++++++
c2dfb7
 src/core/dbus-unit.c                      |  96 +++++++++++++++++
c2dfb7
 src/core/dbus-unit.h                      |   3 +
c2dfb7
 src/core/dbus.c                           |   7 +-
c2dfb7
 src/core/scope.c                          |   3 +
c2dfb7
 src/core/service.c                        |   3 +
c2dfb7
 src/core/slice.c                          |  80 ++++++++++++++
c2dfb7
 src/core/unit.c                           | 123 ++++++++++++++++++++++
c2dfb7
 src/core/unit.h                           |  20 ++++
c2dfb7
 src/libsystemd/sd-bus/bus-common-errors.h |   2 +
c2dfb7
 src/systemctl/systemctl.c                 | 105 +++++++++++++++++-
c2dfb7
 18 files changed, 660 insertions(+), 13 deletions(-)
c2dfb7
c2dfb7
diff --git a/man/systemctl.xml b/man/systemctl.xml
c2dfb7
index d95d3726af..6145486123 100644
c2dfb7
--- a/man/systemctl.xml
c2dfb7
+++ b/man/systemctl.xml
c2dfb7
@@ -873,6 +873,30 @@ Sun 2017-02-26 20:57:49 EST  2h 3min left  Sun 2017-02-26 11:56:36 EST  6h ago
c2dfb7
             the signal to send.</para>
c2dfb7
           </listitem>
c2dfb7
         </varlistentry>
c2dfb7
+        <varlistentry>
c2dfb7
+          <term><command>freeze <replaceable>PATTERN</replaceable>…</command></term>
c2dfb7
+
c2dfb7
+          <listitem>
c2dfb7
+            <para>Freeze one or more units specified on the
c2dfb7
+            command line using cgroup freezer</para>
c2dfb7
+
c2dfb7
+            <para>Freezing the unit will cause all processes contained within the cgroup corresponding to the unit
c2dfb7
+            to be suspended. Being suspended means that unit's processes won't be scheduled to run on CPU until thawed.
c2dfb7
+            Note that this command is supported only on systems that use unified cgroup hierarchy. Unit is automatically
c2dfb7
+            thawed just before we execute a job against the unit, e.g. before the unit is stopped.</para>
c2dfb7
+          </listitem>
c2dfb7
+        </varlistentry>
c2dfb7
+        <varlistentry>
c2dfb7
+          <term><command>thaw <replaceable>PATTERN</replaceable>…</command></term>
c2dfb7
+
c2dfb7
+          <listitem>
c2dfb7
+            <para>Thaw (unfreeze) one or more units specified on the
c2dfb7
+            command line.</para>
c2dfb7
+
c2dfb7
+            <para>This is the inverse operation to the <command>freeze</command> command and resumes the execution of
c2dfb7
+            processes in the unit's cgroup.</para>
c2dfb7
+          </listitem>
c2dfb7
+        </varlistentry>
c2dfb7
         <varlistentry>
c2dfb7
           <term><command>is-active <replaceable>PATTERN</replaceable>…</command></term>
c2dfb7
 
c2dfb7
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
c2dfb7
index 4c0b73850d..992b12811a 100644
c2dfb7
--- a/src/basic/cgroup-util.c
c2dfb7
+++ b/src/basic/cgroup-util.c
c2dfb7
@@ -137,6 +137,17 @@ bool cg_ns_supported(void) {
c2dfb7
         return enabled;
c2dfb7
 }
c2dfb7
 
c2dfb7
+bool cg_freezer_supported(void) {
c2dfb7
+        static thread_local int supported = -1;
c2dfb7
+
c2dfb7
+        if (supported >= 0)
c2dfb7
+                return supported;
c2dfb7
+
c2dfb7
+        supported = cg_all_unified() > 0 && access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) == 0;
c2dfb7
+
c2dfb7
+        return supported;
c2dfb7
+}
c2dfb7
+
c2dfb7
 int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
c2dfb7
         _cleanup_free_ char *fs = NULL;
c2dfb7
         int r;
c2dfb7
@@ -2039,7 +2050,8 @@ int cg_get_keyed_attribute_full(
c2dfb7
          * all keys to retrieve. The 'ret_values' parameter should be passed as string size with the same number of
c2dfb7
          * entries as 'keys'. On success each entry will be set to the value of the matching key.
c2dfb7
          *
c2dfb7
-         * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. */
c2dfb7
+         * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. If mode
c2dfb7
+         * is set to GG_KEY_MODE_GRACEFUL we ignore missing keys and return those that were parsed successfully. */
c2dfb7
 
c2dfb7
         r = cg_get_path(controller, path, attribute, &filename);
c2dfb7
         if (r < 0)
c2dfb7
@@ -2089,8 +2101,8 @@ int cg_get_keyed_attribute_full(
c2dfb7
 
c2dfb7
         if (mode & CG_KEY_MODE_GRACEFUL)
c2dfb7
                 goto done;
c2dfb7
-        else
c2dfb7
-                r = -ENXIO;
c2dfb7
+
c2dfb7
+        r = -ENXIO;
c2dfb7
 
c2dfb7
 fail:
c2dfb7
         for (i = 0; i < n; i++)
c2dfb7
diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h
c2dfb7
index 0673f4b4ce..1210b38a83 100644
c2dfb7
--- a/src/basic/cgroup-util.h
c2dfb7
+++ b/src/basic/cgroup-util.h
c2dfb7
@@ -250,6 +250,7 @@ int cg_mask_to_string(CGroupMask mask, char **ret);
c2dfb7
 int cg_kernel_controllers(Set **controllers);
c2dfb7
 
c2dfb7
 bool cg_ns_supported(void);
c2dfb7
+bool cg_freezer_supported(void);
c2dfb7
 
c2dfb7
 int cg_all_unified(void);
c2dfb7
 int cg_hybrid_unified(void);
c2dfb7
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
c2dfb7
index 46593f6e65..e79cc73dd3 100644
c2dfb7
--- a/src/basic/unit-def.c
c2dfb7
+++ b/src/basic/unit-def.c
c2dfb7
@@ -107,6 +107,15 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
c2dfb7
 
c2dfb7
 DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
c2dfb7
 
c2dfb7
+static const char* const freezer_state_table[_FREEZER_STATE_MAX] = {
c2dfb7
+        [FREEZER_RUNNING] = "running",
c2dfb7
+        [FREEZER_FREEZING] = "freezing",
c2dfb7
+        [FREEZER_FROZEN] = "frozen",
c2dfb7
+        [FREEZER_THAWING] = "thawing",
c2dfb7
+};
c2dfb7
+
c2dfb7
+DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState);
c2dfb7
+
c2dfb7
 static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
c2dfb7
         [AUTOMOUNT_DEAD] = "dead",
c2dfb7
         [AUTOMOUNT_WAITING] = "waiting",
c2dfb7
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
c2dfb7
index db397a31ed..8eea379a6d 100644
c2dfb7
--- a/src/basic/unit-def.h
c2dfb7
+++ b/src/basic/unit-def.h
c2dfb7
@@ -44,6 +44,15 @@ typedef enum UnitActiveState {
c2dfb7
         _UNIT_ACTIVE_STATE_INVALID = -1
c2dfb7
 } UnitActiveState;
c2dfb7
 
c2dfb7
+typedef enum FreezerState {
c2dfb7
+        FREEZER_RUNNING,
c2dfb7
+        FREEZER_FREEZING,
c2dfb7
+        FREEZER_FROZEN,
c2dfb7
+        FREEZER_THAWING,
c2dfb7
+        _FREEZER_STATE_MAX,
c2dfb7
+        _FREEZER_STATE_INVALID = -1
c2dfb7
+} FreezerState;
c2dfb7
+
c2dfb7
 typedef enum AutomountState {
c2dfb7
         AUTOMOUNT_DEAD,
c2dfb7
         AUTOMOUNT_WAITING,
c2dfb7
@@ -245,6 +254,9 @@ UnitLoadState unit_load_state_from_string(const char *s) _pure_;
c2dfb7
 const char *unit_active_state_to_string(UnitActiveState i) _const_;
c2dfb7
 UnitActiveState unit_active_state_from_string(const char *s) _pure_;
c2dfb7
 
c2dfb7
+const char *freezer_state_to_string(FreezerState i) _const_;
c2dfb7
+FreezerState freezer_state_from_string(const char *s) _pure_;
c2dfb7
+
c2dfb7
 const char* automount_state_to_string(AutomountState i) _const_;
c2dfb7
 AutomountState automount_state_from_string(const char *s) _pure_;
c2dfb7
 
c2dfb7
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
c2dfb7
index 7a9857adad..e7ae9273a6 100644
c2dfb7
--- a/src/core/cgroup.c
c2dfb7
+++ b/src/core/cgroup.c
c2dfb7
@@ -2279,6 +2279,51 @@ void unit_add_to_cgroup_empty_queue(Unit *u) {
c2dfb7
                 log_debug_errno(r, "Failed to enable cgroup empty event source: %m");
c2dfb7
 }
c2dfb7
 
c2dfb7
+static void unit_remove_from_cgroup_empty_queue(Unit *u) {
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        if (!u->in_cgroup_empty_queue)
c2dfb7
+                return;
c2dfb7
+
c2dfb7
+        LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u);
c2dfb7
+        u->in_cgroup_empty_queue = false;
c2dfb7
+}
c2dfb7
+
c2dfb7
+static int unit_check_cgroup_events(Unit *u) {
c2dfb7
+        char *values[2] = {};
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        r = cg_get_keyed_attribute_graceful(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events",
c2dfb7
+                                            STRV_MAKE("populated", "frozen"), values);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        /* The cgroup.events notifications can be merged together so act as we saw the given state for the
c2dfb7
+         * first time. The functions we call to handle given state are idempotent, which makes them
c2dfb7
+         * effectively remember the previous state. */
c2dfb7
+        if (values[0]) {
c2dfb7
+                if (streq(values[0], "1"))
c2dfb7
+                        unit_remove_from_cgroup_empty_queue(u);
c2dfb7
+                else
c2dfb7
+                        unit_add_to_cgroup_empty_queue(u);
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        /* Disregard freezer state changes due to operations not initiated by us */
c2dfb7
+        if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) {
c2dfb7
+                if (streq(values[1], "0"))
c2dfb7
+                        unit_thawed(u);
c2dfb7
+                else
c2dfb7
+                        unit_frozen(u);
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        free(values[0]);
c2dfb7
+        free(values[1]);
c2dfb7
+
c2dfb7
+        return 0;
c2dfb7
+}
c2dfb7
+
c2dfb7
 static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
c2dfb7
         Manager *m = userdata;
c2dfb7
 
c2dfb7
@@ -2310,15 +2355,12 @@ static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents,
c2dfb7
                                 /* The watch was just removed */
c2dfb7
                                 continue;
c2dfb7
 
c2dfb7
-                        u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd));
c2dfb7
-                        if (!u) /* Not that inotify might deliver
c2dfb7
-                                 * events for a watch even after it
c2dfb7
-                                 * was removed, because it was queued
c2dfb7
-                                 * before the removal. Let's ignore
c2dfb7
-                                 * this here safely. */
c2dfb7
-                                continue;
c2dfb7
+                        /* Note that inotify might deliver events for a watch even after it was removed,
c2dfb7
+                         * because it was queued before the removal. Let's ignore this here safely. */
c2dfb7
 
c2dfb7
-                        unit_add_to_cgroup_empty_queue(u);
c2dfb7
+                        u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd));
c2dfb7
+                        if (u)
c2dfb7
+                                unit_check_cgroup_events(u);
c2dfb7
                 }
c2dfb7
         }
c2dfb7
 }
c2dfb7
@@ -2884,6 +2926,46 @@ void manager_invalidate_startup_units(Manager *m) {
c2dfb7
                 unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_IO|CGROUP_MASK_BLKIO);
c2dfb7
 }
c2dfb7
 
c2dfb7
+int unit_cgroup_freezer_action(Unit *u, FreezerAction action) {
c2dfb7
+        _cleanup_free_ char *path = NULL;
c2dfb7
+        FreezerState target, kernel = _FREEZER_STATE_INVALID;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(u);
c2dfb7
+        assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
c2dfb7
+
c2dfb7
+        if (!u->cgroup_realized)
c2dfb7
+                return -EBUSY;
c2dfb7
+
c2dfb7
+        target = action == FREEZER_FREEZE ? FREEZER_FROZEN : FREEZER_RUNNING;
c2dfb7
+
c2dfb7
+        r = unit_freezer_state_kernel(u, &kernel);
c2dfb7
+        if (r < 0)
c2dfb7
+                log_unit_debug_errno(u, r, "Failed to obtain cgroup freezer state: %m");
c2dfb7
+
c2dfb7
+        if (target == kernel) {
c2dfb7
+                u->freezer_state = target;
c2dfb7
+                return 0;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.freeze", &path);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        log_unit_debug(u, "%s unit.", action == FREEZER_FREEZE ? "Freezing" : "Thawing");
c2dfb7
+
c2dfb7
+        if (action == FREEZER_FREEZE)
c2dfb7
+                u->freezer_state = FREEZER_FREEZING;
c2dfb7
+        else
c2dfb7
+                u->freezer_state = FREEZER_THAWING;
c2dfb7
+
c2dfb7
+        r = write_string_file(path, one_zero(action == FREEZER_FREEZE), WRITE_STRING_FILE_DISABLE_BUFFER);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        return 0;
c2dfb7
+}
c2dfb7
+
c2dfb7
 static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
c2dfb7
         [CGROUP_AUTO] = "auto",
c2dfb7
         [CGROUP_CLOSED] = "closed",
c2dfb7
@@ -2919,3 +3001,10 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) {
c2dfb7
 }
c2dfb7
 
c2dfb7
 DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);
c2dfb7
+
c2dfb7
+static const char* const freezer_action_table[_FREEZER_ACTION_MAX] = {
c2dfb7
+        [FREEZER_FREEZE] = "freeze",
c2dfb7
+        [FREEZER_THAW] = "thaw",
c2dfb7
+};
c2dfb7
+
c2dfb7
+DEFINE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction);
c2dfb7
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
c2dfb7
index 976224336d..36ea77fdc5 100644
c2dfb7
--- a/src/core/cgroup.h
c2dfb7
+++ b/src/core/cgroup.h
c2dfb7
@@ -33,6 +33,14 @@ typedef enum CGroupDevicePolicy {
c2dfb7
         _CGROUP_DEVICE_POLICY_INVALID = -1
c2dfb7
 } CGroupDevicePolicy;
c2dfb7
 
c2dfb7
+typedef enum FreezerAction {
c2dfb7
+        FREEZER_FREEZE,
c2dfb7
+        FREEZER_THAW,
c2dfb7
+
c2dfb7
+        _FREEZER_ACTION_MAX,
c2dfb7
+        _FREEZER_ACTION_INVALID = -1,
c2dfb7
+} FreezerAction;
c2dfb7
+
c2dfb7
 struct CGroupDeviceAllow {
c2dfb7
         LIST_FIELDS(CGroupDeviceAllow, device_allow);
c2dfb7
         char *path;
c2dfb7
@@ -235,3 +243,7 @@ CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_;
c2dfb7
 
c2dfb7
 bool unit_cgroup_delegate(Unit *u);
c2dfb7
 int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name);
c2dfb7
+int unit_cgroup_freezer_action(Unit *u, FreezerAction action);
c2dfb7
+
c2dfb7
+const char* freezer_action_to_string(FreezerAction a) _const_;
c2dfb7
+FreezerAction freezer_action_from_string(const char *s) _pure_;
c2dfb7
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
c2dfb7
index b3c011b0df..a0777f63d5 100644
c2dfb7
--- a/src/core/dbus-manager.c
c2dfb7
+++ b/src/core/dbus-manager.c
c2dfb7
@@ -683,6 +683,54 @@ static int method_unref_unit(sd_bus_message *message, void *userdata, sd_bus_err
c2dfb7
         return bus_unit_method_unref(message, u, error);
c2dfb7
 }
c2dfb7
 
c2dfb7
+static int method_freeze_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
c2dfb7
+        Manager *m = userdata;
c2dfb7
+        const char *name;
c2dfb7
+        Unit *u;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(message);
c2dfb7
+        assert(m);
c2dfb7
+
c2dfb7
+        r = sd_bus_message_read(message, "s", &name);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        r = bus_load_unit_by_name(m, message, name, &u, error);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        r = bus_unit_validate_load_state(u, error);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        return bus_unit_method_freeze(message, u, error);
c2dfb7
+}
c2dfb7
+
c2dfb7
+static int method_thaw_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
c2dfb7
+        Manager *m = userdata;
c2dfb7
+        const char *name;
c2dfb7
+        Unit *u;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(message);
c2dfb7
+        assert(m);
c2dfb7
+
c2dfb7
+        r = sd_bus_message_read(message, "s", &name);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        r = bus_load_unit_by_name(m, message, name, &u, error);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        r = bus_unit_validate_load_state(u, error);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        return bus_unit_method_thaw(message, u, error);
c2dfb7
+}
c2dfb7
+
c2dfb7
 static int reply_unit_info(sd_bus_message *reply, Unit *u) {
c2dfb7
         _cleanup_free_ char *unit_path = NULL, *job_path = NULL;
c2dfb7
         Unit *following;
c2dfb7
@@ -2500,6 +2548,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
c2dfb7
         SD_BUS_METHOD("ReloadOrRestartUnit", "ss", "o", method_reload_or_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
+        SD_BUS_METHOD("FreezeUnit", "s", NULL, method_freeze_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
+        SD_BUS_METHOD("ThawUnit", "s", NULL, method_thaw_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("RefUnit", "s", NULL, method_ref_unit, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
c2dfb7
index aa15e47754..ce81103e92 100644
c2dfb7
--- a/src/core/dbus-unit.c
c2dfb7
+++ b/src/core/dbus-unit.c
c2dfb7
@@ -42,12 +42,14 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_description, "s", Unit, unit_description);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET2(property_get_active_state, "s", Unit, unit_active_state, unit_active_state_to_string);
c2dfb7
+static BUS_DEFINE_PROPERTY_GET2(property_get_freezer_state, "s", Unit, unit_freezer_state, freezer_state_to_string);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_sub_state, "s", Unit, unit_sub_state_to_string);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET2(property_get_unit_file_state, "s", Unit, unit_get_unit_file_state, unit_file_state_to_string);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_can_reload, "b", Unit, unit_can_reload);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_can_start, "b", Unit, unit_can_start_refuse_manual);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_can_stop, "b", Unit, unit_can_stop_refuse_manual);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_can_isolate, "b", Unit, unit_can_isolate_refuse_manual);
c2dfb7
+static BUS_DEFINE_PROPERTY_GET(property_get_can_freeze, "b", Unit, unit_can_freeze);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET(property_get_need_daemon_reload, "b", Unit, unit_need_daemon_reload);
c2dfb7
 static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_strv, "as", 0);
c2dfb7
 
c2dfb7
@@ -561,6 +563,79 @@ int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error
c2dfb7
         return sd_bus_reply_method_return(message, NULL);
c2dfb7
 }
c2dfb7
 
c2dfb7
+static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userdata, sd_bus_error *error, FreezerAction action) {
c2dfb7
+        const char* perm;
c2dfb7
+        int (*method)(Unit*);
c2dfb7
+        Unit *u = userdata;
c2dfb7
+        bool reply_no_delay = false;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(message);
c2dfb7
+        assert(u);
c2dfb7
+        assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
c2dfb7
+
c2dfb7
+        if (action == FREEZER_FREEZE) {
c2dfb7
+                perm = "stop";
c2dfb7
+                method = unit_freeze;
c2dfb7
+        } else {
c2dfb7
+                perm = "start";
c2dfb7
+                method = unit_thaw;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = mac_selinux_unit_access_check(u, message, perm, error);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        r = bus_verify_manage_units_async_full(
c2dfb7
+                        u,
c2dfb7
+                        perm,
c2dfb7
+                        CAP_SYS_ADMIN,
c2dfb7
+                        N_("Authentication is required to freeze or thaw the processes of '$(unit)' unit."),
c2dfb7
+                        true,
c2dfb7
+                        message,
c2dfb7
+                        error);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+        if (r == 0)
c2dfb7
+                return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
c2dfb7
+
c2dfb7
+        r = method(u);
c2dfb7
+        if (r == -EOPNOTSUPP)
c2dfb7
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not support freezing.", u->id);
c2dfb7
+        if (r == -EBUSY)
c2dfb7
+                return sd_bus_error_setf(error, BUS_ERROR_UNIT_BUSY, "Unit has a pending job.");
c2dfb7
+        if (r == -EHOSTDOWN)
c2dfb7
+                return sd_bus_error_setf(error, BUS_ERROR_UNIT_INACTIVE, "Unit is inactive.");
c2dfb7
+        if (r == -EALREADY)
c2dfb7
+                return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Previously requested freezer operation for unit '%s' is still in progress.", u->id);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+        if (r == 0)
c2dfb7
+                reply_no_delay = true;
c2dfb7
+
c2dfb7
+        assert(!u->pending_freezer_message);
c2dfb7
+
c2dfb7
+        r = sd_bus_message_new_method_return(message, &u->pending_freezer_message);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        if (reply_no_delay) {
c2dfb7
+                r = bus_unit_send_pending_freezer_message(u);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return r;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        return 1;
c2dfb7
+}
c2dfb7
+
c2dfb7
+int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error) {
c2dfb7
+        return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_THAW);
c2dfb7
+}
c2dfb7
+
c2dfb7
+int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error) {
c2dfb7
+        return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_FREEZE);
c2dfb7
+}
c2dfb7
+
c2dfb7
 const sd_bus_vtable bus_unit_vtable[] = {
c2dfb7
         SD_BUS_VTABLE_START(0),
c2dfb7
 
c2dfb7
@@ -592,6 +667,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
c2dfb7
         SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
c2dfb7
+        SD_BUS_PROPERTY("FreezerState", "s", property_get_freezer_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
c2dfb7
         SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
c2dfb7
         SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
@@ -607,6 +683,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
c2dfb7
         SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
+        SD_BUS_PROPERTY("CanFreeze", "b", property_get_can_freeze, 0, SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("Job", "(uo)", property_get_job, offsetof(Unit, job), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
c2dfb7
         SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
         SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST),
c2dfb7
@@ -650,6 +727,8 @@ const sd_bus_vtable bus_unit_vtable[] = {
c2dfb7
         SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("Ref", NULL, NULL, bus_unit_method_ref, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
         SD_BUS_METHOD("Unref", NULL, NULL, bus_unit_method_unref, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
+        SD_BUS_METHOD("Freeze", NULL, NULL, bus_unit_method_freeze, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
+        SD_BUS_METHOD("Thaw", NULL, NULL, bus_unit_method_thaw, SD_BUS_VTABLE_UNPRIVILEGED),
c2dfb7
 
c2dfb7
         /* For dependency types we don't support anymore always return an empty array */
c2dfb7
         SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_empty_strv, 0, SD_BUS_VTABLE_HIDDEN),
c2dfb7
@@ -1209,6 +1288,23 @@ void bus_unit_send_change_signal(Unit *u) {
c2dfb7
         u->sent_dbus_new_signal = true;
c2dfb7
 }
c2dfb7
 
c2dfb7
+int bus_unit_send_pending_freezer_message(Unit *u) {
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        if (!u->pending_freezer_message)
c2dfb7
+                return 0;
c2dfb7
+
c2dfb7
+        r = sd_bus_send(NULL, u->pending_freezer_message, NULL);
c2dfb7
+        if (r < 0)
c2dfb7
+                log_warning_errno(r, "Failed to send queued message, ignoring: %m");
c2dfb7
+
c2dfb7
+        u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
c2dfb7
+
c2dfb7
+        return 0;
c2dfb7
+}
c2dfb7
+
c2dfb7
 static int send_removed_signal(sd_bus *bus, void *userdata) {
c2dfb7
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
c2dfb7
         _cleanup_free_ char *p = NULL;
c2dfb7
diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h
c2dfb7
index 68eb621836..39aa1bb53c 100644
c2dfb7
--- a/src/core/dbus-unit.h
c2dfb7
+++ b/src/core/dbus-unit.h
c2dfb7
@@ -11,6 +11,7 @@ extern const sd_bus_vtable bus_unit_vtable[];
c2dfb7
 extern const sd_bus_vtable bus_unit_cgroup_vtable[];
c2dfb7
 
c2dfb7
 void bus_unit_send_change_signal(Unit *u);
c2dfb7
+int bus_unit_send_pending_freezer_message(Unit *u);
c2dfb7
 void bus_unit_send_removed_signal(Unit *u);
c2dfb7
 
c2dfb7
 int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error);
c2dfb7
@@ -23,6 +24,8 @@ int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bu
c2dfb7
 int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error);
c2dfb7
 int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error);
c2dfb7
 int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error);
c2dfb7
+int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error);
c2dfb7
+int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error);
c2dfb7
 
c2dfb7
 int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error);
c2dfb7
 int bus_unit_validate_load_state(Unit *u, sd_bus_error *error);
c2dfb7
diff --git a/src/core/dbus.c b/src/core/dbus.c
c2dfb7
index 346a440c5d..b69c11c519 100644
c2dfb7
--- a/src/core/dbus.c
c2dfb7
+++ b/src/core/dbus.c
c2dfb7
@@ -1073,10 +1073,15 @@ static void destroy_bus(Manager *m, sd_bus **bus) {
c2dfb7
                 if (j->bus_track && sd_bus_track_get_bus(j->bus_track) == *bus)
c2dfb7
                         j->bus_track = sd_bus_track_unref(j->bus_track);
c2dfb7
 
c2dfb7
-        HASHMAP_FOREACH(u, m->units, i)
c2dfb7
+        HASHMAP_FOREACH(u, m->units, i) {
c2dfb7
                 if (u->bus_track && sd_bus_track_get_bus(u->bus_track) == *bus)
c2dfb7
                         u->bus_track = sd_bus_track_unref(u->bus_track);
c2dfb7
 
c2dfb7
+                /* Get rid of pending freezer messages on this bus */
c2dfb7
+                if (u->pending_freezer_message && sd_bus_message_get_bus(u->pending_freezer_message) == *bus)
c2dfb7
+                        u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
c2dfb7
+        }
c2dfb7
+
c2dfb7
         /* Get rid of queued message on this bus */
c2dfb7
         if (m->pending_reload_message && sd_bus_message_get_bus(m->pending_reload_message) == *bus)
c2dfb7
                 m->pending_reload_message = sd_bus_message_unref(m->pending_reload_message);
c2dfb7
diff --git a/src/core/scope.c b/src/core/scope.c
c2dfb7
index a1a5363244..5a595c65a6 100644
c2dfb7
--- a/src/core/scope.c
c2dfb7
+++ b/src/core/scope.c
c2dfb7
@@ -601,6 +601,9 @@ const UnitVTable scope_vtable = {
c2dfb7
 
c2dfb7
         .kill = scope_kill,
c2dfb7
 
c2dfb7
+        .freeze = unit_freeze_vtable_common,
c2dfb7
+        .thaw = unit_thaw_vtable_common,
c2dfb7
+
c2dfb7
         .get_timeout = scope_get_timeout,
c2dfb7
 
c2dfb7
         .serialize = scope_serialize,
c2dfb7
diff --git a/src/core/service.c b/src/core/service.c
c2dfb7
index 1d98ee37fd..89b41f6783 100644
c2dfb7
--- a/src/core/service.c
c2dfb7
+++ b/src/core/service.c
c2dfb7
@@ -4143,6 +4143,9 @@ const UnitVTable service_vtable = {
c2dfb7
 
c2dfb7
         .kill = service_kill,
c2dfb7
 
c2dfb7
+        .freeze = unit_freeze_vtable_common,
c2dfb7
+        .thaw = unit_thaw_vtable_common,
c2dfb7
+
c2dfb7
         .serialize = service_serialize,
c2dfb7
         .deserialize_item = service_deserialize_item,
c2dfb7
 
c2dfb7
diff --git a/src/core/slice.c b/src/core/slice.c
c2dfb7
index 58f18a4dad..b5eb2f5c01 100644
c2dfb7
--- a/src/core/slice.c
c2dfb7
+++ b/src/core/slice.c
c2dfb7
@@ -344,6 +344,82 @@ static void slice_enumerate_perpetual(Manager *m) {
c2dfb7
                 (void) slice_make_perpetual(m, SPECIAL_SYSTEM_SLICE, NULL);
c2dfb7
 }
c2dfb7
 
c2dfb7
+static bool slice_freezer_action_supported_by_children(Unit *s) {
c2dfb7
+        Unit *member;
c2dfb7
+        void *v;
c2dfb7
+        Iterator i;
c2dfb7
+
c2dfb7
+        assert(s);
c2dfb7
+
c2dfb7
+        HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) {
c2dfb7
+                int r;
c2dfb7
+
c2dfb7
+                if (UNIT_DEREF(member->slice) != s)
c2dfb7
+                        continue;
c2dfb7
+
c2dfb7
+                if (member->type == UNIT_SLICE) {
c2dfb7
+                        r = slice_freezer_action_supported_by_children(member);
c2dfb7
+                        if (!r)
c2dfb7
+                                return r;
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                if (!UNIT_VTABLE(member)->freeze)
c2dfb7
+                        return false;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        return true;
c2dfb7
+}
c2dfb7
+
c2dfb7
+static int slice_freezer_action(Unit *s, FreezerAction action) {
c2dfb7
+        Unit *member;
c2dfb7
+        void *v;
c2dfb7
+        Iterator i;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(s);
c2dfb7
+        assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
c2dfb7
+
c2dfb7
+        if (!slice_freezer_action_supported_by_children(s))
c2dfb7
+                return log_unit_warning(s, "Requested freezer operation is not supported by all children of the slice");
c2dfb7
+
c2dfb7
+        HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) {
c2dfb7
+                if (UNIT_DEREF(member->slice) != s)
c2dfb7
+                        continue;
c2dfb7
+
c2dfb7
+                if (action == FREEZER_FREEZE)
c2dfb7
+                        r = UNIT_VTABLE(member)->freeze(member);
c2dfb7
+                else
c2dfb7
+                        r = UNIT_VTABLE(member)->thaw(member);
c2dfb7
+
c2dfb7
+                if (r < 0)
c2dfb7
+                        return r;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = unit_cgroup_freezer_action(s, action);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        return 0;
c2dfb7
+}
c2dfb7
+
c2dfb7
+static int slice_freeze(Unit *s) {
c2dfb7
+        assert(s);
c2dfb7
+
c2dfb7
+        return slice_freezer_action(s, FREEZER_FREEZE);
c2dfb7
+}
c2dfb7
+
c2dfb7
+static int slice_thaw(Unit *s) {
c2dfb7
+        assert(s);
c2dfb7
+
c2dfb7
+        return slice_freezer_action(s, FREEZER_THAW);
c2dfb7
+}
c2dfb7
+
c2dfb7
+static bool slice_can_freeze(Unit *s) {
c2dfb7
+        assert(s);
c2dfb7
+
c2dfb7
+        return slice_freezer_action_supported_by_children(s);
c2dfb7
+}
c2dfb7
+
c2dfb7
 const UnitVTable slice_vtable = {
c2dfb7
         .object_size = sizeof(Slice),
c2dfb7
         .cgroup_context_offset = offsetof(Slice, cgroup_context),
c2dfb7
@@ -368,6 +444,10 @@ const UnitVTable slice_vtable = {
c2dfb7
 
c2dfb7
         .kill = slice_kill,
c2dfb7
 
c2dfb7
+        .freeze = slice_freeze,
c2dfb7
+        .thaw = slice_thaw,
c2dfb7
+        .can_freeze = slice_can_freeze,
c2dfb7
+
c2dfb7
         .serialize = slice_serialize,
c2dfb7
         .deserialize_item = slice_deserialize_item,
c2dfb7
 
c2dfb7
diff --git a/src/core/unit.c b/src/core/unit.c
c2dfb7
index f57260727f..29ce6c1fb7 100644
c2dfb7
--- a/src/core/unit.c
c2dfb7
+++ b/src/core/unit.c
c2dfb7
@@ -583,6 +583,7 @@ void unit_free(Unit *u) {
c2dfb7
         sd_bus_slot_unref(u->match_bus_slot);
c2dfb7
         sd_bus_track_unref(u->bus_track);
c2dfb7
         u->deserialized_refs = strv_free(u->deserialized_refs);
c2dfb7
+        u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
c2dfb7
 
c2dfb7
         unit_free_requires_mounts_for(u);
c2dfb7
 
c2dfb7
@@ -685,6 +686,38 @@ void unit_free(Unit *u) {
c2dfb7
         free(u);
c2dfb7
 }
c2dfb7
 
c2dfb7
+FreezerState unit_freezer_state(Unit *u) {
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        return u->freezer_state;
c2dfb7
+}
c2dfb7
+
c2dfb7
+int unit_freezer_state_kernel(Unit *u, FreezerState *ret) {
c2dfb7
+        char *values[1] = {};
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events",
c2dfb7
+                                   STRV_MAKE("frozen"), values);
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        r = _FREEZER_STATE_INVALID;
c2dfb7
+
c2dfb7
+        if (values[0])  {
c2dfb7
+                if (streq(values[0], "0"))
c2dfb7
+                        r = FREEZER_RUNNING;
c2dfb7
+                else if (streq(values[0], "1"))
c2dfb7
+                        r = FREEZER_FROZEN;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        free(values[0]);
c2dfb7
+        *ret = r;
c2dfb7
+
c2dfb7
+        return 0;
c2dfb7
+}
c2dfb7
+
c2dfb7
 UnitActiveState unit_active_state(Unit *u) {
c2dfb7
         assert(u);
c2dfb7
 
c2dfb7
@@ -1760,6 +1793,7 @@ int unit_start(Unit *u) {
c2dfb7
          * before it will start again. */
c2dfb7
 
c2dfb7
         unit_add_to_dbus_queue(u);
c2dfb7
+        unit_cgroup_freezer_action(u, FREEZER_THAW);
c2dfb7
 
c2dfb7
         return UNIT_VTABLE(u)->start(u);
c2dfb7
 }
c2dfb7
@@ -1812,6 +1846,7 @@ int unit_stop(Unit *u) {
c2dfb7
                 return -EBADR;
c2dfb7
 
c2dfb7
         unit_add_to_dbus_queue(u);
c2dfb7
+        unit_cgroup_freezer_action(u, FREEZER_THAW);
c2dfb7
 
c2dfb7
         return UNIT_VTABLE(u)->stop(u);
c2dfb7
 }
c2dfb7
@@ -1868,6 +1903,8 @@ int unit_reload(Unit *u) {
c2dfb7
                 return 0;
c2dfb7
         }
c2dfb7
 
c2dfb7
+        unit_cgroup_freezer_action(u, FREEZER_THAW);
c2dfb7
+
c2dfb7
         return UNIT_VTABLE(u)->reload(u);
c2dfb7
 }
c2dfb7
 
c2dfb7
@@ -3208,6 +3245,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
c2dfb7
         if (!sd_id128_is_null(u->invocation_id))
c2dfb7
                 unit_serialize_item_format(u, f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id));
c2dfb7
 
c2dfb7
+        (void) unit_serialize_item_format(u, f, "freezer-state", "%s", freezer_state_to_string(unit_freezer_state(u)));
c2dfb7
+
c2dfb7
         bus_track_serialize(u->bus_track, f, "ref");
c2dfb7
 
c2dfb7
         for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) {
c2dfb7
@@ -3574,6 +3613,16 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
c2dfb7
                                         log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m");
c2dfb7
                         }
c2dfb7
 
c2dfb7
+                        continue;
c2dfb7
+                } else if (streq(l, "freezer-state")) {
c2dfb7
+                        FreezerState s;
c2dfb7
+
c2dfb7
+                        s = freezer_state_from_string(v);
c2dfb7
+                        if (s < 0)
c2dfb7
+                                log_unit_debug(u, "Failed to deserialize freezer-state '%s', ignoring.", v);
c2dfb7
+                        else
c2dfb7
+                                u->freezer_state = s;
c2dfb7
+
c2dfb7
                         continue;
c2dfb7
                 }
c2dfb7
 
c2dfb7
@@ -5507,6 +5556,80 @@ void unit_log_skip(Unit *u, const char *result) {
c2dfb7
                    "UNIT_RESULT=%s", result);
c2dfb7
 }
c2dfb7
 
c2dfb7
+bool unit_can_freeze(Unit *u) {
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        if (UNIT_VTABLE(u)->can_freeze)
c2dfb7
+                return UNIT_VTABLE(u)->can_freeze(u);
c2dfb7
+
c2dfb7
+        return UNIT_VTABLE(u)->freeze;
c2dfb7
+}
c2dfb7
+
c2dfb7
+void unit_frozen(Unit *u) {
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        u->freezer_state = FREEZER_FROZEN;
c2dfb7
+
c2dfb7
+        bus_unit_send_pending_freezer_message(u);
c2dfb7
+}
c2dfb7
+
c2dfb7
+void unit_thawed(Unit *u) {
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        u->freezer_state = FREEZER_RUNNING;
c2dfb7
+
c2dfb7
+        bus_unit_send_pending_freezer_message(u);
c2dfb7
+}
c2dfb7
+
c2dfb7
+static int unit_freezer_action(Unit *u, FreezerAction action) {
c2dfb7
+        UnitActiveState s;
c2dfb7
+        int (*method)(Unit*);
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(u);
c2dfb7
+        assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
c2dfb7
+
c2dfb7
+        method = action == FREEZER_FREEZE ? UNIT_VTABLE(u)->freeze : UNIT_VTABLE(u)->thaw;
c2dfb7
+        if (!method || !cg_freezer_supported())
c2dfb7
+                return -EOPNOTSUPP;
c2dfb7
+
c2dfb7
+        if (u->job)
c2dfb7
+                return -EBUSY;
c2dfb7
+
c2dfb7
+        if (u->load_state != UNIT_LOADED)
c2dfb7
+                return -EHOSTDOWN;
c2dfb7
+
c2dfb7
+        s = unit_active_state(u);
c2dfb7
+        if (s != UNIT_ACTIVE)
c2dfb7
+                return -EHOSTDOWN;
c2dfb7
+
c2dfb7
+        if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING))
c2dfb7
+                return -EALREADY;
c2dfb7
+
c2dfb7
+        r = method(u);
c2dfb7
+        if (r <= 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        return 1;
c2dfb7
+}
c2dfb7
+
c2dfb7
+int unit_freeze(Unit *u) {
c2dfb7
+        return unit_freezer_action(u, FREEZER_FREEZE);
c2dfb7
+}
c2dfb7
+
c2dfb7
+int unit_thaw(Unit *u) {
c2dfb7
+        return unit_freezer_action(u, FREEZER_THAW);
c2dfb7
+}
c2dfb7
+
c2dfb7
+/* Wrappers around low-level cgroup freezer operations common for service and scope units */
c2dfb7
+int unit_freeze_vtable_common(Unit *u) {
c2dfb7
+        return unit_cgroup_freezer_action(u, FREEZER_FREEZE);
c2dfb7
+}
c2dfb7
+
c2dfb7
+int unit_thaw_vtable_common(Unit *u) {
c2dfb7
+        return unit_cgroup_freezer_action(u, FREEZER_THAW);
c2dfb7
+}
c2dfb7
+
c2dfb7
 static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
c2dfb7
         [COLLECT_INACTIVE] = "inactive",
c2dfb7
         [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
c2dfb7
diff --git a/src/core/unit.h b/src/core/unit.h
c2dfb7
index b40ff9b961..6e37fd6f5a 100644
c2dfb7
--- a/src/core/unit.h
c2dfb7
+++ b/src/core/unit.h
c2dfb7
@@ -118,6 +118,9 @@ typedef struct Unit {
c2dfb7
         UnitLoadState load_state;
c2dfb7
         Unit *merged_into;
c2dfb7
 
c2dfb7
+        FreezerState freezer_state;
c2dfb7
+        sd_bus_message *pending_freezer_message;
c2dfb7
+
c2dfb7
         char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
c2dfb7
         char *instance;
c2dfb7
 
c2dfb7
@@ -456,6 +459,11 @@ typedef struct UnitVTable {
c2dfb7
 
c2dfb7
         int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error);
c2dfb7
 
c2dfb7
+        /* Freeze the unit */
c2dfb7
+        int (*freeze)(Unit *u);
c2dfb7
+        int (*thaw)(Unit *u);
c2dfb7
+        bool (*can_freeze)(Unit *u);
c2dfb7
+
c2dfb7
         bool (*can_reload)(Unit *u);
c2dfb7
 
c2dfb7
         /* Write all data that cannot be restored from other sources
c2dfb7
@@ -641,6 +649,8 @@ const char *unit_description(Unit *u) _pure_;
c2dfb7
 bool unit_has_name(Unit *u, const char *name);
c2dfb7
 
c2dfb7
 UnitActiveState unit_active_state(Unit *u);
c2dfb7
+FreezerState unit_freezer_state(Unit *u);
c2dfb7
+int unit_freezer_state_kernel(Unit *u, FreezerState *ret);
c2dfb7
 
c2dfb7
 const char* unit_sub_state_to_string(Unit *u);
c2dfb7
 
c2dfb7
@@ -814,6 +824,16 @@ void unit_log_failure(Unit *u, const char *result);
c2dfb7
  * after some execution, rather than succeeded or failed. */
c2dfb7
 void unit_log_skip(Unit *u, const char *result);
c2dfb7
 
c2dfb7
+bool unit_can_freeze(Unit *u);
c2dfb7
+int unit_freeze(Unit *u);
c2dfb7
+void unit_frozen(Unit *u);
c2dfb7
+
c2dfb7
+int unit_thaw(Unit *u);
c2dfb7
+void unit_thawed(Unit *u);
c2dfb7
+
c2dfb7
+int unit_freeze_vtable_common(Unit *u);
c2dfb7
+int unit_thaw_vtable_common(Unit *u);
c2dfb7
+
c2dfb7
 /* Macros which append UNIT= or USER_UNIT= to the message */
c2dfb7
 
c2dfb7
 #define log_unit_full(unit, level, error, ...)                          \
c2dfb7
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
c2dfb7
index 3945c7f6ac..77da78d4d4 100644
c2dfb7
--- a/src/libsystemd/sd-bus/bus-common-errors.h
c2dfb7
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
c2dfb7
@@ -30,6 +30,8 @@
c2dfb7
 #define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser"
c2dfb7
 #define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced"
c2dfb7
 #define BUS_ERROR_DISK_FULL "org.freedesktop.systemd1.DiskFull"
c2dfb7
+#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy"
c2dfb7
+#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive"
c2dfb7
 
c2dfb7
 #define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
c2dfb7
 #define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
c2dfb7
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
c2dfb7
index e0db97e339..e963f19b0a 100644
c2dfb7
--- a/src/systemctl/systemctl.c
c2dfb7
+++ b/src/systemctl/systemctl.c
c2dfb7
@@ -27,6 +27,7 @@
c2dfb7
 #include "bus-message.h"
c2dfb7
 #include "bus-unit-util.h"
c2dfb7
 #include "bus-util.h"
c2dfb7
+#include "bus-wait-for-units.h"
c2dfb7
 #include "cgroup-show.h"
c2dfb7
 #include "cgroup-util.h"
c2dfb7
 #include "copy.h"
c2dfb7
@@ -3728,6 +3729,98 @@ static int kill_unit(int argc, char *argv[], void *userdata) {
c2dfb7
 
c2dfb7
         return r;
c2dfb7
 }
c2dfb7
+static int freeze_or_thaw_unit(int argc, char *argv[], void *userdata) {
c2dfb7
+        _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL;
c2dfb7
+        _cleanup_strv_free_ char **names = NULL;
c2dfb7
+        int r, ret = EXIT_SUCCESS;
c2dfb7
+        char **name;
c2dfb7
+        const char *method;
c2dfb7
+        sd_bus *bus;
c2dfb7
+
c2dfb7
+        r = acquire_bus(BUS_FULL, &bus;;
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        polkit_agent_open_maybe();
c2dfb7
+
c2dfb7
+        r = expand_names(bus, strv_skip(argv, 1), NULL, &names);
c2dfb7
+        if (r < 0)
c2dfb7
+                return log_error_errno(r, "Failed to expand names: %m");
c2dfb7
+
c2dfb7
+        if (!arg_no_block) {
c2dfb7
+                r = bus_wait_for_units_new(bus, &w);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return log_error_errno(r, "Failed to allocate unit waiter: %m");
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        if (streq(argv[0], "freeze"))
c2dfb7
+                method = "FreezeUnit";
c2dfb7
+        else if (streq(argv[0], "thaw"))
c2dfb7
+                method = "ThawUnit";
c2dfb7
+        else
c2dfb7
+                assert_not_reached("Unhandled method");
c2dfb7
+
c2dfb7
+        STRV_FOREACH(name, names) {
c2dfb7
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
c2dfb7
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
c2dfb7
+
c2dfb7
+                if (w) {
c2dfb7
+                        /* If we shall wait for the cleaning to complete, let's add a ref on the unit first */
c2dfb7
+                        r = sd_bus_call_method(
c2dfb7
+                                        bus,
c2dfb7
+                                        "org.freedesktop.systemd1",
c2dfb7
+                                        "/org/freedesktop/systemd1",
c2dfb7
+                                        "org.freedesktop.systemd1.Manager",
c2dfb7
+                                        "RefUnit",
c2dfb7
+                                        &error,
c2dfb7
+                                        NULL,
c2dfb7
+                                        "s", *name);
c2dfb7
+                        if (r < 0) {
c2dfb7
+                                log_error_errno(r, "Failed to add reference to unit %s: %s", *name, bus_error_message(&error, r));
c2dfb7
+                                if (ret == EXIT_SUCCESS)
c2dfb7
+                                        ret = r;
c2dfb7
+                                continue;
c2dfb7
+                        }
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                r = sd_bus_message_new_method_call(
c2dfb7
+                                bus,
c2dfb7
+                                &m,
c2dfb7
+                                "org.freedesktop.systemd1",
c2dfb7
+                                "/org/freedesktop/systemd1",
c2dfb7
+                                "org.freedesktop.systemd1.Manager",
c2dfb7
+                                method);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return bus_log_create_error(r);
c2dfb7
+
c2dfb7
+                r = sd_bus_message_append(m, "s", *name);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return bus_log_create_error(r);
c2dfb7
+
c2dfb7
+                r = sd_bus_call(bus, m, 0, &error, NULL);
c2dfb7
+                if (r < 0) {
c2dfb7
+                        log_error_errno(r, "Failed to %s unit %s: %s", argv[0], *name, bus_error_message(&error, r));
c2dfb7
+                        if (ret == EXIT_SUCCESS) {
c2dfb7
+                                ret = r;
c2dfb7
+                                continue;
c2dfb7
+                        }
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                if (w) {
c2dfb7
+                        r = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_REFFED|BUS_WAIT_FOR_MAINTENANCE_END, NULL, NULL);
c2dfb7
+                        if (r < 0)
c2dfb7
+                                return log_error_errno(r, "Failed to watch unit %s: %m", *name);
c2dfb7
+                }
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = bus_wait_for_units_run(w);
c2dfb7
+        if (r < 0)
c2dfb7
+                return log_error_errno(r, "Failed to wait for units: %m");
c2dfb7
+        if (r == BUS_WAIT_FAILURE)
c2dfb7
+                ret = EXIT_FAILURE;
c2dfb7
+
c2dfb7
+        return ret;
c2dfb7
+}
c2dfb7
 
c2dfb7
 typedef struct ExecStatusInfo {
c2dfb7
         char *name;
c2dfb7
@@ -3832,6 +3925,7 @@ typedef struct UnitStatusInfo {
c2dfb7
         const char *id;
c2dfb7
         const char *load_state;
c2dfb7
         const char *active_state;
c2dfb7
+        const char *freezer_state;
c2dfb7
         const char *sub_state;
c2dfb7
         const char *unit_file_state;
c2dfb7
         const char *unit_file_preset;
c2dfb7
@@ -3949,7 +4043,7 @@ static void print_status_info(
c2dfb7
                 bool *ellipsized) {
c2dfb7
 
c2dfb7
         char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX];
c2dfb7
-        const char *s1, *s2, *active_on, *active_off, *on, *off, *ss;
c2dfb7
+        const char *s1, *s2, *active_on, *active_off, *on, *off, *ss, *fs;
c2dfb7
         _cleanup_free_ char *formatted_path = NULL;
c2dfb7
         ExecStatusInfo *p;
c2dfb7
         usec_t timestamp;
c2dfb7
@@ -4056,6 +4150,10 @@ static void print_status_info(
c2dfb7
                 printf("   Active: %s%s%s",
c2dfb7
                        active_on, strna(i->active_state), active_off);
c2dfb7
 
c2dfb7
+        fs = !isempty(i->freezer_state) && !streq(i->freezer_state, "running") ? i->freezer_state : NULL;
c2dfb7
+        if (fs)
c2dfb7
+                printf(" %s(%s)%s", ansi_highlight_yellow(), fs, active_off);
c2dfb7
+
c2dfb7
         if (!isempty(i->result) && !streq(i->result, "success"))
c2dfb7
                 printf(" (Result: %s)", i->result);
c2dfb7
 
c2dfb7
@@ -4985,6 +5083,7 @@ static int show_one(
c2dfb7
                 { "Id",                             "s",              NULL,           offsetof(UnitStatusInfo, id)                                },
c2dfb7
                 { "LoadState",                      "s",              NULL,           offsetof(UnitStatusInfo, load_state)                        },
c2dfb7
                 { "ActiveState",                    "s",              NULL,           offsetof(UnitStatusInfo, active_state)                      },
c2dfb7
+                { "FreezerState",                   "s",              NULL,           offsetof(UnitStatusInfo, freezer_state)                     },
c2dfb7
                 { "SubState",                       "s",              NULL,           offsetof(UnitStatusInfo, sub_state)                         },
c2dfb7
                 { "UnitFileState",                  "s",              NULL,           offsetof(UnitStatusInfo, unit_file_state)                   },
c2dfb7
                 { "UnitFilePreset",                 "s",              NULL,           offsetof(UnitStatusInfo, unit_file_preset)                  },
c2dfb7
@@ -7139,6 +7238,8 @@ static void systemctl_help(void) {
c2dfb7
                "                                      if supported, otherwise restart\n"
c2dfb7
                "  isolate UNIT                        Start one unit and stop all others\n"
c2dfb7
                "  kill UNIT...                        Send signal to processes of a unit\n"
c2dfb7
+               "  freeze PATTERN...                   Freeze execution of unit processes\n"
c2dfb7
+               "  thaw PATTERN...                     Resume execution of a frozen unit\n"
c2dfb7
                "  is-active PATTERN...                Check whether units are active\n"
c2dfb7
                "  is-failed PATTERN...                Check whether units are failed\n"
c2dfb7
                "  status [PATTERN...|PID...]          Show runtime status of one or more units\n"
c2dfb7
@@ -8280,6 +8381,8 @@ static int systemctl_main(int argc, char *argv[]) {
c2dfb7
                 { "condrestart",           2,        VERB_ANY, VERB_ONLINE_ONLY, start_unit           }, /* For compatibility with RH */
c2dfb7
                 { "isolate",               2,        2,        VERB_ONLINE_ONLY, start_unit           },
c2dfb7
                 { "kill",                  2,        VERB_ANY, VERB_ONLINE_ONLY, kill_unit            },
c2dfb7
+                { "freeze",                2,        VERB_ANY, VERB_ONLINE_ONLY, freeze_or_thaw_unit  },
c2dfb7
+                { "thaw",                  2,        VERB_ANY, VERB_ONLINE_ONLY, freeze_or_thaw_unit  },
c2dfb7
                 { "is-active",             2,        VERB_ANY, VERB_ONLINE_ONLY, check_unit_active    },
c2dfb7
                 { "check",                 2,        VERB_ANY, VERB_ONLINE_ONLY, check_unit_active    }, /* deprecated alias of is-active */
c2dfb7
                 { "is-failed",             2,        VERB_ANY, VERB_ONLINE_ONLY, check_unit_failed    },