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