Compare commits

..

8 Commits

Author SHA1 Message Date
Lennart Poettering 6eb35fd695
Merge pull request #15547 from kkdwivedi/notify-barrier
Introduce sd_notify_barrier
2020-05-01 08:48:42 +02:00
Zbigniew Jędrzejewski-Szmek b76ef59756
Merge pull request #13512 from msekletar/freezer
core: introduce support for cgroup freezer
2020-05-01 07:52:29 +02:00
Kumar Kartikeya Dwivedi 5ec7a9947e
man: sd_notify() race is gone with sd_notify_barrier()
Add note for change of behaviour in systemd-notify, where parent pid trick
is only used when --no-block is passed, and with enough privileges ofcourse.

Also, fix a small error in systemd(1).
2020-05-01 03:22:59 +05:30
Kumar Kartikeya Dwivedi 4f07ddfa9b
Introduce sd_notify_barrier
This adds the sd_notify_barrier function, to allow users to synchronize against
the reception of sd_notify(3) status messages. It acts as a synchronization
point, and a successful return gurantees that all previous messages have been
consumed by the manager. This can be used to eliminate race conditions where
the sending process exits too early for systemd to associate its PID to a
cgroup and attribute the status message to a unit correctly.

systemd-notify now uses this function for proper notification delivery and be
useful for NotifyAccess=all units again in user mode, or in cases where it
doesn't have a control process as parent.

Fixes: #2739
2020-05-01 03:22:47 +05:30
Michal Sekletár d446ae89c0 test: add test for cgroup v2 freezer support 2020-04-30 19:02:55 +02:00
Michal Sekletár d9e45bc3ab 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.
2020-04-30 19:02:51 +02:00
Michal Sekletár 25a1f04c68 basic/cgroup-util: introduce cg_get_keyed_attribute_full()
Callers of cg_get_keyed_attribute_full() can now specify via the flag whether the
missing keyes in cgroup attribute file are OK or not. Also the wrappers for both
strict and graceful version are provided.
2020-04-29 18:41:19 +02:00
Michal Sekletár 08deac6e3e selinux: do preprocessor check only in selinux-access.c
This has the advantage that mac_selinux_access_check() can be used as a
function in all contexts. For example, parameters passed to it won't be
reported as unused if the "function" call is replaced with 0 on SELinux
disabled builds.
2020-04-29 13:56:40 +02:00
36 changed files with 1088 additions and 35 deletions

4
NEWS
View File

@ -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

View File

@ -718,7 +718,7 @@ manpages = [
['sd_machine_get_class', '3', ['sd_machine_get_ifindices'], ''],
['sd_notify',
'3',
['sd_notifyf', 'sd_pid_notify', 'sd_pid_notify_with_fds', 'sd_pid_notifyf'],
['sd_notifyf', 'sd_pid_notify', 'sd_pid_notify_with_fds', 'sd_pid_notifyf', 'sd_notify_barrier'],
''],
['sd_path_lookup', '3', ['sd_path_lookup_strv'], ''],
['sd_pid_get_owner_uid',

View File

@ -22,6 +22,7 @@
<refname>sd_pid_notify</refname>
<refname>sd_pid_notifyf</refname>
<refname>sd_pid_notify_with_fds</refname>
<refname>sd_notify_barrier</refname>
<refpurpose>Notify service manager about start-up completion and other service status changes</refpurpose>
</refnamediv>
@ -65,6 +66,12 @@
<paramdef>const int *<parameter>fds</parameter></paramdef>
<paramdef>unsigned <parameter>n_fds</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_notify_barrier</function></funcdef>
<paramdef>int <parameter>unset_environment</parameter></paramdef>
<paramdef>uint64_t <parameter>timeout</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
@ -261,6 +268,17 @@
as prematurely discarding file descriptors from the store.</para></listitem>
</varlistentry>
<varlistentry>
<term>BARRIER=1</term>
<listitem><para>Tells the service manager that the client is explicitly requesting synchronization by means of
closing the file descriptor sent with this command. The service manager gurantees that the processing of a <varname>
BARRIER=1</varname> command will only happen after all previous notification messages sent before this command
have been processed. Hence, this command accompanied with a single file descriptor can be used to synchronize
against reception of all previous status messages. Note that this command cannot be mixed with other notifications,
and has to be sent in a separate message to the service manager, otherwise all assignments will be ignored. Note that
sending 0 or more than 1 file descriptor with this command is a violation of the protocol.</para></listitem>
</varlistentry>
</variablelist>
<para>It is recommended to prefix variable names that are not
@ -282,6 +300,13 @@
attribute the message to the unit, and thus will ignore it, even if
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
<para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as a synchronization point
and ensures all notifications sent before this call have been picked up by the service manager when it returns
successfully. Use of <function>sd_notify_barrier()</function> is needed for clients which are not invoked by the
service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
unit.</para>
<para><function>sd_notifyf()</function> is similar to
<function>sd_notify()</function> but takes a
<function>printf()</function>-like format string plus
@ -312,6 +337,14 @@
to the service manager on messages that do not expect them (i.e.
without <literal>FDSTORE=1</literal>) they are immediately closed
on reception.</para>
<para><function>sd_notify_barrier()</function> allows the caller to
synchronize against reception of previously sent notification messages
and uses the <literal>BARRIER=1</literal> command. It takes a relative
<varname>timeout</varname> value in microseconds which is passed to
<citerefentry><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum>
</citerefentry>. A value of UINT64_MAX is interpreted as infinite timeout.
</para>
</refsect1>
<refsect1>
@ -402,6 +435,22 @@
<programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &amp;fd, 1);</programlisting>
</example>
<example>
<title>Eliminating race conditions</title>
<para>When the client sending the notifications is not spawned
by the service manager, it may exit too quickly and the service
manager may fail to attribute them correctly to the unit. To
prevent such races, use <function>sd_notify_barrier()</function>
to synchronize against reception of all notifications sent before
this call is made.</para>
<programlisting>sd_notify(0, "READY=1");
/* set timeout to 5 seconds */
sd_notify_barrier(0, 5 * 1000000);
</programlisting>
</example>
</refsect1>
<refsect1>

View File

@ -303,6 +303,30 @@ Sun 2017-02-26 20:57:49 EST 2h 3min left Sun 2017-02-26 11:56:36 EST 6h ago
generally redundant and reproducible on the next invocation of the unit).</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>freeze <replaceable>PATTERN</replaceable></command></term>
<listitem>
<para>Freeze one or more units specified on the
command line using cgroup freezer</para>
<para>Freezing the unit will cause all processes contained within the cgroup corresponding to the unit
to be suspended. Being suspended means that unit's processes won't be scheduled to run on CPU until thawed.
Note that this command is supported only on systems that use unified cgroup hierarchy. Unit is automatically
thawed just before we execute a job against the unit, e.g. before the unit is stopped.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>thaw <replaceable>PATTERN</replaceable></command></term>
<listitem>
<para>Thaw (unfreeze) one or more units specified on the
command line.</para>
<para>This is the inverse operation to the <command>freeze</command> command and resumes the execution of
processes in the unit's cgroup.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>is-active <replaceable>PATTERN</replaceable></command></term>

View File

@ -54,15 +54,19 @@
off the process, i.e. on all processes that match <varname>NotifyAccess=</varname><option>main</option> or
<varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit sends an
<function>sd_notify()</function> message and immediately exits, the service manager might not be able to properly
attribute the message to the unit, and thus will ignore it, even if
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
attribute the message to the unit, and thus will ignore it, even if <varname>NotifyAccess=</varname><option>all
</option> is set for it. When <option>--no-block</option> is used, all synchronization for reception of notifications
is disabled, and hence the aforementioned race may occur if the invoking process is not the service manager or spawned
by the service manager.</para>
<para>Hence, <command>systemd-notify</command> will first attempt to invoke <function>sd_notify()</function>
pretending to have the PID of the invoking process. This will only succeed when invoked with sufficient privileges.
On failure, it will then fall back to invoking it under its own PID. This behaviour is useful in order that when
the tool is invoked from a shell script the shell process — and not the <command>systemd-notify</command> process
— appears as sender of the message, which in turn is helpful if the shell process is the main process of a service,
due to the limitations of <varname>NotifyAccess=</varname><option>all</option>. Use the <option>--pid=</option>
switch to tweak this behaviour.</para>
<para><command>systemd-notify</command> will first attempt to invoke <function>sd_notify()</function> pretending to
have the PID of the invoking process. This will only succeed when invoked with sufficient privileges. On failure,
it will then fall back to invoking it under its own PID. This behaviour is useful in order that when the tool is
invoked from a shell script the shell process — and not the <command>systemd-notify</command> process — appears as
sender of the message, which in turn is helpful if the shell process is the main process of a service, due to the
limitations of <varname>NotifyAccess=</varname><option>all</option> described above.</para>
</refsect1>
<refsect1>
@ -129,6 +133,17 @@
with systemd. </para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-block</option></term>
<listitem><para>Do not synchronously wait for the requested operation to finish.
Use of this option is only recommended when <command>systemd-notify</command>
is spawned by the service manager, or when the invoking process is directly spawned
by the service manager and has enough privileges to allow <command>systemd-notify
</command> to send the notification on its behalf. Sending notifications with
this option set is prone to race conditions in all other cases.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>

View File

@ -959,7 +959,14 @@
<option>exec</option>. Conversely, if an auxiliary process of the unit sends an
<function>sd_notify()</function> message and immediately exits, the service manager might not be able to
properly attribute the message to the unit, and thus will ignore it, even if
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para></listitem>
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
<para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as a synchronization point
and ensures all notifications sent before this call have been picked up by the service manager when it returns
successfully. Use of <function>sd_notify_barrier()</function> is needed for clients which are not invoked by the
service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
unit.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -257,7 +257,7 @@
execution compared to the target unit's state and is marked successful and
complete when both satisfy. However, this job also pulls in other
dependencies due to the defined relationships and thus leads to, in our
our example, start jobs for any of those inactive units getting queued as
example, start jobs for any of those inactive units getting queued as
well.</para>
<para>systemd contains native implementations of various tasks

View File

@ -149,6 +149,17 @@ bool cg_ns_supported(void) {
return enabled;
}
bool cg_freezer_supported(void) {
static thread_local int supported = -1;
if (supported >= 0)
return supported;
supported = cg_all_unified() > 0 && access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) == 0;
return supported;
}
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
_cleanup_free_ char *fs = NULL;
int r;
@ -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;
}

View File

@ -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);

View File

@ -108,6 +108,15 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
static const char* const freezer_state_table[_FREEZER_STATE_MAX] = {
[FREEZER_RUNNING] = "running",
[FREEZER_FREEZING] = "freezing",
[FREEZER_FROZEN] = "frozen",
[FREEZER_THAWING] = "thawing",
};
DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState);
static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
[AUTOMOUNT_DEAD] = "dead",
[AUTOMOUNT_WAITING] = "waiting",

View File

@ -48,6 +48,15 @@ typedef enum UnitActiveState {
_UNIT_ACTIVE_STATE_INVALID = -1
} UnitActiveState;
typedef enum FreezerState {
FREEZER_RUNNING,
FREEZER_FREEZING,
FREEZER_FROZEN,
FREEZER_THAWING,
_FREEZER_STATE_MAX,
_FREEZER_STATE_INVALID = -1
} FreezerState;
typedef enum AutomountState {
AUTOMOUNT_DEAD,
AUTOMOUNT_WAITING,
@ -253,6 +262,9 @@ UnitLoadState unit_load_state_from_string(const char *s) _pure_;
const char *unit_active_state_to_string(UnitActiveState i) _const_;
UnitActiveState unit_active_state_from_string(const char *s) _pure_;
const char *freezer_state_to_string(FreezerState i) _const_;
FreezerState freezer_state_from_string(const char *s) _pure_;
const char* automount_state_to_string(AutomountState i) _const_;
AutomountState automount_state_from_string(const char *s) _pure_;

View File

@ -16,7 +16,9 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "io-util.h"
#include "limits-util.h"
#include "nulstr-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
@ -2661,6 +2663,16 @@ void unit_add_to_cgroup_empty_queue(Unit *u) {
log_debug_errno(r, "Failed to enable cgroup empty event source: %m");
}
static void unit_remove_from_cgroup_empty_queue(Unit *u) {
assert(u);
if (!u->in_cgroup_empty_queue)
return;
LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u);
u->in_cgroup_empty_queue = false;
}
int unit_check_oom(Unit *u) {
_cleanup_free_ char *oom_kill = NULL;
bool increased;
@ -2761,6 +2773,41 @@ static void unit_add_to_cgroup_oom_queue(Unit *u) {
log_error_errno(r, "Failed to enable cgroup oom event source: %m");
}
static int unit_check_cgroup_events(Unit *u) {
char *values[2] = {};
int r;
assert(u);
r = cg_get_keyed_attribute_graceful(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events",
STRV_MAKE("populated", "frozen"), values);
if (r < 0)
return r;
/* The cgroup.events notifications can be merged together so act as we saw the given state for the
* first time. The functions we call to handle given state are idempotent, which makes them
* effectively remember the previous state. */
if (values[0]) {
if (streq(values[0], "1"))
unit_remove_from_cgroup_empty_queue(u);
else
unit_add_to_cgroup_empty_queue(u);
}
/* Disregard freezer state changes due to operations not initiated by us */
if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) {
if (streq(values[1], "0"))
unit_thawed(u);
else
unit_frozen(u);
}
free(values[0]);
free(values[1]);
return 0;
}
static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Manager *m = userdata;
@ -2797,7 +2844,7 @@ static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents,
u = hashmap_get(m->cgroup_control_inotify_wd_unit, INT_TO_PTR(e->wd));
if (u)
unit_add_to_cgroup_empty_queue(u);
unit_check_cgroup_events(u);
u = hashmap_get(m->cgroup_memory_inotify_wd_unit, INT_TO_PTR(e->wd));
if (u)
@ -3550,6 +3597,46 @@ int compare_job_priority(const void *a, const void *b) {
return strcmp(x->unit->id, y->unit->id);
}
int unit_cgroup_freezer_action(Unit *u, FreezerAction action) {
_cleanup_free_ char *path = NULL;
FreezerState target, kernel = _FREEZER_STATE_INVALID;
int r;
assert(u);
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
if (!u->cgroup_realized)
return -EBUSY;
target = action == FREEZER_FREEZE ? FREEZER_FROZEN : FREEZER_RUNNING;
r = unit_freezer_state_kernel(u, &kernel);
if (r < 0)
log_unit_debug_errno(u, r, "Failed to obtain cgroup freezer state: %m");
if (target == kernel) {
u->freezer_state = target;
return 0;
}
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.freeze", &path);
if (r < 0)
return r;
log_unit_debug(u, "%s unit.", action == FREEZER_FREEZE ? "Freezing" : "Thawing");
if (action == FREEZER_FREEZE)
u->freezer_state = FREEZER_FREEZING;
else
u->freezer_state = FREEZER_THAWING;
r = write_string_file(path, one_zero(action == FREEZER_FREEZE), WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
return r;
return 0;
}
static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
[CGROUP_DEVICE_POLICY_AUTO] = "auto",
[CGROUP_DEVICE_POLICY_CLOSED] = "closed",
@ -3585,3 +3672,10 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) {
}
DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);
static const char* const freezer_action_table[_FREEZER_ACTION_MAX] = {
[FREEZER_FREEZE] = "freeze",
[FREEZER_THAW] = "thaw",
};
DEFINE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction);

View File

@ -47,6 +47,14 @@ typedef enum CGroupDevicePolicy {
_CGROUP_DEVICE_POLICY_INVALID = -1
} CGroupDevicePolicy;
typedef enum FreezerAction {
FREEZER_FREEZE,
FREEZER_THAW,
_FREEZER_ACTION_MAX,
_FREEZER_ACTION_INVALID = -1,
} FreezerAction;
struct CGroupDeviceAllow {
LIST_FIELDS(CGroupDeviceAllow, device_allow);
char *path;
@ -274,3 +282,7 @@ bool unit_cgroup_delegate(Unit *u);
int compare_job_priority(const void *a, const void *b);
int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name);
int unit_cgroup_freezer_action(Unit *u, FreezerAction action);
const char* freezer_action_to_string(FreezerAction a) _const_;
FreezerAction freezer_action_from_string(const char *s) _pure_;

View File

@ -620,6 +620,14 @@ static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_err
return method_generic_unit_operation(message, userdata, error, bus_unit_method_clean, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
}
static int method_freeze_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return method_generic_unit_operation(message, userdata, error, bus_unit_method_freeze, 0);
}
static int method_thaw_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return method_generic_unit_operation(message, userdata, error, bus_unit_method_thaw, 0);
}
static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Don't load the unit (because unloaded units can't be in failed state), and don't insist on the
* unit to be loaded properly (since a failed unit might have its unit file disappeared) */
@ -2584,6 +2592,18 @@ const sd_bus_vtable bus_manager_vtable[] = {
NULL,,
method_clean_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("FreezeUnit",
"s",
SD_BUS_PARAM(name),
NULL,,
method_freeze_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("ThawUnit",
"s",
SD_BUS_PARAM(name),
NULL,,
method_thaw_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("ResetFailedUnit",
"s",
SD_BUS_PARAM(name),

View File

@ -46,12 +46,14 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
static BUS_DEFINE_PROPERTY_GET(property_get_description, "s", Unit, unit_description);
static BUS_DEFINE_PROPERTY_GET2(property_get_active_state, "s", Unit, unit_active_state, unit_active_state_to_string);
static BUS_DEFINE_PROPERTY_GET2(property_get_freezer_state, "s", Unit, unit_freezer_state, freezer_state_to_string);
static BUS_DEFINE_PROPERTY_GET(property_get_sub_state, "s", Unit, unit_sub_state_to_string);
static BUS_DEFINE_PROPERTY_GET2(property_get_unit_file_state, "s", Unit, unit_get_unit_file_state, unit_file_state_to_string);
static BUS_DEFINE_PROPERTY_GET(property_get_can_reload, "b", Unit, unit_can_reload);
static BUS_DEFINE_PROPERTY_GET(property_get_can_start, "b", Unit, unit_can_start_refuse_manual);
static BUS_DEFINE_PROPERTY_GET(property_get_can_stop, "b", Unit, unit_can_stop_refuse_manual);
static BUS_DEFINE_PROPERTY_GET(property_get_can_isolate, "b", Unit, unit_can_isolate_refuse_manual);
static BUS_DEFINE_PROPERTY_GET(property_get_can_freeze, "b", Unit, unit_can_freeze);
static BUS_DEFINE_PROPERTY_GET(property_get_need_daemon_reload, "b", Unit, unit_need_daemon_reload);
static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_strv, "as", 0);
@ -724,6 +726,79 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error
return sd_bus_reply_method_return(message, NULL);
}
static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userdata, sd_bus_error *error, FreezerAction action) {
const char* perm;
int (*method)(Unit*);
Unit *u = userdata;
bool reply_no_delay = false;
int r;
assert(message);
assert(u);
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
if (action == FREEZER_FREEZE) {
perm = "stop";
method = unit_freeze;
} else {
perm = "start";
method = unit_thaw;
}
r = mac_selinux_unit_access_check(u, message, perm, error);
if (r < 0)
return r;
r = bus_verify_manage_units_async_full(
u,
perm,
CAP_SYS_ADMIN,
N_("Authentication is required to freeze or thaw the processes of '$(unit)' unit."),
true,
message,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
r = method(u);
if (r == -EOPNOTSUPP)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not support freezing.", u->id);
if (r == -EBUSY)
return sd_bus_error_setf(error, BUS_ERROR_UNIT_BUSY, "Unit has a pending job.");
if (r == -EHOSTDOWN)
return sd_bus_error_setf(error, BUS_ERROR_UNIT_INACTIVE, "Unit is inactive.");
if (r == -EALREADY)
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Previously requested freezer operation for unit '%s' is still in progress.", u->id);
if (r < 0)
return r;
if (r == 0)
reply_no_delay = true;
assert(!u->pending_freezer_message);
r = sd_bus_message_new_method_return(message, &u->pending_freezer_message);
if (r < 0)
return r;
if (reply_no_delay) {
r = bus_unit_send_pending_freezer_message(u);
if (r < 0)
return r;
}
return 1;
}
int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_THAW);
}
int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_FREEZE);
}
static int property_get_refs(
sd_bus *bus,
const char *path,
@ -793,6 +868,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("FreezerState", "s", property_get_freezer_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST),
@ -809,6 +885,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CanClean", "as", property_get_can_clean, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CanFreeze", "b", property_get_can_freeze, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Job", "(uo)", property_get_job, offsetof(Unit, job), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST),
@ -940,6 +1017,16 @@ const sd_bus_vtable bus_unit_vtable[] = {
NULL,,
bus_unit_method_clean,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Freeze",
NULL,
NULL,
bus_unit_method_freeze,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Thaw",
NULL,
NULL,
bus_unit_method_thaw,
SD_BUS_VTABLE_UNPRIVILEGED),
/* For dependency types we don't support anymore always return an empty array */
SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_empty_strv, 0, SD_BUS_VTABLE_HIDDEN),
@ -1566,6 +1653,23 @@ void bus_unit_send_pending_change_signal(Unit *u, bool including_new) {
bus_unit_send_change_signal(u);
}
int bus_unit_send_pending_freezer_message(Unit *u) {
int r;
assert(u);
if (!u->pending_freezer_message)
return 0;
r = sd_bus_send(NULL, u->pending_freezer_message, NULL);
if (r < 0)
log_warning_errno(r, "Failed to send queued message, ignoring: %m");
u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
return 0;
}
static int send_removed_signal(sd_bus *bus, void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_free_ char *p = NULL;

View File

@ -11,6 +11,7 @@ extern const sd_bus_vtable bus_unit_cgroup_vtable[];
void bus_unit_send_change_signal(Unit *u);
void bus_unit_send_pending_change_signal(Unit *u, bool including_new);
int bus_unit_send_pending_freezer_message(Unit *u);
void bus_unit_send_removed_signal(Unit *u);
int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error);
@ -25,6 +26,8 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd
int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error);
typedef enum BusUnitQueueFlags {
BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE = 1 << 0,

View File

@ -960,10 +960,15 @@ static void destroy_bus(Manager *m, sd_bus **bus) {
if (j->bus_track && sd_bus_track_get_bus(j->bus_track) == *bus)
j->bus_track = sd_bus_track_unref(j->bus_track);
HASHMAP_FOREACH(u, m->units, i)
HASHMAP_FOREACH(u, m->units, i) {
if (u->bus_track && sd_bus_track_get_bus(u->bus_track) == *bus)
u->bus_track = sd_bus_track_unref(u->bus_track);
/* Get rid of pending freezer messages on this bus */
if (u->pending_freezer_message && sd_bus_message_get_bus(u->pending_freezer_message) == *bus)
u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
}
/* Get rid of queued message on this bus */
if (m->pending_reload_message && sd_bus_message_get_bus(m->pending_reload_message) == *bus)
m->pending_reload_message = sd_bus_message_unref(m->pending_reload_message);

View File

@ -2284,6 +2284,20 @@ static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, ui
return 0;
}
static bool manager_process_barrier_fd(const char *buf, FDSet *fds) {
assert(buf);
/* nothing else must be sent when using BARRIER=1 */
if (STR_IN_SET(buf, "BARRIER=1", "BARRIER=1\n")) {
if (fdset_size(fds) != 1)
log_warning("Got incorrect number of fds with BARRIER=1, closing them.");
return true;
} else if (startswith(buf, "BARRIER=1\n") || strstr(buf, "\nBARRIER=1\n") || endswith(buf, "\nBARRIER=1"))
log_warning("Extra notification messages sent with BARRIER=1, ignoring everything.");
return false;
}
static void manager_invoke_notify_message(
Manager *m,
Unit *u,
@ -2417,6 +2431,10 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
/* Make sure it's NUL-terminated. */
buf[n] = 0;
/* possibly a barrier fd, let's see */
if (manager_process_barrier_fd(buf, fds))
return 0;
/* Increase the generation counter used for filtering out duplicate unit invocations. */
m->notifygen++;

View File

@ -635,6 +635,9 @@ const UnitVTable scope_vtable = {
.kill = scope_kill,
.freeze = unit_freeze_vtable_common,
.thaw = unit_thaw_vtable_common,
.get_timeout = scope_get_timeout,
.serialize = scope_serialize,

View File

@ -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

View File

@ -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,

View File

@ -5,6 +5,7 @@
#include "alloc-util.h"
#include "dbus-slice.h"
#include "dbus-unit.h"
#include "fd-util.h"
#include "log.h"
#include "serialize.h"
#include "slice.h"
@ -347,6 +348,82 @@ static void slice_enumerate_perpetual(Manager *m) {
(void) slice_make_perpetual(m, SPECIAL_SYSTEM_SLICE, NULL);
}
static bool slice_freezer_action_supported_by_children(Unit *s) {
Unit *member;
void *v;
Iterator i;
assert(s);
HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) {
int r;
if (UNIT_DEREF(member->slice) != s)
continue;
if (member->type == UNIT_SLICE) {
r = slice_freezer_action_supported_by_children(member);
if (!r)
return r;
}
if (!UNIT_VTABLE(member)->freeze)
return false;
}
return true;
}
static int slice_freezer_action(Unit *s, FreezerAction action) {
Unit *member;
void *v;
Iterator i;
int r;
assert(s);
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
if (!slice_freezer_action_supported_by_children(s))
return log_unit_warning(s, "Requested freezer operation is not supported by all children of the slice");
HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) {
if (UNIT_DEREF(member->slice) != s)
continue;
if (action == FREEZER_FREEZE)
r = UNIT_VTABLE(member)->freeze(member);
else
r = UNIT_VTABLE(member)->thaw(member);
if (r < 0)
return r;
}
r = unit_cgroup_freezer_action(s, action);
if (r < 0)
return r;
return 0;
}
static int slice_freeze(Unit *s) {
assert(s);
return slice_freezer_action(s, FREEZER_FREEZE);
}
static int slice_thaw(Unit *s) {
assert(s);
return slice_freezer_action(s, FREEZER_THAW);
}
static bool slice_can_freeze(Unit *s) {
assert(s);
return slice_freezer_action_supported_by_children(s);
}
const UnitVTable slice_vtable = {
.object_size = sizeof(Slice),
.cgroup_context_offset = offsetof(Slice, cgroup_context),
@ -371,6 +448,10 @@ const UnitVTable slice_vtable = {
.kill = slice_kill,
.freeze = slice_freeze,
.thaw = slice_thaw,
.can_freeze = slice_can_freeze,
.serialize = slice_serialize,
.deserialize_item = slice_deserialize_item,

View File

@ -628,6 +628,7 @@ void unit_free(Unit *u) {
sd_bus_slot_unref(u->match_bus_slot);
sd_bus_track_unref(u->bus_track);
u->deserialized_refs = strv_free(u->deserialized_refs);
u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message);
unit_free_requires_mounts_for(u);
@ -737,6 +738,38 @@ void unit_free(Unit *u) {
free(u);
}
FreezerState unit_freezer_state(Unit *u) {
assert(u);
return u->freezer_state;
}
int unit_freezer_state_kernel(Unit *u, FreezerState *ret) {
char *values[1] = {};
int r;
assert(u);
r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events",
STRV_MAKE("frozen"), values);
if (r < 0)
return r;
r = _FREEZER_STATE_INVALID;
if (values[0]) {
if (streq(values[0], "0"))
r = FREEZER_RUNNING;
else if (streq(values[0], "1"))
r = FREEZER_FROZEN;
}
free(values[0]);
*ret = r;
return 0;
}
UnitActiveState unit_active_state(Unit *u) {
assert(u);
@ -1846,6 +1879,7 @@ int unit_start(Unit *u) {
* waits for a holdoff timer to elapse before it will start again. */
unit_add_to_dbus_queue(u);
unit_cgroup_freezer_action(u, FREEZER_THAW);
return UNIT_VTABLE(u)->start(u);
}
@ -1898,6 +1932,7 @@ int unit_stop(Unit *u) {
return -EBADR;
unit_add_to_dbus_queue(u);
unit_cgroup_freezer_action(u, FREEZER_THAW);
return UNIT_VTABLE(u)->stop(u);
}
@ -1954,6 +1989,8 @@ int unit_reload(Unit *u) {
return 0;
}
unit_cgroup_freezer_action(u, FREEZER_THAW);
return UNIT_VTABLE(u)->reload(u);
}
@ -3497,6 +3534,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
if (!sd_id128_is_null(u->invocation_id))
(void) serialize_item_format(f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id));
(void) serialize_item_format(f, "freezer-state", "%s", freezer_state_to_string(unit_freezer_state(u)));
bus_track_serialize(u->bus_track, f, "ref");
for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) {
@ -3805,6 +3844,16 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m");
}
continue;
} else if (streq(l, "freezer-state")) {
FreezerState s;
s = freezer_state_from_string(v);
if (s < 0)
log_unit_debug(u, "Failed to deserialize freezer-state '%s', ignoring.", v);
else
u->freezer_state = s;
continue;
}
@ -6076,6 +6125,80 @@ int unit_can_clean(Unit *u, ExecCleanMask *ret) {
return UNIT_VTABLE(u)->can_clean(u, ret);
}
bool unit_can_freeze(Unit *u) {
assert(u);
if (UNIT_VTABLE(u)->can_freeze)
return UNIT_VTABLE(u)->can_freeze(u);
return UNIT_VTABLE(u)->freeze;
}
void unit_frozen(Unit *u) {
assert(u);
u->freezer_state = FREEZER_FROZEN;
bus_unit_send_pending_freezer_message(u);
}
void unit_thawed(Unit *u) {
assert(u);
u->freezer_state = FREEZER_RUNNING;
bus_unit_send_pending_freezer_message(u);
}
static int unit_freezer_action(Unit *u, FreezerAction action) {
UnitActiveState s;
int (*method)(Unit*);
int r;
assert(u);
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
method = action == FREEZER_FREEZE ? UNIT_VTABLE(u)->freeze : UNIT_VTABLE(u)->thaw;
if (!method || !cg_freezer_supported())
return -EOPNOTSUPP;
if (u->job)
return -EBUSY;
if (u->load_state != UNIT_LOADED)
return -EHOSTDOWN;
s = unit_active_state(u);
if (s != UNIT_ACTIVE)
return -EHOSTDOWN;
if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING))
return -EALREADY;
r = method(u);
if (r <= 0)
return r;
return 1;
}
int unit_freeze(Unit *u) {
return unit_freezer_action(u, FREEZER_FREEZE);
}
int unit_thaw(Unit *u) {
return unit_freezer_action(u, FREEZER_THAW);
}
/* Wrappers around low-level cgroup freezer operations common for service and scope units */
int unit_freeze_vtable_common(Unit *u) {
return unit_cgroup_freezer_action(u, FREEZER_FREEZE);
}
int unit_thaw_vtable_common(Unit *u) {
return unit_cgroup_freezer_action(u, FREEZER_THAW);
}
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
[COLLECT_INACTIVE] = "inactive",
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",

View File

@ -114,6 +114,9 @@ typedef struct Unit {
UnitLoadState load_state;
Unit *merged_into;
FreezerState freezer_state;
sd_bus_message *pending_freezer_message;
char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
char *instance;
@ -483,6 +486,11 @@ typedef struct UnitVTable {
/* Clear out the various runtime/state/cache/logs/configuration data */
int (*clean)(Unit *u, ExecCleanMask m);
/* Freeze the unit */
int (*freeze)(Unit *u);
int (*thaw)(Unit *u);
bool (*can_freeze)(Unit *u);
/* Return which kind of data can be cleaned */
int (*can_clean)(Unit *u, ExecCleanMask *ret);
@ -695,6 +703,8 @@ const char *unit_status_string(Unit *u) _pure_;
bool unit_has_name(const Unit *u, const char *name);
UnitActiveState unit_active_state(Unit *u);
FreezerState unit_freezer_state(Unit *u);
int unit_freezer_state_kernel(Unit *u, FreezerState *ret);
const char* unit_sub_state_to_string(Unit *u);
@ -878,6 +888,16 @@ void unit_destroy_runtime_directory(Unit *u, const ExecContext *context);
int unit_clean(Unit *u, ExecCleanMask mask);
int unit_can_clean(Unit *u, ExecCleanMask *ret_mask);
bool unit_can_freeze(Unit *u);
int unit_freeze(Unit *u);
void unit_frozen(Unit *u);
int unit_thaw(Unit *u);
void unit_thawed(Unit *u);
int unit_freeze_vtable_common(Unit *u);
int unit_thaw_vtable_common(Unit *u);
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \

View File

@ -29,6 +29,7 @@
#define BUS_ERROR_DISK_FULL "org.freedesktop.systemd1.DiskFull"
#define BUS_ERROR_NOTHING_TO_CLEAN "org.freedesktop.systemd1.NothingToClean"
#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy"
#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive"
#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"

View File

@ -4,6 +4,7 @@
#include <limits.h>
#include <mqueue.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
@ -23,6 +24,7 @@
#include "process-util.h"
#include "socket-util.h"
#include "strv.h"
#include "time-util.h"
#include "util.h"
#define SNDBUF_SIZE (8*1024*1024)
@ -551,6 +553,34 @@ finish:
return r;
}
_public_ int sd_notify_barrier(int unset_environment, uint64_t timeout) {
_cleanup_close_pair_ int pipe_fd[2] = { -1, -1 };
struct timespec ts;
int r;
if (pipe2(pipe_fd, O_CLOEXEC) < 0)
return -errno;
r = sd_pid_notify_with_fds(0, unset_environment, "BARRIER=1", &pipe_fd[1], 1);
if (r <= 0)
return r;
pipe_fd[1] = safe_close(pipe_fd[1]);
struct pollfd pfd = {
.fd = pipe_fd[0],
/* POLLHUP is implicit */
.events = 0,
};
r = ppoll(&pfd, 1, timeout == UINT64_MAX ? NULL : timespec_store(&ts, timeout), NULL);
if (r < 0)
return -errno;
if (r == 0)
return -ETIMEDOUT;
return 1;
}
_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) {
return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0);
}

View File

@ -18,6 +18,7 @@
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
#include "user-util.h"
#include "util.h"
@ -27,6 +28,7 @@ static const char *arg_status = NULL;
static bool arg_booted = false;
static uid_t arg_uid = UID_INVALID;
static gid_t arg_gid = GID_INVALID;
static bool arg_no_block = false;
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -45,6 +47,7 @@ static int help(void) {
" --uid=USER Set user to send from\n"
" --status=TEXT Set status text\n"
" --booted Check if the system was booted up with systemd\n"
" --no-block Do not wait until operation finished\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, ansi_highlight(), ansi_normal()
@ -83,6 +86,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_STATUS,
ARG_BOOTED,
ARG_UID,
ARG_NO_BLOCK
};
static const struct option options[] = {
@ -93,6 +97,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "status", required_argument, NULL, ARG_STATUS },
{ "booted", no_argument, NULL, ARG_BOOTED },
{ "uid", required_argument, NULL, ARG_UID },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{}
};
@ -157,6 +162,10 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_NO_BLOCK:
arg_no_block = true;
break;
case '?':
return -EINVAL;
@ -256,6 +265,16 @@ static int run(int argc, char* argv[]) {
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"No status data could be sent: $NOTIFY_SOCKET was not set");
if (!arg_no_block) {
r = sd_notify_barrier(0, 5 * USEC_PER_SEC);
if (r < 0)
return log_error_errno(r, "Failed to invoke barrier: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"No status data could be sent: $NOTIFY_SOCKET was not set");
}
return 0;
}

View File

@ -3834,11 +3834,12 @@ static int kill_unit(int argc, char *argv[], void *userdata) {
return r;
}
static int clean_unit(int argc, char *argv[], void *userdata) {
static int clean_or_freeze_unit(int argc, char *argv[], void *userdata) {
_cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL;
_cleanup_strv_free_ char **names = NULL;
int r, ret = EXIT_SUCCESS;
char **name;
const char *method;
sd_bus *bus;
r = acquire_bus(BUS_FULL, &bus);
@ -3863,6 +3864,13 @@ static int clean_unit(int argc, char *argv[], void *userdata) {
return log_error_errno(r, "Failed to allocate unit waiter: %m");
}
if (streq(argv[0], "clean"))
method = "CleanUnit";
else if (streq(argv[0], "freeze"))
method = "FreezeUnit";
else if (streq(argv[0], "thaw"))
method = "ThawUnit";
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
@ -3892,7 +3900,7 @@ static int clean_unit(int argc, char *argv[], void *userdata) {
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"CleanUnit");
method);
if (r < 0)
return bus_log_create_error(r);
@ -3900,13 +3908,15 @@ static int clean_unit(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, arg_clean_what);
if (r < 0)
return bus_log_create_error(r);
if (streq(method, "CleanUnit")) {
r = sd_bus_message_append_strv(m, arg_clean_what);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_call(bus, m, 0, &error, NULL);
if (r < 0) {
log_error_errno(r, "Failed to clean unit %s: %s", *name, bus_error_message(&error, r));
log_error_errno(r, "Failed to %s unit %s: %s", argv[0], *name, bus_error_message(&error, r));
if (ret == EXIT_SUCCESS) {
ret = r;
continue;
@ -4046,6 +4056,7 @@ typedef struct UnitStatusInfo {
const char *id;
const char *load_state;
const char *active_state;
const char *freezer_state;
const char *sub_state;
const char *unit_file_state;
const char *unit_file_preset;
@ -4182,7 +4193,7 @@ static void print_status_info(
bool *ellipsized) {
char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX];
const char *s1, *s2, *active_on, *active_off, *on, *off, *ss;
const char *s1, *s2, *active_on, *active_off, *on, *off, *ss, *fs;
_cleanup_free_ char *formatted_path = NULL;
ExecStatusInfo *p;
usec_t timestamp;
@ -4276,12 +4287,16 @@ static void print_status_info(
ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state;
if (ss)
printf(" Active: %s%s (%s)%s",
printf(" Active: %s%s (%s)%s",
active_on, strna(i->active_state), ss, active_off);
else
printf(" Active: %s%s%s",
active_on, strna(i->active_state), active_off);
fs = !isempty(i->freezer_state) && !streq(i->freezer_state, "running") ? i->freezer_state : NULL;
if (fs)
printf(" %s(%s)%s", ansi_highlight_yellow(), fs, active_off);
if (!isempty(i->result) && !streq(i->result, "success"))
printf(" (Result: %s)", i->result);
@ -5539,12 +5554,14 @@ static int show_one(
static const struct bus_properties_map property_map[] = {
{ "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) },
{ "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) },
{ "FreezerState", "s", NULL, offsetof(UnitStatusInfo, freezer_state) },
{ "Documentation", "as", NULL, offsetof(UnitStatusInfo, documentation) },
{}
}, status_map[] = {
{ "Id", "s", NULL, offsetof(UnitStatusInfo, id) },
{ "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) },
{ "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) },
{ "FreezerState", "s", NULL, offsetof(UnitStatusInfo, freezer_state) },
{ "SubState", "s", NULL, offsetof(UnitStatusInfo, sub_state) },
{ "UnitFileState", "s", NULL, offsetof(UnitStatusInfo, unit_file_state) },
{ "UnitFilePreset", "s", NULL, offsetof(UnitStatusInfo, unit_file_preset) },
@ -7887,6 +7904,8 @@ static int systemctl_help(void) {
" kill UNIT... Send signal to processes of a unit\n"
" clean UNIT... Clean runtime, cache, state, logs or\n"
" configuration of unit\n"
" freeze PATTERN... Freeze execution of unit processes\n"
" thaw PATTERN... Resume execution of a frozen unit\n"
" is-active PATTERN... Check whether units are active\n"
" is-failed PATTERN... Check whether units are failed\n"
" status [PATTERN...|PID...] Show runtime status of one or more units\n"
@ -9160,7 +9179,9 @@ static int systemctl_main(int argc, char *argv[]) {
{ "condrestart", 2, VERB_ANY, VERB_ONLINE_ONLY, start_unit }, /* For compatibility with RH */
{ "isolate", 2, 2, VERB_ONLINE_ONLY, start_unit },
{ "kill", 2, VERB_ANY, VERB_ONLINE_ONLY, kill_unit },
{ "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_unit },
{ "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_or_freeze_unit },
{ "freeze", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_or_freeze_unit },
{ "thaw", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_or_freeze_unit },
{ "is-active", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active },
{ "check", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active }, /* deprecated alias of is-active */
{ "is-failed", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_failed },

View File

@ -286,6 +286,19 @@ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _s
*/
int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds);
/*
Returns > 0 if synchronization with systemd succeeded. Returns < 0
on error. Returns 0 if $NOTIFY_SOCKET was not set. Note that the
timeout parameter of this function call takes the timeout in µs, and
will be passed to ppoll(2), hence the behaviour will be similar to
ppoll(2). This function can be called after sending a status message
to systemd, if one needs to synchronize against reception of the
status messages sent before this call is made. Therefore, this
cannot be used to know if the status message was processed
successfully, but to only synchronize against its consumption.
*/
int sd_notify_barrier(int unset_environment, uint64_t timeout);
/*
Returns > 0 if the system was booted with systemd. Returns < 0 on
error. Returns 0 if the system was not booted with systemd. Note

View File

@ -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]);

View File

@ -0,0 +1 @@
../TEST-01-BASIC/Makefile

7
test/TEST-38-FREEZER/test.sh Executable file
View File

@ -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

View File

@ -102,6 +102,7 @@ BASICTOOLS=(
tar
tee
test
timeout
touch
tr
true

View File

@ -0,0 +1,2 @@
[Service]
ExecStart=/bin/sleep 3600

View File

@ -0,0 +1,6 @@
[Unit]
Description=TEST-38-FREEZER
[Service]
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot

293
test/units/testsuite-38.sh Executable file
View File

@ -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