core: add new OnSuccess= dependency type

This is similar to OnFailure= but is activated whenever a unit returns
into inactive state successfully.

I was always afraid of adding this, since it effectively allows building
loops and makes our engine Turing complete, but it pretty much already
was it was just hidden.

Given that we have per-unit ratelimits as well as an event loop global
ratelimit I feel safe to add this finally, given it actually is useful.

Fixes: #13386
This commit is contained in:
Lennart Poettering 2021-04-14 14:36:15 +02:00
parent 47cd17ead4
commit 294446dcb9
13 changed files with 115 additions and 27 deletions

View file

@ -1632,6 +1632,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as OnFailureOf = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as OnSuccess = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as OnSuccessOf = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Triggers = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as TriggeredBy = ['...', ...];
@ -1702,6 +1706,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b DefaultDependencies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OnSuccesJobMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OnFailureJobMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b IgnoreOnIsolate = ...;
@ -1781,6 +1787,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property OnFailureOf is not documented!-->
<!--property OnSuccess is not documented!-->
<!--property OnSuccessOf is not documented!-->
<!--property ReloadPropagatedFrom is not documented!-->
<!--property PropagatesStopTo is not documented!-->
@ -1805,6 +1815,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property CanFreeze is not documented!-->
<!--property OnSuccesJobMode is not documented!-->
<!--property OnFailureJobMode is not documented!-->
<!--property JobRunningTimeoutUSec is not documented!-->
@ -1919,6 +1931,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="OnFailureOf"/>
<variablelist class="dbus-property" generated="True" extra-ref="OnSuccess"/>
<variablelist class="dbus-property" generated="True" extra-ref="OnSuccessOf"/>
<variablelist class="dbus-property" generated="True" extra-ref="Triggers"/>
<variablelist class="dbus-property" generated="True" extra-ref="TriggeredBy"/>
@ -2003,6 +2019,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="DefaultDependencies"/>
<variablelist class="dbus-property" generated="True" extra-ref="OnSuccesJobMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="OnFailureJobMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="IgnoreOnIsolate"/>

View file

@ -776,11 +776,16 @@
<varlistentry>
<term><varname>OnFailure=</varname></term>
<listitem><para>A space-separated list of one or more units
that are activated when this unit enters the
<literal>failed</literal> state. A service unit using
<varname>Restart=</varname> enters the failed state only after
the start limits are reached.</para></listitem>
<listitem><para>A space-separated list of one or more units that are activated when this unit enters
the <literal>failed</literal> state. A service unit using <varname>Restart=</varname> enters the
failed state only after the start limits are reached.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>OnSuccess=</varname></term>
<listitem><para>A space-separated list of one or more units that are activated when this unit enters
the <literal>inactive</literal> state.</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -274,6 +274,8 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
[UNIT_CONFLICTED_BY] = "ConflictedBy",
[UNIT_BEFORE] = "Before",
[UNIT_AFTER] = "After",
[UNIT_ON_SUCCESS] = "OnSuccess",
[UNIT_ON_SUCCESS_OF] = "OnSuccessOf",
[UNIT_ON_FAILURE] = "OnFailure",
[UNIT_ON_FAILURE_OF] = "OnFailureOf",
[UNIT_TRIGGERS] = "Triggers",

View file

@ -227,7 +227,9 @@ typedef enum UnitDependency {
UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */
UNIT_AFTER,
/* On Failure */
/* OnSuccess= + OnFailure= */
UNIT_ON_SUCCESS,
UNIT_ON_SUCCESS_OF,
UNIT_ON_FAILURE,
UNIT_ON_FAILURE_OF,

View file

@ -866,6 +866,8 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("After", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnFailureOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnSuccess", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnSuccessOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@ -903,6 +905,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("RefuseManualStop", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_stop), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("AllowIsolate", "b", bus_property_get_bool, offsetof(Unit, allow_isolate), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultDependencies", "b", bus_property_get_bool, offsetof(Unit, default_dependencies), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnSuccesJobMode", "s", property_get_job_mode, offsetof(Unit, on_success_job_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnFailureJobMode", "s", property_get_job_mode, offsetof(Unit, on_failure_job_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@ -2126,6 +2129,9 @@ static int bus_unit_set_transient_property(
if (streq(name, "DefaultDependencies"))
return bus_set_transient_bool(u, name, &u->default_dependencies, message, flags, error);
if (streq(name, "OnSuccessJobMode"))
return bus_set_transient_job_mode(u, name, &u->on_success_job_mode, message, flags, error);
if (streq(name, "OnFailureJobMode"))
return bus_set_transient_job_mode(u, name, &u->on_failure_job_mode, message, flags, error);
@ -2301,6 +2307,7 @@ static int bus_unit_set_transient_property(
UNIT_CONFLICTS,
UNIT_BEFORE,
UNIT_AFTER,
UNIT_ON_SUCCESS,
UNIT_ON_FAILURE,
UNIT_PROPAGATES_RELOAD_TO,
UNIT_RELOAD_PROPAGATED_FROM,

View file

@ -1047,7 +1047,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr
job_type_to_string(t),
job_result_to_string(result)));
unit_start_on_failure(u);
unit_start_on_failure(u, "OnFailure=", UNIT_ATOM_ON_FAILURE, u->on_failure_job_mode);
}
unit_trigger_notify(u);

View file

@ -264,6 +264,7 @@ Unit.BindTo, config_parse_unit_deps,
Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0
Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0
Unit.After, config_parse_unit_deps, UNIT_AFTER, 0
Unit.OnSuccess, config_parse_unit_deps, UNIT_ON_SUCCESS, 0
Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0
Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0
Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0
@ -281,6 +282,7 @@ Unit.RefuseManualStart, config_parse_bool,
Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop)
Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate)
Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies)
Unit.OnSuccessJobMode, config_parse_job_mode, 0, offsetof(Unit, on_success_job_mode)
Unit.OnFailureJobMode, config_parse_job_mode, 0, offsetof(Unit, on_failure_job_mode)
{# The following is a legacy alias name for compatibility #}
Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode)

View file

@ -74,6 +74,7 @@ static const UnitDependencyAtom atom_map[_UNIT_DEPENDENCY_MAX] = {
/* These are simple dependency types: they consist of a single atom only */
[UNIT_BEFORE] = UNIT_ATOM_BEFORE,
[UNIT_AFTER] = UNIT_ATOM_AFTER,
[UNIT_ON_SUCCESS] = UNIT_ATOM_ON_SUCCESS,
[UNIT_ON_FAILURE] = UNIT_ATOM_ON_FAILURE,
[UNIT_TRIGGERS] = UNIT_ATOM_TRIGGERS,
[UNIT_TRIGGERED_BY] = UNIT_ATOM_TRIGGERED_BY,
@ -88,6 +89,7 @@ static const UnitDependencyAtom atom_map[_UNIT_DEPENDENCY_MAX] = {
* things discoverable/debuggable as they are the inverse dependencies to some of the above. As they
* have no effect of their own, they all map to no atoms at all, i.e. the value 0. */
[UNIT_RELOAD_PROPAGATED_FROM] = 0,
[UNIT_ON_SUCCESS_OF] = 0,
[UNIT_ON_FAILURE_OF] = 0,
[UNIT_STOP_PROPAGATED_FROM] = 0,
};
@ -175,6 +177,9 @@ UnitDependency unit_dependency_from_unique_atom(UnitDependencyAtom atom) {
case UNIT_ATOM_AFTER:
return UNIT_AFTER;
case UNIT_ATOM_ON_SUCCESS:
return UNIT_ON_SUCCESS;
case UNIT_ATOM_ON_FAILURE:
return UNIT_ON_FAILURE;

View file

@ -62,6 +62,7 @@ typedef enum UnitDependencyAtom {
/* The remaining atoms map 1:1 to the equally named high-level deps */
UNIT_ATOM_BEFORE = UINT64_C(1) << 22,
UNIT_ATOM_AFTER = UINT64_C(1) << 23,
UNIT_ATOM_ON_SUCCESS = UINT64_C(1) << 24,
UNIT_ATOM_ON_FAILURE = UINT64_C(1) << 25,
UNIT_ATOM_TRIGGERS = UINT64_C(1) << 26,
UNIT_ATOM_TRIGGERED_BY = UINT64_C(1) << 27,

View file

@ -774,12 +774,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
"%s\tRefuseManualStart: %s\n"
"%s\tRefuseManualStop: %s\n"
"%s\tDefaultDependencies: %s\n"
"%s\tOnSuccessJobMode: %s\n"
"%s\tOnFailureJobMode: %s\n"
"%s\tIgnoreOnIsolate: %s\n",
prefix, yes_no(u->stop_when_unneeded),
prefix, yes_no(u->refuse_manual_start),
prefix, yes_no(u->refuse_manual_stop),
prefix, yes_no(u->default_dependencies),
prefix, job_mode_to_string(u->on_success_job_mode),
prefix, job_mode_to_string(u->on_failure_job_mode),
prefix, yes_no(u->ignore_on_isolate));

View file

@ -101,6 +101,7 @@ Unit* unit_new(Manager *m, size_t size) {
u->unit_file_state = _UNIT_FILE_STATE_INVALID;
u->unit_file_preset = -1;
u->on_failure_job_mode = JOB_REPLACE;
u->on_success_job_mode = JOB_FAIL;
u->cgroup_control_inotify_wd = -1;
u->cgroup_memory_inotify_wd = -1;
u->job_timeout = USEC_INFINITY;
@ -896,6 +897,7 @@ static void unit_maybe_warn_about_dependency(
UNIT_CONFLICTED_BY,
UNIT_BEFORE,
UNIT_AFTER,
UNIT_ON_SUCCESS,
UNIT_ON_FAILURE,
UNIT_TRIGGERS,
UNIT_TRIGGERED_BY))
@ -1507,6 +1509,31 @@ static int unit_add_startup_units(Unit *u) {
return set_ensure_put(&u->manager->startup_units, NULL, u);
}
static int unit_validate_on_failure_job_mode(
Unit *u,
const char *job_mode_setting,
JobMode job_mode,
const char *dependency_name,
UnitDependencyAtom atom) {
Unit *other, *found = NULL;
if (job_mode != JOB_ISOLATE)
return 0;
UNIT_FOREACH_DEPENDENCY(other, u, atom) {
if (!found)
found = other;
else if (found != other)
return log_unit_error_errno(
u, SYNTHETIC_ERRNO(ENOEXEC),
"More than one %s dependencies specified but %sisolate set. Refusing.",
dependency_name, job_mode_setting);
}
return 0;
}
int unit_load(Unit *u) {
int r;
@ -1560,19 +1587,13 @@ int unit_load(Unit *u) {
if (r < 0)
goto fail;
if (u->on_failure_job_mode == JOB_ISOLATE) {
Unit *other, *found = NULL;
r = unit_validate_on_failure_job_mode(u, "OnSuccessJobMode=", u->on_success_job_mode, "OnSuccess=", UNIT_ATOM_ON_SUCCESS);
if (r < 0)
goto fail;
UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_ON_FAILURE) {
if (!found)
found = other;
else if (found != other) {
r = log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC),
"More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing.");
goto fail;
}
}
}
r = unit_validate_on_failure_job_mode(u, "OnFailureJobMode=", u->on_failure_job_mode, "OnFailure=", UNIT_ATOM_ON_FAILURE);
if (r < 0)
goto fail;
if (u->job_running_timeout != USEC_INFINITY && u->job_running_timeout > u->job_timeout)
log_unit_warning(u, "JobRunningTimeoutSec= is greater than JobTimeoutSec=, it has no effect.");
@ -2082,25 +2103,39 @@ static void retroactively_stop_dependencies(Unit *u) {
manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL);
}
void unit_start_on_failure(Unit *u) {
void unit_start_on_failure(
Unit *u,
const char *dependency_name,
UnitDependencyAtom atom,
JobMode job_mode) {
bool logged = false;
Unit *other;
int r;
assert(u);
assert(dependency_name);
assert(IN_SET(atom, UNIT_ATOM_ON_SUCCESS, UNIT_ATOM_ON_FAILURE));
UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_ON_FAILURE) {
/* Act on OnFailure= and OnSuccess= dependencies */
UNIT_FOREACH_DEPENDENCY(other, u, atom) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (!logged) {
log_unit_info(u, "Triggering OnFailure= dependencies.");
log_unit_info(u, "Triggering %s dependencies.", dependency_name);
logged = true;
}
r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, &error, NULL);
r = manager_add_job(u->manager, JOB_START, other, job_mode, NULL, &error, NULL);
if (r < 0)
log_unit_warning_errno(u, r, "Failed to enqueue OnFailure= job, ignoring: %s", bus_error_message(&error, r));
log_unit_warning_errno(
u, r, "Failed to enqueue %s job, ignoring: %s",
dependency_name, bus_error_message(&error, r));
}
if (logged)
log_unit_debug(u, "Triggering %s dependencies done.", dependency_name);
}
void unit_trigger_notify(Unit *u) {
@ -2568,7 +2603,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
log_unit_debug(u, "Unit entered failed state.");
if (!(flags & UNIT_NOTIFY_WILL_AUTO_RESTART))
unit_start_on_failure(u);
unit_start_on_failure(u, "OnFailure=", UNIT_ATOM_ON_FAILURE, u->on_failure_job_mode);
}
if (UNIT_IS_ACTIVE_OR_RELOADING(ns) && !UNIT_IS_ACTIVE_OR_RELOADING(os)) {
@ -2584,6 +2619,10 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
unit_emit_audit_stop(u, ns);
unit_log_resources(u);
}
if (ns == UNIT_INACTIVE && !IN_SET(os, UNIT_FAILED, UNIT_INACTIVE, UNIT_MAINTENANCE) &&
!(flags & UNIT_NOTIFY_WILL_AUTO_RESTART))
unit_start_on_failure(u, "OnSuccess=", UNIT_ATOM_ON_SUCCESS, u->on_success_job_mode);
}
manager_recheck_journal(m);
@ -2871,6 +2910,8 @@ int unit_add_dependency(
[UNIT_CONFLICTED_BY] = UNIT_CONFLICTS,
[UNIT_BEFORE] = UNIT_AFTER,
[UNIT_AFTER] = UNIT_BEFORE,
[UNIT_ON_SUCCESS] = UNIT_ON_SUCCESS_OF,
[UNIT_ON_SUCCESS_OF] = UNIT_ON_SUCCESS,
[UNIT_ON_FAILURE] = UNIT_ON_FAILURE_OF,
[UNIT_ON_FAILURE_OF] = UNIT_ON_FAILURE,
[UNIT_REFERENCES] = UNIT_REFERENCED_BY,

View file

@ -334,7 +334,8 @@ typedef struct Unit {
* ones which might have appeared. */
sd_event_source *rewatch_pids_event_source;
/* How to start OnFailure units */
/* How to start OnSuccess=/OnFailure= units */
JobMode on_success_job_mode;
JobMode on_failure_job_mode;
/* Tweaking the GC logic */
@ -828,7 +829,7 @@ bool unit_will_restart(Unit *u);
int unit_add_default_target_dependency(Unit *u, Unit *target);
void unit_start_on_failure(Unit *u);
void unit_start_on_failure(Unit *u, const char *dependency_name, UnitDependencyAtom atom, JobMode job_mode);
void unit_trigger_notify(Unit *u);
UnitFileState unit_get_unit_file_state(Unit *u);

View file

@ -76,6 +76,8 @@ JoinsNamespaceOf=
OnFailure=
OnFailureIsolate=
OnFailureJobMode=
OnSuccess=
OnSuccessJobMode=
PartOf=
PropagateReloadFrom=
PropagateReloadTo=