core: introduce support for cgroup freezer
With cgroup v2 the cgroup freezer is implemented as a cgroup attribute called cgroup.freeze. cgroup can be frozen by writing "1" to the file and kernel will send us a notification through "cgroup.events" after the operation is finished and processes in the cgroup entered quiescent state, i.e. they are not scheduled to run. Writing "0" to the attribute file does the inverse and process execution is resumed. This commit exposes above low-level functionality through systemd's DBus API. Each unit type must provide specialized implementation for these methods, otherwise, we return an error. So far only service, scope, and slice unit types provide the support. It is possible to check if a given unit has the support using CanFreeze() DBus property. Note that DBus API has a synchronous behavior and we dispatch the reply to freeze/thaw requests only after the kernel has notified us that requested operation was completed.
This commit is contained in:
+95
-1
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+6
-1
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4449,6 +4449,9 @@ const UnitVTable service_vtable = {
|
||||
.clean = service_clean,
|
||||
.can_clean = service_can_clean,
|
||||
|
||||
.freeze = unit_freeze_vtable_common,
|
||||
.thaw = unit_thaw_vtable_common,
|
||||
|
||||
.serialize = service_serialize,
|
||||
.deserialize_item = service_deserialize_item,
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
+123
@@ -628,6 +628,7 @@ void unit_free(Unit *u) {
|
||||
sd_bus_slot_unref(u->match_bus_slot);
|
||||
sd_bus_track_unref(u->bus_track);
|
||||
u->deserialized_refs = strv_free(u->deserialized_refs);
|
||||
u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
|
||||
|
||||
unit_free_requires_mounts_for(u);
|
||||
|
||||
@@ -737,6 +738,38 @@ void unit_free(Unit *u) {
|
||||
free(u);
|
||||
}
|
||||
|
||||
FreezerState unit_freezer_state(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
return u->freezer_state;
|
||||
}
|
||||
|
||||
int unit_freezer_state_kernel(Unit *u, FreezerState *ret) {
|
||||
char *values[1] = {};
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
|
||||
r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events",
|
||||
STRV_MAKE("frozen"), values);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = _FREEZER_STATE_INVALID;
|
||||
|
||||
if (values[0]) {
|
||||
if (streq(values[0], "0"))
|
||||
r = FREEZER_RUNNING;
|
||||
else if (streq(values[0], "1"))
|
||||
r = FREEZER_FROZEN;
|
||||
}
|
||||
|
||||
free(values[0]);
|
||||
*ret = r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
UnitActiveState unit_active_state(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
@@ -1846,6 +1879,7 @@ int unit_start(Unit *u) {
|
||||
* waits for a holdoff timer to elapse before it will start again. */
|
||||
|
||||
unit_add_to_dbus_queue(u);
|
||||
unit_cgroup_freezer_action(u, FREEZER_THAW);
|
||||
|
||||
return UNIT_VTABLE(u)->start(u);
|
||||
}
|
||||
@@ -1898,6 +1932,7 @@ int unit_stop(Unit *u) {
|
||||
return -EBADR;
|
||||
|
||||
unit_add_to_dbus_queue(u);
|
||||
unit_cgroup_freezer_action(u, FREEZER_THAW);
|
||||
|
||||
return UNIT_VTABLE(u)->stop(u);
|
||||
}
|
||||
@@ -1954,6 +1989,8 @@ int unit_reload(Unit *u) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
unit_cgroup_freezer_action(u, FREEZER_THAW);
|
||||
|
||||
return UNIT_VTABLE(u)->reload(u);
|
||||
}
|
||||
|
||||
@@ -3497,6 +3534,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
|
||||
if (!sd_id128_is_null(u->invocation_id))
|
||||
(void) serialize_item_format(f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id));
|
||||
|
||||
(void) serialize_item_format(f, "freezer-state", "%s", freezer_state_to_string(unit_freezer_state(u)));
|
||||
|
||||
bus_track_serialize(u->bus_track, f, "ref");
|
||||
|
||||
for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) {
|
||||
@@ -3805,6 +3844,16 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
|
||||
log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m");
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (streq(l, "freezer-state")) {
|
||||
FreezerState s;
|
||||
|
||||
s = freezer_state_from_string(v);
|
||||
if (s < 0)
|
||||
log_unit_debug(u, "Failed to deserialize freezer-state '%s', ignoring.", v);
|
||||
else
|
||||
u->freezer_state = s;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -6076,6 +6125,80 @@ int unit_can_clean(Unit *u, ExecCleanMask *ret) {
|
||||
return UNIT_VTABLE(u)->can_clean(u, ret);
|
||||
}
|
||||
|
||||
bool unit_can_freeze(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
if (UNIT_VTABLE(u)->can_freeze)
|
||||
return UNIT_VTABLE(u)->can_freeze(u);
|
||||
|
||||
return UNIT_VTABLE(u)->freeze;
|
||||
}
|
||||
|
||||
void unit_frozen(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
u->freezer_state = FREEZER_FROZEN;
|
||||
|
||||
bus_unit_send_pending_freezer_message(u);
|
||||
}
|
||||
|
||||
void unit_thawed(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
u->freezer_state = FREEZER_RUNNING;
|
||||
|
||||
bus_unit_send_pending_freezer_message(u);
|
||||
}
|
||||
|
||||
static int unit_freezer_action(Unit *u, FreezerAction action) {
|
||||
UnitActiveState s;
|
||||
int (*method)(Unit*);
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
|
||||
|
||||
method = action == FREEZER_FREEZE ? UNIT_VTABLE(u)->freeze : UNIT_VTABLE(u)->thaw;
|
||||
if (!method || !cg_freezer_supported())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (u->job)
|
||||
return -EBUSY;
|
||||
|
||||
if (u->load_state != UNIT_LOADED)
|
||||
return -EHOSTDOWN;
|
||||
|
||||
s = unit_active_state(u);
|
||||
if (s != UNIT_ACTIVE)
|
||||
return -EHOSTDOWN;
|
||||
|
||||
if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING))
|
||||
return -EALREADY;
|
||||
|
||||
r = method(u);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int unit_freeze(Unit *u) {
|
||||
return unit_freezer_action(u, FREEZER_FREEZE);
|
||||
}
|
||||
|
||||
int unit_thaw(Unit *u) {
|
||||
return unit_freezer_action(u, FREEZER_THAW);
|
||||
}
|
||||
|
||||
/* Wrappers around low-level cgroup freezer operations common for service and scope units */
|
||||
int unit_freeze_vtable_common(Unit *u) {
|
||||
return unit_cgroup_freezer_action(u, FREEZER_FREEZE);
|
||||
}
|
||||
|
||||
int unit_thaw_vtable_common(Unit *u) {
|
||||
return unit_cgroup_freezer_action(u, FREEZER_THAW);
|
||||
}
|
||||
|
||||
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
|
||||
[COLLECT_INACTIVE] = "inactive",
|
||||
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
|
||||
|
||||
@@ -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, ...) \
|
||||
|
||||
Reference in New Issue
Block a user