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:
commit
10859c4902
3
TODO
3
TODO
@ -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=.
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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_;
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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, ...) \
|
||||
|
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
434
src/shared/bus-wait-for-units.c
Normal file
434
src/shared/bus-wait-for-units.c
Normal 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;
|
||||
}
|
35
src/shared/bus-wait-for-units.h
Normal file
35
src/shared/bus-wait-for-units.h
Normal 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);
|
@ -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
|
||||
|
@ -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 },
|
||||
|
1
test/TEST-33-CLEAN-UNIT/Makefile
Symbolic link
1
test/TEST-33-CLEAN-UNIT/Makefile
Symbolic link
@ -0,0 +1 @@
|
||||
../TEST-01-BASIC/Makefile
|
49
test/TEST-33-CLEAN-UNIT/test.sh
Executable file
49
test/TEST-33-CLEAN-UNIT/test.sh
Executable 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 "$@"
|
79
test/TEST-33-CLEAN-UNIT/testsuite.sh
Executable file
79
test/TEST-33-CLEAN-UNIT/testsuite.sh
Executable 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
|
Loading…
x
Reference in New Issue
Block a user