1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-28 02:50:16 +03:00

Merge pull request #12176 from poettering/clean-dir2

add "systemctl clean" operation for clearing out StateDirectory=, CacheDirectory=, … of a service unit
This commit is contained in:
Lennart Poettering 2019-07-11 15:12:02 +02:00 committed by GitHub
commit 10859c4902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1326 additions and 227 deletions

3
TODO
View File

@ -313,9 +313,6 @@ Features:
* expose IO accounting data on the bus, show it in systemd-run --wait and log
about it in the resource log message
* add "systemctl purge" for flushing out configuration, state, logs, ... of a
unit when it is stopped
* show whether a service has out-of-date configuration in "systemctl status" by
using mtime data of ConfigurationDirectory=.

View File

@ -506,6 +506,21 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--what=</option></term>
<listitem>
<para>Select what type of per-unit resources to remove when the <command>clean</command> command is
invoked, see below. Takes one of <constant>configuration</constant>, <constant>state</constant>,
<constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant> to select the
type of resource. This option may be specified more than once, in which case all specified resource
types are removed. Also accepts the special value <constant>all</constant> as a shortcut for
specifiying all five resource types. If this option is not specified defaults to the combination of
<constant>cache</constant> and <constant>runtime</constant>, i.e. the two kinds of resources that
are generally considered to be redundant and can be reconstructed on next invocation.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-f</option></term>
<term><option>--force</option></term>
@ -904,6 +919,24 @@ Sun 2017-02-26 20:57:49 EST 2h 3min left Sun 2017-02-26 11:56:36 EST 6h ago
the signal to send.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>clean <replaceable>PATTERN</replaceable></command></term>
<listitem>
<para>Remove the configuration, state, cache, logs or runtime data of the specified units. Use
<option>--what=</option> to select which kind of resource to remove. For service units this may
be used to remove the directories configured with <varname>ConfigurationDirectory=</varname>,
<varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>,
<varname>LogsDirectory=</varname> and <varname>RuntimeDirectory=</varname>, see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. For timer units this may be used to clear out the persistent timestamp data if
<varname>Persistent=</varname> is used and <option>--what=state</option> is selected, see
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This
command only applies to units that use either of these settings. If <option>--what=</option> is
not specified, both the cache and runtime data are removed (as these two types of data are
generally redundant and reproducible on the next invocation of the unit).</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>is-active <replaceable>PATTERN</replaceable></command></term>

View File

@ -981,6 +981,11 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
the directories is tied directly to the lifetime of the unit, and it is not necessary to ensure that the
<filename>tmpfiles.d</filename> configuration is executed before the unit is started.</para>
<para>To remove any of the directories created by these settings, use the <command>systemctl clean
</command> command on the relevant units, see
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para>
<para>Example: if a system service unit has the following,
<programlisting>RuntimeDirectory=foo/bar baz</programlisting>
the service manager creates <filename>/run/foo</filename> (if it does not exist),

View File

@ -610,6 +610,16 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>TimeoutCleanSec=</varname></term>
<listitem><para>Configures a timeout on the clean-up operation requested through <command>systemctl
clean …</command>, see
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details. Takes the usual time values and defaults to <constant>infinity</constant>, i.e. by default
no time-out is applied. If a time-out is configured the clean operation will be aborted forcibly when
the time-out is reached, potentially leaving resources on disk.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>RuntimeMaxSec=</varname></term>

View File

@ -286,16 +286,18 @@
<varlistentry>
<term><varname>Persistent=</varname></term>
<listitem><para>Takes a boolean argument. If true, the time
when the service unit was last triggered is stored on disk.
When the timer is activated, the service unit is triggered
immediately if it would have been triggered at least once
during the time when the timer was inactive. This is useful to
catch up on missed runs of the service when the machine was
off. Note that this setting only has an effect on timers
configured with <varname>OnCalendar=</varname>. Defaults
to <varname>false</varname>.
</para></listitem>
<listitem><para>Takes a boolean argument. If true, the time when the service unit was last triggered
is stored on disk. When the timer is activated, the service unit is triggered immediately if it
would have been triggered at least once during the time when the timer was inactive. This is useful
to catch up on missed runs of the service when the system was powered down. Note that this setting
only has an effect on timers configured with <varname>OnCalendar=</varname>. Defaults to
<varname>false</varname>.</para>
<para>Use <command>systemctl clean --what=state …</command> on the timer unit to remove the timestamp
file maintained by this option from disk. In particular, use this command before uninstalling a timer
unit. See
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -161,9 +161,8 @@ int rm_rf(const char *path, RemoveFlags flags) {
if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
return -EINVAL;
/* We refuse to clean the root file system with this
* call. This is extra paranoia to never cause a really
* seriously broken system. */
/* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
* really seriously broken system. */
if (path_equal_or_files_same(path, "/", AT_SYMLINK_NOFOLLOW))
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
@ -175,6 +174,9 @@ int rm_rf(const char *path, RemoveFlags flags) {
if (r >= 0)
return r;
if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
return 0;
if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
return r;
@ -183,34 +185,45 @@ int rm_rf(const char *path, RemoveFlags flags) {
fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
if (fd < 0) {
if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
return 0;
if (!IN_SET(errno, ENOTDIR, ELOOP))
return -errno;
if (!(flags & REMOVE_PHYSICAL)) {
if (statfs(path, &s) < 0)
return -errno;
if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES))
return 0;
if (is_physical_fs(&s))
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Attempted to remove files from a disk file system under \"%s\", refusing.",
path);
if (FLAGS_SET(flags, REMOVE_ROOT)) {
if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
if (statfs(path, &s) < 0)
return -errno;
if (is_physical_fs(&s))
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Attempted to remove files from a disk file system under \"%s\", refusing.",
path);
}
if (unlink(path) < 0) {
if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
return 0;
return -errno;
}
}
if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
if (unlink(path) < 0 && errno != ENOENT)
return -errno;
return 0;
}
r = rm_rf_children(fd, flags, NULL);
if (flags & REMOVE_ROOT) {
if (rmdir(path) < 0) {
if (r == 0 && errno != ENOENT)
r = -errno;
}
}
if (FLAGS_SET(flags, REMOVE_ROOT) &&
rmdir(path) < 0 &&
r >= 0 &&
(!FLAGS_SET(flags, REMOVE_MISSING_OK) || errno != ENOENT))
r = -errno;
return r;
}

View File

@ -6,10 +6,11 @@
#include "errno-util.h"
typedef enum RemoveFlags {
REMOVE_ONLY_DIRECTORIES = 1 << 0,
REMOVE_ROOT = 1 << 1,
REMOVE_PHYSICAL = 1 << 2, /* if not set, only removes files on tmpfs, never physical file systems */
REMOVE_SUBVOLUME = 1 << 3,
REMOVE_ONLY_DIRECTORIES = 1 << 0, /* Only remove empty directories, no files */
REMOVE_ROOT = 1 << 1, /* Remove the specified directory itself too, not just the contents of it */
REMOVE_PHYSICAL = 1 << 2, /* If not set, only removes files on tmpfs, never physical file systems */
REMOVE_SUBVOLUME = 1 << 3, /* Drop btrfs subvolumes in the tree too */
REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
} RemoveFlags;
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);

View File

@ -102,7 +102,8 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
[UNIT_INACTIVE] = "inactive",
[UNIT_FAILED] = "failed",
[UNIT_ACTIVATING] = "activating",
[UNIT_DEACTIVATING] = "deactivating"
[UNIT_DEACTIVATING] = "deactivating",
[UNIT_MAINTENANCE] = "maintenance",
};
DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
@ -177,6 +178,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_FINAL_SIGKILL] = "final-sigkill",
[SERVICE_FAILED] = "failed",
[SERVICE_AUTO_RESTART] = "auto-restart",
[SERVICE_CLEANING] = "cleaning",
};
DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);

View File

@ -40,6 +40,7 @@ typedef enum UnitActiveState {
UNIT_FAILED,
UNIT_ACTIVATING,
UNIT_DEACTIVATING,
UNIT_MAINTENANCE,
_UNIT_ACTIVE_STATE_MAX,
_UNIT_ACTIVE_STATE_INVALID = -1
} UnitActiveState;
@ -116,6 +117,7 @@ typedef enum ServiceState {
SERVICE_FINAL_SIGKILL,
SERVICE_FAILED,
SERVICE_AUTO_RESTART,
SERVICE_CLEANING,
_SERVICE_STATE_MAX,
_SERVICE_STATE_INVALID = -1
} ServiceState;

View File

@ -634,6 +634,12 @@ static int method_kill_unit(sd_bus_message *message, void *userdata, sd_bus_erro
return method_generic_unit_operation(message, userdata, error, bus_unit_method_kill, 0);
}
static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Load the unit if necessary, in order to load it, and insist on the unit being loaded to be
* cleaned */
return method_generic_unit_operation(message, userdata, error, bus_unit_method_clean, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
}
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) */
@ -2473,6 +2479,7 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("EnqueueUnitJob", "sss", "uososa(uosos)", method_enqueue_unit_job, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CleanUnit", "sas", NULL, method_clean_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RefUnit", "s", NULL, method_ref_unit, SD_BUS_VTABLE_UNPRIVILEGED),

View File

@ -105,6 +105,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutAbortUSec", "t", property_get_timeout_abort_usec, 0, 0),
SD_BUS_PROPERTY("TimeoutCleanUSec", "t", bus_property_get_usec, offsetof(Service, timeout_clean_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST),
BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0),
@ -124,6 +125,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("ReloadResult", "s", property_get_result, offsetof(Service, reload_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Service, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),

View File

@ -52,6 +52,42 @@ static BUS_DEFINE_PROPERTY_GET(property_get_can_isolate, "b", Unit, unit_can_iso
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);
static int property_get_can_clean(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Unit *u = userdata;
ExecCleanMask mask;
int r;
assert(bus);
assert(reply);
r = unit_can_clean(u, &mask);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "s");
if (r < 0)
return r;
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
if (!FLAGS_SET(mask, 1U << t))
continue;
r = sd_bus_message_append(reply, "s", exec_resource_type_to_string(t));
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int property_get_names(
sd_bus *bus,
const char *path,
@ -617,6 +653,74 @@ int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error
return sd_bus_reply_method_return(message, NULL);
}
int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error *error) {
ExecCleanMask mask = 0;
Unit *u = userdata;
int r;
assert(message);
assert(u);
r = mac_selinux_unit_access_check(u, message, "stop", error);
if (r < 0)
return r;
r = sd_bus_message_enter_container(message, 'a', "s");
if (r < 0)
return r;
for (;;) {
const char *i;
r = sd_bus_message_read(message, "s", &i);
if (r < 0)
return r;
if (r == 0)
break;
if (streq(i, "all"))
mask |= EXEC_CLEAN_ALL;
else {
ExecDirectoryType t;
t = exec_resource_type_from_string(i);
if (t < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
mask |= 1U << t;
}
}
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
r = bus_verify_manage_units_async_full(
u,
"clean",
CAP_DAC_OVERRIDE,
N_("Authentication is required to delete files and directories associated with '$(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 = unit_clean(u, mask);
if (r == -EOPNOTSUPP)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not supporting cleaning.", u->id);
if (r == -EUNATCH)
return sd_bus_error_setf(error, BUS_ERROR_NOTHING_TO_CLEAN, "No matching resources found.");
if (r == -EBUSY)
return sd_bus_error_setf(error, BUS_ERROR_UNIT_BUSY, "Unit is not inactive or has pending job.");
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
static int property_get_refs(
sd_bus *bus,
const char *path,
@ -701,6 +805,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST),
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("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),
@ -748,6 +853,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Ref", NULL, NULL, bus_unit_method_ref, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Unref", NULL, NULL, bus_unit_method_unref, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Clean", "as", NULL, bus_unit_method_clean, 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),

View File

@ -25,6 +25,7 @@ int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bu
int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error);
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);
typedef enum BusUnitQueueFlags {
BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE = 1 << 0,

View File

@ -4774,6 +4774,60 @@ void exec_context_revert_tty(ExecContext *c) {
}
}
int exec_context_get_clean_directories(
ExecContext *c,
char **prefix,
ExecCleanMask mask,
char ***ret) {
_cleanup_strv_free_ char **l = NULL;
ExecDirectoryType t;
int r;
assert(c);
assert(prefix);
assert(ret);
for (t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
char **i;
if (!FLAGS_SET(mask, 1U << t))
continue;
if (!prefix[t])
continue;
STRV_FOREACH(i, c->directories[t].paths) {
char *j;
j = path_join(prefix[t], *i);
if (!j)
return -ENOMEM;
r = strv_consume(&l, j);
if (r < 0)
return r;
}
}
*ret = TAKE_PTR(l);
return 0;
}
int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) {
ExecCleanMask mask = 0;
assert(c);
assert(ret);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
if (!strv_isempty(c->directories[t].paths))
mask |= 1U << t;
*ret = mask;
return 0;
}
void exec_status_start(ExecStatus *s, pid_t pid) {
assert(s);
@ -5449,6 +5503,7 @@ static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES);
/* This table maps ExecDirectoryType to the setting it is configured with in the unit */
static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory",
[EXEC_DIRECTORY_STATE] = "StateDirectory",
@ -5459,6 +5514,21 @@ static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
/* And this table maps ExecDirectoryType too, but to a generic term identifying the type of resource. This
* one is supposed to be generic enough to be used for unit types that don't use ExecContext and per-unit
* directories, specifically .timer units with their timestamp touch file. */
static const char* const exec_resource_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_RUNTIME] = "runtime",
[EXEC_DIRECTORY_STATE] = "state",
[EXEC_DIRECTORY_CACHE] = "cache",
[EXEC_DIRECTORY_LOGS] = "logs",
[EXEC_DIRECTORY_CONFIGURATION] = "configuration",
};
DEFINE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType);
/* And this table also maps ExecDirectoryType, to the environment variable we pass the selected directory to
* the service payload in. */
static const char* const exec_directory_env_name_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_RUNTIME] = "RUNTIME_DIRECTORY",
[EXEC_DIRECTORY_STATE] = "STATE_DIRECTORY",

View File

@ -132,6 +132,19 @@ typedef struct ExecDirectory {
mode_t mode;
} ExecDirectory;
typedef enum ExecCleanMask {
/* In case you wonder why the bitmask below doesn't use "directory" in its name: we want to keep this
* generic so that .timer timestamp files can nicely be covered by this too, and similar. */
EXEC_CLEAN_RUNTIME = 1U << EXEC_DIRECTORY_RUNTIME,
EXEC_CLEAN_STATE = 1U << EXEC_DIRECTORY_STATE,
EXEC_CLEAN_CACHE = 1U << EXEC_DIRECTORY_CACHE,
EXEC_CLEAN_LOGS = 1U << EXEC_DIRECTORY_LOGS,
EXEC_CLEAN_CONFIGURATION = 1U << EXEC_DIRECTORY_CONFIGURATION,
EXEC_CLEAN_NONE = 0,
EXEC_CLEAN_ALL = (1U << _EXEC_DIRECTORY_TYPE_MAX) - 1,
_EXEC_CLEAN_MASK_INVALID = -1,
} ExecCleanMask;
/* Encodes configuration parameters applied to invoked commands. Does not carry runtime data, but only configuration
* changes sourced from unit files and suchlike. ExecContext objects are usually embedded into Unit objects, and do not
* change after being loaded. */
@ -369,6 +382,9 @@ void exec_context_free_log_extra_fields(ExecContext *c);
void exec_context_revert_tty(ExecContext *c);
int exec_context_get_clean_directories(ExecContext *c, char **prefix, ExecCleanMask mask, char ***ret);
int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret);
void exec_status_start(ExecStatus *s, pid_t pid);
void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status);
void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix);
@ -401,3 +417,6 @@ ExecKeyringMode exec_keyring_mode_from_string(const char *s) _pure_;
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;

View File

@ -315,6 +315,7 @@ Service.TimeoutSec, config_parse_service_timeout, 0,
Service.TimeoutStartSec, config_parse_service_timeout, 0, 0
Service.TimeoutStopSec, config_parse_sec_fix_0, 0, offsetof(Service, timeout_stop_usec)
Service.TimeoutAbortSec, config_parse_service_timeout_abort, 0, 0
Service.TimeoutCleanSec, config_parse_sec, 0, offsetof(Service, timeout_clean_usec)
Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec)
Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec)
m4_dnl The following five only exist for compatibility, they moved into Unit, see above

View File

@ -30,6 +30,7 @@
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "rm-rf.h"
#include "serialize.h"
#include "service.h"
#include "signal-util.h"
@ -59,7 +60,8 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
/* For Type=idle we never want to delay any other jobs, hence we
@ -80,7 +82,8 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
@ -103,6 +106,7 @@ static void service_init(Unit *u) {
s->timeout_abort_set = u->manager->default_timeout_abort_set;
s->restart_usec = u->manager->default_restart_usec;
s->runtime_max_usec = USEC_INFINITY;
s->timeout_clean_usec = USEC_INFINITY;
s->type = _SERVICE_TYPE_INVALID;
s->socket_fd = -1;
s->stdin_fd = s->stdout_fd = s->stderr_fd = -1;
@ -787,8 +791,9 @@ static int service_load(Unit *u) {
}
static void service_dump(Unit *u, FILE *f, const char *prefix) {
char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX];
char buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX];
char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX],
buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX],
buf_clean[FORMAT_TIMESPAN_MAX];
ServiceExecCommand c;
Service *s = SERVICE(u);
const char *prefix2;
@ -802,6 +807,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
"%sService State: %s\n"
"%sResult: %s\n"
"%sReload Result: %s\n"
"%sClean Result: %s\n"
"%sPermissionsStartOnly: %s\n"
"%sRootDirectoryStartOnly: %s\n"
"%sRemainAfterExit: %s\n"
@ -814,6 +820,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
prefix, service_state_to_string(s->state),
prefix, service_result_to_string(s->result),
prefix, service_result_to_string(s->reload_result),
prefix, service_result_to_string(s->clean_result),
prefix, yes_no(s->permissions_start_only),
prefix, yes_no(s->root_directory_start_only),
prefix, yes_no(s->remain_after_exit),
@ -869,8 +876,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
prefix, format_timespan(buf_abort, sizeof(buf_abort), s->timeout_abort_usec, USEC_PER_SEC));
fprintf(f,
"%sTimeoutCleanSec: %s\n"
"%sRuntimeMaxSec: %s\n"
"%sWatchdogSec: %s\n",
prefix, format_timespan(buf_clean, sizeof(buf_clean), s->timeout_clean_usec, USEC_PER_SEC),
prefix, format_timespan(buf_runtime, sizeof(buf_runtime), s->runtime_max_usec, USEC_PER_SEC),
prefix, format_timespan(buf_watchdog, sizeof(buf_watchdog), s->watchdog_usec, USEC_PER_SEC));
@ -1069,7 +1078,8 @@ static void service_set_state(Service *s, ServiceState state) {
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_AUTO_RESTART))
SERVICE_AUTO_RESTART,
SERVICE_CLEANING))
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
if (!IN_SET(state,
@ -1085,7 +1095,8 @@ static void service_set_state(Service *s, ServiceState state) {
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_CLEANING)) {
service_unwatch_control_pid(s);
s->control_command = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
@ -1151,6 +1162,9 @@ static usec_t service_coldplug_timeout(Service *s) {
case SERVICE_AUTO_RESTART:
return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec);
case SERVICE_CLEANING:
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_clean_usec);
default:
return USEC_INFINITY;
}
@ -1188,13 +1202,14 @@ static int service_coldplug(Unit *u) {
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_CLEANING)) {
r = unit_watch_pid(UNIT(s), s->control_pid, false);
if (r < 0)
return r;
}
if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) {
(void) unit_enqueue_rewatch_pids(u);
(void) unit_setup_dynamic_creds(u);
(void) unit_setup_exec_runtime(u);
@ -2368,7 +2383,7 @@ static int service_start(Unit *u) {
* please! */
if (IN_SET(s->state,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_CLEANING))
return -EAGAIN;
/* Already on it! */
@ -2455,6 +2470,12 @@ static int service_stop(Unit *u) {
return 0;
}
/* If we are currently cleaning, then abort it, brutally. */
if (s->state == SERVICE_CLEANING) {
service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS);
return 0;
}
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
service_enter_stop(s, SERVICE_SUCCESS);
@ -3563,6 +3584,14 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
service_enter_dead(s, f, true);
break;
case SERVICE_CLEANING:
if (s->clean_result == SERVICE_SUCCESS)
s->clean_result = f;
service_enter_dead(s, SERVICE_SUCCESS, false);
break;
default:
assert_not_reached("Uh, control process died at wrong time.");
}
@ -3679,6 +3708,15 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
service_enter_restart(s);
break;
case SERVICE_CLEANING:
log_unit_warning(UNIT(s), "Cleaning timed out. killing.");
if (s->clean_result == SERVICE_SUCCESS)
s->clean_result = SERVICE_FAILURE_TIMEOUT;
service_enter_signal(s, SERVICE_FINAL_SIGKILL, 0);
break;
default:
assert_not_reached("Timeout at wrong time.");
}
@ -4086,6 +4124,7 @@ static void service_reset_failed(Unit *u) {
s->result = SERVICE_SUCCESS;
s->reload_result = SERVICE_SUCCESS;
s->clean_result = SERVICE_SUCCESS;
s->n_restarts = 0;
s->flush_n_restarts = false;
}
@ -4155,6 +4194,77 @@ static int service_exit_status(Unit *u) {
return s->main_exec_status.status;
}
static int service_clean(Unit *u, ExecCleanMask mask) {
_cleanup_strv_free_ char **l = NULL;
Service *s = SERVICE(u);
pid_t pid;
int r;
assert(s);
assert(mask != 0);
if (s->state != SERVICE_DEAD)
return -EBUSY;
r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l);
if (r < 0)
return r;
if (strv_isempty(l))
return -EUNATCH;
service_unwatch_control_pid(s);
s->clean_result = SERVICE_SUCCESS;
s->control_command = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_clean_usec));
if (r < 0)
goto fail;
r = unit_fork_helper_process(UNIT(s), "(sd-rmrf)", &pid);
if (r < 0)
goto fail;
if (r == 0) {
int ret = EXIT_SUCCESS;
char **i;
STRV_FOREACH(i, l) {
r = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
if (r < 0) {
log_error_errno(r, "Failed to remove '%s': %m", *i);
ret = EXIT_FAILURE;
}
}
_exit(ret);
}
r = unit_watch_pid(u, pid, true);
if (r < 0)
goto fail;
s->control_pid = pid;
service_set_state(s, SERVICE_CLEANING);
return 0;
fail:
log_unit_warning_errno(UNIT(s), r, "Failed to initiate cleaning: %m");
s->clean_result = SERVICE_FAILURE_RESOURCES;
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
return r;
}
static int service_can_clean(Unit *u, ExecCleanMask *ret) {
Service *s = SERVICE(u);
assert(s);
return exec_context_get_clean_mask(&s->exec_context, ret);
}
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
[SERVICE_RESTART_NO] = "no",
[SERVICE_RESTART_ON_SUCCESS] = "on-success",
@ -4255,6 +4365,8 @@ const UnitVTable service_vtable = {
.can_reload = service_can_reload,
.kill = service_kill,
.clean = service_clean,
.can_clean = service_can_clean,
.serialize = service_serialize,
.deserialize_item = service_deserialize_item,

View File

@ -99,6 +99,7 @@ struct Service {
usec_t timeout_stop_usec;
usec_t timeout_abort_usec;
bool timeout_abort_set;
usec_t timeout_clean_usec;
usec_t runtime_max_usec;
dual_timestamp watchdog_timestamp;
@ -147,6 +148,7 @@ struct Service {
/* If we shut down, remember why */
ServiceResult result;
ServiceResult reload_result;
ServiceResult clean_result;
bool main_pid_known:1;
bool main_pid_alien:1;

View File

@ -833,6 +833,41 @@ static void timer_timezone_change(Unit *u) {
}
}
static int timer_clean(Unit *u, ExecCleanMask mask) {
Timer *t = TIMER(u);
int r;
assert(t);
assert(mask != 0);
if (t->state != TIMER_DEAD)
return -EBUSY;
if (!IN_SET(mask, EXEC_CLEAN_STATE))
return -EUNATCH;
r = timer_setup_persistent(t);
if (r < 0)
return r;
if (!t->stamp_path)
return -EUNATCH;
if (unlink(t->stamp_path) && errno != ENOENT)
return log_unit_error_errno(u, errno, "Failed to clean stamp file of timer: %m");
return 0;
}
static int timer_can_clean(Unit *u, ExecCleanMask *ret) {
Timer *t = TIMER(u);
assert(t);
*ret = t->persistent ? EXEC_CLEAN_STATE : 0;
return 0;
}
static const char* const timer_base_table[_TIMER_BASE_MAX] = {
[TIMER_ACTIVE] = "OnActiveSec",
[TIMER_BOOT] = "OnBootSec",
@ -872,6 +907,9 @@ const UnitVTable timer_vtable = {
.start = timer_start,
.stop = timer_stop,
.clean = timer_clean,
.can_clean = timer_can_clean,
.serialize = timer_serialize,
.deserialize_item = timer_deserialize_item,

View File

@ -1750,6 +1750,8 @@ int unit_start(Unit *u) {
state = unit_active_state(u);
if (UNIT_IS_ACTIVE_OR_RELOADING(state))
return -EALREADY;
if (state == UNIT_MAINTENANCE)
return -EAGAIN;
/* Units that aren't loaded cannot be started */
if (u->load_state != UNIT_LOADED)
@ -5750,6 +5752,53 @@ int unit_test_trigger_loaded(Unit *u) {
return 0;
}
int unit_clean(Unit *u, ExecCleanMask mask) {
UnitActiveState state;
assert(u);
/* Special return values:
*
* -EOPNOTSUPP cleaning not supported for this unit type
* -EUNATCH cleaning not defined for this resource type
* -EBUSY unit currently can't be cleaned since it's running or not properly loaded, or has
* a job queued or similar
*/
if (!UNIT_VTABLE(u)->clean)
return -EOPNOTSUPP;
if (mask == 0)
return -EUNATCH;
if (u->load_state != UNIT_LOADED)
return -EBUSY;
if (u->job)
return -EBUSY;
state = unit_active_state(u);
if (!IN_SET(state, UNIT_INACTIVE))
return -EBUSY;
return UNIT_VTABLE(u)->clean(u, mask);
}
int unit_can_clean(Unit *u, ExecCleanMask *ret) {
assert(u);
if (!UNIT_VTABLE(u)->clean ||
u->load_state != UNIT_LOADED) {
*ret = 0;
return 0;
}
/* When the clean() method is set, can_clean() really should be set too */
assert(UNIT_VTABLE(u)->can_clean);
return UNIT_VTABLE(u)->can_clean(u, ret);
}
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
[COLLECT_INACTIVE] = "inactive",
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",

View File

@ -475,6 +475,12 @@ typedef struct UnitVTable {
int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error);
/* Clear out the various runtime/state/cache/logs/configuration data */
int (*clean)(Unit *u, ExecCleanMask m);
/* Return which kind of data can be cleaned */
int (*can_clean)(Unit *u, ExecCleanMask *ret);
bool (*can_reload)(Unit *u);
/* Write all data that cannot be restored from other sources
@ -854,6 +860,9 @@ int unit_failure_action_exit_status(Unit *u);
int unit_test_trigger_loaded(Unit *u);
int unit_clean(Unit *u, ExecCleanMask mask);
int unit_can_clean(Unit *u, ExecCleanMask *ret_mask);
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \

View File

@ -27,6 +27,8 @@
#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser"
#define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced"
#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_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"

View File

@ -803,9 +803,12 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r
int bus_add_match_internal(
sd_bus *bus,
const char *match) {
const char *match,
uint64_t *ret_counter) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *e;
int r;
assert(bus);
@ -814,16 +817,24 @@ int bus_add_match_internal(
e = append_eavesdrop(bus, match);
return sd_bus_call_method(
r = sd_bus_call_method(
bus,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"AddMatch",
NULL,
NULL,
&reply,
"s",
e);
if (r < 0)
return r;
/* If the caller asked for it, return the read counter of the reply */
if (ret_counter)
*ret_counter = reply->read_counter;
return r;
}
int bus_add_match_internal_async(

View File

@ -3,7 +3,7 @@
#include "sd-bus.h"
int bus_add_match_internal(sd_bus *bus, const char *match);
int bus_add_match_internal(sd_bus *bus, const char *match, uint64_t *ret_counter);
int bus_add_match_internal_async(sd_bus *bus, sd_bus_slot **ret, const char *match, sd_bus_message_handler_t callback, void *userdata);
int bus_remove_match_internal(sd_bus *bus, const char *match);

View File

@ -44,6 +44,11 @@ struct match_callback {
unsigned last_iteration;
/* Don't dispatch this slot with with messages that arrived in any iteration before or at the this
* one. We use this to ensure that matches don't apply "retroactively" and thus can confuse the
* caller: matches will only match incoming messages from the moment on the match was installed. */
uint64_t after;
char *match_string;
struct bus_match_node *match_node;
@ -226,6 +231,7 @@ struct sd_bus {
size_t wqueue_allocated;
uint64_t cookie;
uint64_t read_counter; /* A counter for each incoming msg */
char *unique_name;
uint64_t unique_id;

View File

@ -287,8 +287,16 @@ int bus_match_run(
case BUS_MATCH_LEAF:
if (bus) {
if (node->leaf.callback->last_iteration == bus->iteration_counter)
return 0;
/* Don't run this match as long as the AddMatch() call is not complete yet.
*
* Don't run this match unless the 'after' counter has been reached.
*
* Don't run this match more than once per iteration */
if (node->leaf.callback->install_slot ||
m->read_counter <= node->leaf.callback->after ||
node->leaf.callback->last_iteration == bus->iteration_counter)
return bus_match_run(bus, node->next, m);
node->leaf.callback->last_iteration = bus->iteration_counter;
}

View File

@ -128,6 +128,8 @@ struct sd_bus_message {
size_t header_offsets[_BUS_MESSAGE_HEADER_MAX];
unsigned n_header_offsets;
uint64_t read_counter;
};
static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) {

View File

@ -1146,6 +1146,7 @@ static int bus_socket_make_message(sd_bus *bus, size_t size) {
bus->n_fds = 0;
if (t) {
t->read_counter = ++bus->read_counter;
bus->rqueue[bus->rqueue_size++] = bus_message_ref_queued(t, bus);
sd_bus_message_unref(t);
}

View File

@ -484,6 +484,7 @@ static int synthesize_connected_signal(sd_bus *bus) {
return r;
bus_message_set_sender_local(bus, m);
m->read_counter = ++bus->read_counter;
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
@ -2422,6 +2423,8 @@ static int process_timeout(sd_bus *bus) {
if (r < 0)
return r;
m->read_counter = ++bus->read_counter;
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
return r;
@ -2524,6 +2527,7 @@ static int process_reply(sd_bus *bus, sd_bus_message *m) {
synthetic_reply->realtime = m->realtime;
synthetic_reply->monotonic = m->monotonic;
synthetic_reply->seqnum = m->seqnum;
synthetic_reply->read_counter = m->read_counter;
r = bus_seal_synthetic_message(bus, synthetic_reply);
if (r < 0)
@ -2866,6 +2870,8 @@ static int process_closing_reply_callback(sd_bus *bus, struct reply_callback *c)
if (r < 0)
return r;
m->read_counter = ++bus->read_counter;
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
return r;
@ -2930,6 +2936,7 @@ static int process_closing(sd_bus *bus, sd_bus_message **ret) {
return r;
bus_message_set_sender_local(bus, m);
m->read_counter = ++bus->read_counter;
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
@ -3239,8 +3246,6 @@ static int add_match_callback(
bus->current_slot = match_slot->match_callback.install_slot;
bus->current_handler = add_match_callback;
bus->current_userdata = userdata;
match_slot->match_callback.install_slot = sd_bus_slot_unref(match_slot->match_callback.install_slot);
} else {
if (failed) /* Generic failure handling: destroy the connection */
bus_enter_closing(sd_bus_message_get_bus(m));
@ -3248,6 +3253,9 @@ static int add_match_callback(
r = 1;
}
/* We don't need the install method reply slot anymore, let's free it */
match_slot->match_callback.install_slot = sd_bus_slot_unref(match_slot->match_callback.install_slot);
if (failed && match_slot->floating)
bus_slot_disconnect(match_slot, true);
@ -3319,7 +3327,7 @@ static int bus_add_match_full(
* then make it floating. */
r = sd_bus_slot_set_floating(s->match_callback.install_slot, true);
} else
r = bus_add_match_internal(bus, s->match_callback.match_string);
r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
if (r < 0)
goto finish;

View File

@ -0,0 +1,434 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-util.h"
#include "bus-wait-for-units.h"
#include "hashmap.h"
#include "string-util.h"
#include "strv.h"
#include "unit-def.h"
typedef struct WaitForItem {
BusWaitForUnits *parent;
BusWaitForUnitsFlags flags;
char *bus_path;
sd_bus_slot *slot_get_all;
sd_bus_slot *slot_properties_changed;
bus_wait_for_units_unit_callback unit_callback;
void *userdata;
char *active_state;
uint32_t job_id;
char *clean_result;
} WaitForItem;
typedef struct BusWaitForUnits {
sd_bus *bus;
sd_bus_slot *slot_disconnected;
Hashmap *items;
bus_wait_for_units_ready_callback ready_callback;
void *userdata;
WaitForItem *current;
BusWaitForUnitsState state;
bool has_failed:1;
} BusWaitForUnits;
static WaitForItem *wait_for_item_free(WaitForItem *item) {
int r;
if (!item)
return NULL;
if (item->parent) {
if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) {
r = sd_bus_call_method_async(
item->parent->bus,
NULL,
"org.freedesktop.systemd1",
item->bus_path,
"org.freedesktop.systemd1.Unit",
"Unref",
NULL,
NULL,
NULL);
if (r < 0)
log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path);
}
assert_se(hashmap_remove(item->parent->items, item->bus_path) == item);
if (item->parent->current == item)
item->parent->current = NULL;
}
sd_bus_slot_unref(item->slot_properties_changed);
sd_bus_slot_unref(item->slot_get_all);
free(item->bus_path);
free(item->active_state);
free(item->clean_result);
return mfree(item);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
static void bus_wait_for_units_clear(BusWaitForUnits *d) {
WaitForItem *item;
assert(d);
d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected);
d->bus = sd_bus_unref(d->bus);
while ((item = hashmap_first(d->items))) {
d->current = item;
item->unit_callback(d, item->bus_path, false, item->userdata);
wait_for_item_free(item);
}
d->items = hashmap_free(d->items);
}
static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
BusWaitForUnits *d = userdata;
assert(m);
assert(d);
log_error("Warning! D-Bus connection terminated.");
bus_wait_for_units_clear(d);
if (d->ready_callback)
d->ready_callback(d, false, d->userdata);
else /* If no ready callback is specified close the connection so that the event loop exits */
sd_bus_close(sd_bus_message_get_bus(m));
return 0;
}
int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) {
_cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL;
int r;
assert(bus);
assert(ret);
d = new(BusWaitForUnits, 1);
if (!d)
return -ENOMEM;
*d = (BusWaitForUnits) {
.state = BUS_WAIT_SUCCESS,
.bus = sd_bus_ref(bus),
};
r = sd_bus_match_signal_async(
bus,
&d->slot_disconnected,
"org.freedesktop.DBus.Local",
NULL,
"org.freedesktop.DBus.Local",
"Disconnected",
match_disconnected, NULL, d);
if (r < 0)
return r;
*ret = TAKE_PTR(d);
return 0;
}
BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) {
if (!d)
return NULL;
bus_wait_for_units_clear(d);
sd_bus_slot_unref(d->slot_disconnected);
sd_bus_unref(d->bus);
return mfree(d);
}
static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) {
assert(d);
if (!d->bus) /* Disconnected? */
return true;
return hashmap_isempty(d->items);
}
void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
assert(d);
d->ready_callback = callback;
d->userdata = userdata;
}
static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
assert(d);
if (!bus_wait_for_units_is_ready(d))
return;
d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
if (d->ready_callback)
d->ready_callback(d, d->state, d->userdata);
}
static void wait_for_item_check_ready(WaitForItem *item) {
BusWaitForUnits *d;
assert(item);
assert(d = item->parent);
if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) {
if (item->clean_result && !streq(item->clean_result, "success"))
d->has_failed = true;
if (!item->active_state || streq(item->active_state, "maintenance"))
return;
}
if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0)
return;
if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) {
if (streq_ptr(item->active_state, "failed"))
d->has_failed = true;
else if (!streq_ptr(item->active_state, "inactive"))
return;
}
if (item->unit_callback) {
d->current = item;
item->unit_callback(d, item->bus_path, true, item->userdata);
}
wait_for_item_free(item);
bus_wait_for_units_check_ready(d);
}
static int property_map_job(
sd_bus *bus,
const char *member,
sd_bus_message *m,
sd_bus_error *error,
void *userdata) {
WaitForItem *item = userdata;
const char *path;
uint32_t id;
int r;
assert(item);
r = sd_bus_message_read(m, "(uo)", &id, &path);
if (r < 0)
return r;
item->job_id = id;
return 0;
}
static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) {
static const struct bus_properties_map map[] = {
{ "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
{ "Job", "(uo)", property_map_job, 0 },
{ "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
{}
};
int r;
assert(item);
assert(m);
r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, NULL, item);
if (r < 0)
return r;
wait_for_item_check_ready(item);
return 0;
}
static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
WaitForItem *item = userdata;
const char *interface;
int r;
assert(item);
r = sd_bus_message_read(m, "s", &interface);
if (r < 0) {
log_debug_errno(r, "Failed to parse PropertiesChanged signal: %m");
return 0;
}
if (!streq(interface, "org.freedesktop.systemd1.Unit"))
return 0;
r = wait_for_item_parse_properties(item, m);
if (r < 0)
log_debug_errno(r, "Failed to process PropertiesChanged signal: %m");
return 0;
}
static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) {
WaitForItem *item = userdata;
int r;
assert(item);
if (sd_bus_error_is_set(error)) {
BusWaitForUnits *d = item->parent;
d->has_failed = true;
log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s",
item->bus_path, error->message);
d->current = item;
item->unit_callback(d, item->bus_path, false, item->userdata);
wait_for_item_free(item);
bus_wait_for_units_check_ready(d);
return 0;
}
r = wait_for_item_parse_properties(item, m);
if (r < 0)
log_debug_errno(r, "Failed to process GetAll method reply: %m");
return 0;
}
int bus_wait_for_units_add_unit(
BusWaitForUnits *d,
const char *unit,
BusWaitForUnitsFlags flags,
bus_wait_for_units_unit_callback callback,
void *userdata) {
_cleanup_(wait_for_item_freep) WaitForItem *item = NULL;
int r;
assert(d);
assert(unit);
assert(flags != 0);
r = hashmap_ensure_allocated(&d->items, &string_hash_ops);
if (r < 0)
return r;
item = new(WaitForItem, 1);
if (!item)
return -ENOMEM;
*item = (WaitForItem) {
.flags = flags,
.bus_path = unit_dbus_path_from_name(unit),
.unit_callback = callback,
.userdata = userdata,
.job_id = UINT32_MAX,
};
if (!item->bus_path)
return -ENOMEM;
if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) {
r = sd_bus_call_method_async(
d->bus,
NULL,
"org.freedesktop.systemd1",
item->bus_path,
"org.freedesktop.systemd1.Unit",
"Ref",
NULL,
NULL,
NULL);
if (r < 0)
return log_debug_errno(r, "Failed to add reference to unit %s: %m", unit);
item->flags |= BUS_WAIT_REFFED;
}
r = sd_bus_match_signal_async(
d->bus,
&item->slot_properties_changed,
"org.freedesktop.systemd1",
item->bus_path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
on_properties_changed,
NULL,
item);
if (r < 0)
return log_debug_errno(r, "Failed to request match for PropertiesChanged signal: %m");
r = sd_bus_call_method_async(
d->bus,
&item->slot_get_all,
"org.freedesktop.systemd1",
item->bus_path,
"org.freedesktop.DBus.Properties",
"GetAll",
on_get_all_properties,
item,
"s", FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END) ? NULL : "org.freedesktop.systemd1.Unit");
if (r < 0)
return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit);
r = hashmap_put(d->items, item->bus_path, item);
if (r < 0)
return r;
d->state = BUS_WAIT_RUNNING;
item->parent = d;
TAKE_PTR(item);
return 0;
}
int bus_wait_for_units_run(BusWaitForUnits *d) {
int r;
assert(d);
while (d->state == BUS_WAIT_RUNNING) {
r = sd_bus_process(d->bus, NULL);
if (r < 0)
return r;
if (r > 0)
continue;
r = sd_bus_wait(d->bus, (uint64_t) -1);
if (r < 0)
return r;
}
return d->state;
}
BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) {
assert(d);
return d->state;
}

View File

@ -0,0 +1,35 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "macro.h"
#include "sd-bus.h"
typedef struct BusWaitForUnits BusWaitForUnits;
typedef enum BusWaitForUnitsState {
BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */
BUS_WAIT_FAILURE, /* dito, but something failed */
BUS_WAIT_RUNNING, /* Still something to wait for */
_BUS_WAIT_FOR_UNITS_STATE_MAX,
_BUS_WAIT_FOR_UNITS_STATE_INVALID = -1,
} BusWaitForUnitsState;
typedef enum BusWaitForUnitsFlags {
BUS_WAIT_FOR_MAINTENANCE_END = 1 << 0, /* Wait until the unit is no longer in maintenance state */
BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */
BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */
BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */
} BusWaitForUnitsFlags;
typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata);
typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret);
BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d);
BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata);
int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata);
int bus_wait_for_units_run(BusWaitForUnits *d);
DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free);

View File

@ -29,6 +29,8 @@ shared_sources = files('''
bus-util.h
bus-wait-for-jobs.c
bus-wait-for-jobs.h
bus-wait-for-units.c
bus-wait-for-units.h
calendarspec.c
calendarspec.h
cgroup-show.c

View File

@ -28,6 +28,7 @@
#include "bus-unit-util.h"
#include "bus-util.h"
#include "bus-wait-for-jobs.h"
#include "bus-wait-for-units.h"
#include "cgroup-show.h"
#include "cgroup-util.h"
#include "copy.h"
@ -162,12 +163,14 @@ static const char *arg_boot_loader_entry = NULL;
static bool arg_now = false;
static bool arg_jobs_before = false;
static bool arg_jobs_after = false;
static char **arg_clean_what = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_wall, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
static int daemon_reload(int argc, char *argv[], void* userdata);
static int trivial_method(int argc, char *argv[], void *userdata);
@ -2790,158 +2793,6 @@ static const char *verb_to_job_type(const char *verb) {
return "start";
}
typedef struct {
sd_bus_slot *match;
sd_event *event;
Set *unit_paths;
bool any_failed;
} WaitContext;
static void wait_context_free(WaitContext *c) {
c->match = sd_bus_slot_unref(c->match);
c->event = sd_event_unref(c->event);
c->unit_paths = set_free_free(c->unit_paths);
}
static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
const char *path, *interface, *active_state = NULL, *job_path = NULL;
WaitContext *c = userdata;
bool is_failed;
int r;
/* Called whenever we get a PropertiesChanged signal. Checks if ActiveState changed to inactive/failed.
*
* Signal parameters: (s interface, a{sv} changed_properties, as invalidated_properties) */
path = sd_bus_message_get_path(m);
if (!set_contains(c->unit_paths, path))
return 0;
r = sd_bus_message_read(m, "s", &interface);
if (r < 0)
return bus_log_parse_error(r);
if (!streq(interface, "org.freedesktop.systemd1.Unit")) /* ActiveState is on the Unit interface */
return 0;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *s;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv");
if (r < 0)
return bus_log_parse_error(r);
if (r == 0) /* end of array */
break;
r = sd_bus_message_read(m, "s", &s); /* Property name */
if (r < 0)
return bus_log_parse_error(r);
if (streq(s, "ActiveState")) {
r = sd_bus_message_read(m, "v", "s", &active_state);
if (r < 0)
return bus_log_parse_error(r);
if (job_path) /* Found everything we need */
break;
} else if (streq(s, "Job")) {
uint32_t job_id;
r = sd_bus_message_read(m, "v", "(uo)", &job_id, &job_path);
if (r < 0)
return bus_log_parse_error(r);
/* There's still a job pending for this unit, let's ignore this for now, and return right-away. */
if (job_id != 0)
return 0;
if (active_state) /* Found everything we need */
break;
} else {
r = sd_bus_message_skip(m, "v"); /* Other property */
if (r < 0)
return bus_log_parse_error(r);
}
r = sd_bus_message_exit_container(m);
if (r < 0)
return bus_log_parse_error(r);
}
/* If this didn't contain the ActiveState property we can't do anything */
if (!active_state)
return 0;
is_failed = streq(active_state, "failed");
if (streq(active_state, "inactive") || is_failed) {
log_debug("%s became %s, dropping from --wait tracking", path, active_state);
free(set_remove(c->unit_paths, path));
c->any_failed = c->any_failed || is_failed;
} else
log_debug("ActiveState on %s changed to %s", path, active_state);
if (set_isempty(c->unit_paths))
sd_event_exit(c->event, EXIT_SUCCESS);
return 0;
}
static int wait_context_watch(
WaitContext *wait_context,
sd_bus *bus,
const char *name) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *unit_path = NULL;
int r;
assert(wait_context);
assert(name);
log_debug("Watching for property changes of %s", name);
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"RefUnit",
&error,
NULL,
"s", name);
if (r < 0)
return log_error_errno(r, "Failed to add reference to unit %s: %s", name, bus_error_message(&error, r));
unit_path = unit_dbus_path_from_name(name);
if (!unit_path)
return log_oom();
r = set_ensure_allocated(&wait_context->unit_paths, &string_hash_ops);
if (r < 0)
return log_oom();
r = set_put_strdup(wait_context->unit_paths, unit_path);
if (r < 0)
return log_error_errno(r, "Failed to add unit path %s to set: %m", unit_path);
r = sd_bus_match_signal_async(bus,
&wait_context->match,
NULL,
unit_path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
on_properties_changed, NULL, wait_context);
if (r < 0)
return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m");
return 0;
}
static int start_unit_one(
sd_bus *bus,
const char *method, /* When using classic per-job bus methods */
@ -2950,7 +2801,7 @@ static int start_unit_one(
const char *mode,
sd_bus_error *error,
BusWaitForJobs *w,
WaitContext *wait_context) {
BusWaitForUnits *wu) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *path;
@ -2962,12 +2813,6 @@ static int start_unit_one(
assert(mode);
assert(error);
if (wait_context) {
r = wait_context_watch(wait_context, bus, name);
if (r < 0)
return r;
}
log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
arg_dry_run ? "Would execute" : "Executing",
method, name, mode);
@ -3056,6 +2901,12 @@ static int start_unit_one(
return log_error_errno(r, "Failed to watch job for %s: %m", name);
}
if (wu) {
r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to watch unit %s: %m", name);
}
return 0;
fail:
@ -3187,8 +3038,8 @@ static const char** make_extra_args(const char *extra_args[static 4]) {
}
static int start_unit(int argc, char *argv[], void *userdata) {
_cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_(wait_context_free) WaitContext wait_context = {};
const char *method, *job_type, *mode, *one_name, *suffix = NULL;
_cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */
_cleanup_strv_free_ char **names = NULL;
@ -3276,19 +3127,15 @@ static int start_unit(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to enable subscription: %m");
r = sd_event_default(&wait_context.event);
r = bus_wait_for_units_new(bus, &wu);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
r = sd_bus_attach_event(bus, wait_context.event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
return log_error_errno(r, "Failed to allocate unit watch context: %m");
}
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = start_unit_one(bus, method, job_type, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu);
if (ret == EXIT_SUCCESS && r < 0)
ret = translate_bus_error_to_exit_status(r, &error);
@ -3313,11 +3160,11 @@ static int start_unit(int argc, char *argv[], void *userdata) {
(void) check_triggering_units(bus, *name);
}
if (ret == EXIT_SUCCESS && arg_wait && !set_isempty(wait_context.unit_paths)) {
r = sd_event_loop(wait_context.event);
if (arg_wait) {
r = bus_wait_for_units_run(wu);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
if (wait_context.any_failed)
return log_error_errno(r, "Failed to wait for units: %m");
if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS)
ret = EXIT_FAILURE;
}
@ -3951,6 +3798,101 @@ static int kill_unit(int argc, char *argv[], void *userdata) {
return r;
}
static int clean_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;
sd_bus *bus;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
if (!arg_clean_what) {
arg_clean_what = strv_new("cache", "runtime");
if (!arg_clean_what)
return log_oom();
}
r = expand_names(bus, strv_skip(argv, 1), NULL, &names);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
if (!arg_no_block) {
r = bus_wait_for_units_new(bus, &w);
if (r < 0)
return log_error_errno(r, "Failed to allocate unit waiter: %m");
}
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;
if (w) {
/* If we shall wait for the cleaning to complete, let's add a ref on the unit first */
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"RefUnit",
&error,
NULL,
"s", *name);
if (r < 0) {
log_error_errno(r, "Failed to add reference to unit %s: %s", *name, bus_error_message(&error, r));
if (ret == EXIT_SUCCESS)
ret = r;
continue;
}
}
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"CleanUnit");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", *name);
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);
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));
if (ret == EXIT_SUCCESS) {
ret = r;
continue;
}
}
if (w) {
r = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_REFFED|BUS_WAIT_FOR_MAINTENANCE_END, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to watch unit %s: %m", *name);
}
}
r = bus_wait_for_units_run(w);
if (r < 0)
return log_error_errno(r, "Failed to wait for units: %m");
if (r == BUS_WAIT_FAILURE)
ret = EXIT_FAILURE;
return ret;
}
typedef struct ExecStatusInfo {
char *name;
@ -7712,6 +7654,7 @@ static int systemctl_help(void) {
" When shutting down or sleeping, ignore inhibitors\n"
" --kill-who=WHO Who to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" --what=RESOURCES Which types of resources to remove\n"
" --now Start or stop unit in addition to enabling or disabling it\n"
" --dry-run Only print what would be done\n"
" -q --quiet Suppress output\n"
@ -7760,6 +7703,8 @@ static int systemctl_help(void) {
" if supported, otherwise restart\n"
" isolate UNIT Start one unit and stop all others\n"
" kill UNIT... Send signal to processes of a unit\n"
" clean UNIT... Clean runtime, cache, state, logs or\n"
" or configuration of 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"
@ -8063,6 +8008,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_NOW,
ARG_MESSAGE,
ARG_WAIT,
ARG_WHAT,
};
static const struct option options[] = {
@ -8114,6 +8060,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "now", no_argument, NULL, ARG_NOW },
{ "message", required_argument, NULL, ARG_MESSAGE },
{ "show-transaction", no_argument, NULL, 'T' },
{ "what", required_argument, NULL, ARG_WHAT },
{}
};
@ -8466,6 +8413,38 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_show_transaction = true;
break;
case ARG_WHAT: {
const char *p;
if (isempty(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--what= requires arguments.");
for (p = optarg;;) {
_cleanup_free_ char *k = NULL;
r = extract_first_word(&p, &k, ",", 0);
if (r < 0)
return log_error_errno(r, "Failed to parse directory type: %s", optarg);
if (r == 0)
break;
if (streq(k, "help")) {
puts("runtime\n"
"state\n"
"cache\n"
"logs\n"
"configuration");
return 0;
}
r = strv_consume(&arg_clean_what, TAKE_PTR(k));
if (r < 0)
return log_oom();
}
break;
}
case '.':
/* Output an error mimicking getopt, and print a hint afterwards */
log_error("%s: invalid option -- '.'", program_invocation_name);
@ -8912,6 +8891,7 @@ 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 },
{ "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

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

49
test/TEST-33-CLEAN-UNIT/test.sh Executable file
View File

@ -0,0 +1,49 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
set -e
TEST_DESCRIPTION="test CleanUnit"
. $TEST_BASE_DIR/test-functions
test_setup() {
create_empty_image
mkdir -p $TESTDIR/root
mount ${LOOPDEV}p1 $TESTDIR/root
(
LOG_LEVEL=5
eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
setup_basic_environment
# mask some services that we do not want to run in these tests
ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket
ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-machined.service
# setup the testsuite service
cat >$initdir/etc/systemd/system/testsuite.service <<EOF
[Unit]
Description=Testsuite service
[Service]
ExecStart=/bin/bash -x /testsuite.sh
Type=oneshot
StandardOutput=tty
StandardError=tty
EOF
cp testsuite.sh $initdir/
setup_testsuite
) || return 1
setup_nspawn_root
ddebug "umount $TESTDIR/root"
umount $TESTDIR/root
}
do_test "$@"

View File

@ -0,0 +1,79 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
set -ex
set -o pipefail
cat > /etc/systemd/system/testservice.service <<EOF
[Service]
ConfigurationDirectory=testservice
RuntimeDirectory=testservice
StateDirectory=testservice
CacheDirectory=testservice
LogsDirectory=testservice
RuntimeDirectoryPreserve=yes
ExecStart=/bin/sleep infinity
Type=exec
EOF
systemctl daemon-reload
! test -e /etc/testservice
! test -e /run/testservice
! test -e /var/lib/testservice
! test -e /var/cache/testservice
! test -e /var/log/testservice
systemctl start testservice
test -d /etc/testservice
test -d /run/testservice
test -d /var/lib/testservice
test -d /var/cache/testservice
test -d /var/log/testservice
! systemctl clean testservice
systemctl stop testservice
test -d /etc/testservice
test -d /run/testservice
test -d /var/lib/testservice
test -d /var/cache/testservice
test -d /var/log/testservice
systemctl clean testservice --what=configuration
! test -e /etc/testservice
test -d /run/testservice
test -d /var/lib/testservice
test -d /var/cache/testservice
test -d /var/log/testservice
systemctl clean testservice
! test -e /etc/testservice
! test -e /run/testservice
test -d /var/lib/testservice
! test -e /var/cache/testservice
test -d /var/log/testservice
systemctl clean testservice --what=logs
! test -e /etc/testservice
! test -e /run/testservice
test -d /var/lib/testservice
! test -e /var/cache/testservice
! test -e /var/log/testservice
systemctl clean testservice --what=all
! test -e /etc/testservice
! test -e /run/testservice
! test -e /var/lib/testservice
! test -e /var/cache/testservice
! test -e /var/log/testservice
echo OK > /testok
exit 0