summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd.exec.xml106
-rw-r--r--src/core/execute.h27
-rw-r--r--src/core/job.c16
-rw-r--r--src/core/job.h4
-rw-r--r--src/core/service.c100
-rw-r--r--src/core/unit-dependency-atom.c24
-rw-r--r--src/core/unit-dependency-atom.h33
-rw-r--r--src/core/unit.c23
-rw-r--r--src/core/unit.h3
l---------test/TEST-68-PROPAGATE-EXIT-STATUS/Makefile1
-rwxr-xr-xtest/TEST-68-PROPAGATE-EXIT-STATUS/test.sh11
-rw-r--r--test/units/testsuite-68.service7
-rwxr-xr-xtest/units/testsuite-68.sh230
13 files changed, 548 insertions, 37 deletions
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index cd21d5b28d..3378689b75 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -3547,6 +3547,41 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
</varlistentry>
<varlistentry>
+ <term><varname>$MONITOR_METADATA</varname></term>
+
+ <listitem><para>Only defined for the service unit type, this environment variable is passed to all
+ <varname>ExecStart=</varname> and <varname>ExecStartPre=</varname> processes which run in services
+ triggered by <varname>OnFailure=</varname> or <varname>OnSuccess=</varname> dependencies.</para>
+
+ <para>
+ The contents of this variable consists of a semi-colon separated list of metadata fields associated with the triggering
+ service. For each service which triggered the <varname>OnFailure=</varname> or <varname>OnSuccess=</varname>
+ dependency the following fields will be set:
+ </para>
+
+ <itemizedlist>
+ <listitem><para><constant>SERVICE_RESULT</constant></para></listitem>
+ <listitem><para><constant>EXIT_CODE</constant></para></listitem>
+ <listitem><para><constant>EXIT_STATUS</constant></para></listitem>
+ <listitem><para><constant>INVOCATION_ID</constant></para></listitem>
+ <listitem><para><constant>UNIT</constant></para></listitem>
+ </itemizedlist>
+
+ <para>The fields <constant>SERVICE_RESULT</constant>, <constant>EXIT_CODE</constant> and
+ <constant>EXIT_STATUS</constant> may take the same values that are allowed when set for
+ <varname>ExecStop=</varname> and <varname>ExecStopPost=</varname> processes. The fields
+ <constant>INVOCATION_ID</constant> and <constant>UNIT</constant> are the invocaton id and unit
+ name of the service which triggered the dependency. Each field is comma separated, i.e.</para>
+
+ <programlisting>
+SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=triggering.service
+ </programlisting>
+
+ </listitem>
+
+ </varlistentry>
+
+ <varlistentry>
<term><varname>$PIDFILE</varname></term>
<listitem><para>The path to the configured PID file, in case the process is forked off on behalf of
@@ -3984,6 +4019,77 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
</refsect1>
<refsect1>
+ <title>Examples</title>
+
+ <example>
+ <title><varname>$MONITOR_METADATA</varname> usage</title>
+
+ <para>A service <filename index="false">myfailer.service</filename> which can trigger an
+ <varname>OnFailure=</varname> dependency.</para>
+
+ <programlisting>
+[Unit]
+Description=Service which can trigger an OnFailure= dependency
+OnFailure=myhandler.service
+
+[Service]
+ExecStart=/bin/myprogram
+ </programlisting>
+
+ <para>A service <filename index="false">mysuccess.service</filename> which can trigger an
+ <varname>OnSuccess=</varname> dependency.</para>
+
+ <programlisting>
+[Unit]
+Description=Service which can trigger an OnSuccess= dependency
+OnSuccess=myhandler.service
+
+[Service]
+ExecStart=/bin/mysecondprogram
+ </programlisting>
+
+ <para>A service <filename index="false">myhandler.service</filename> which can be triggered
+ by any of the above services.</para>
+
+ <programlisting>
+[Unit]
+Description=Acts on service failing or succeeding
+
+[Service]
+ExecStart=/bin/bash -c "echo $MONITOR_METADATA"
+ </programlisting>
+
+ <para>If <filename index="false">myfailer.service</filename> were to run and exit in failure,
+ then <filename index="false">myhandler.service</filename> would be triggered and the
+ <varname>$MONITOR_METADATA</varname> variable would be set as follows:</para>
+
+ <programlisting>
+MONITOR_METADATA=SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=myfailer.service
+ </programlisting>
+
+ <para>If <filename index="false">mysuccess.service</filename> were to run and exit in success,
+ then <filename index="false">myhandler.service</filename> would be triggered and the
+ <varname>$MONITOR_METADATA</varname> variable would be set as follows:</para>
+
+ <programlisting>
+MONITOR_METADATA=SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=mysuccess.service
+ </programlisting>
+
+ <para>If <filename index="false">myfailer.service</filename> and <filename index="false">mysuccess.service</filename> were to run and exit,
+ there is a chance that the triggered dependency start job might be merged. Thus only a single invocation of
+ <filename index="false">myhandler.service</filename> would be triggered. In this case the <varname>$MONITOR_METADATA</varname> variable
+ would be a list containing exit metadata for both of <filename index="false">myfailer.service</filename>
+ and <filename index="false">mysuccess.service</filename>.</para>
+
+ <programlisting>
+MONITOR_METADATA=SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=myfailer.service;SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=mysuccess.service
+ </programlisting>
+
+ </example>
+
+ </refsect1>
+
+ <refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
diff --git a/src/core/execute.h b/src/core/execute.h
index b0da375def..805e9b4765 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -370,21 +370,22 @@ static inline bool exec_context_with_rootfs(const ExecContext *c) {
}
typedef enum ExecFlags {
- EXEC_APPLY_SANDBOXING = 1 << 0,
- EXEC_APPLY_CHROOT = 1 << 1,
- EXEC_APPLY_TTY_STDIN = 1 << 2,
- EXEC_PASS_LOG_UNIT = 1 << 3, /* Whether to pass the unit name to the service's journal stream connection */
- EXEC_CHOWN_DIRECTORIES = 1 << 4, /* chown() the runtime/state/cache/log directories to the user we run as, under all conditions */
- EXEC_NSS_BYPASS_BUS = 1 << 5, /* Set the SYSTEMD_NSS_BYPASS_BUS environment variable, to disable nss-systemd for dbus */
- EXEC_CGROUP_DELEGATE = 1 << 6,
- EXEC_IS_CONTROL = 1 << 7,
- EXEC_CONTROL_CGROUP = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */
- EXEC_WRITE_CREDENTIALS = 1 << 9, /* Set up the credential store logic */
+ EXEC_APPLY_SANDBOXING = 1 << 0,
+ EXEC_APPLY_CHROOT = 1 << 1,
+ EXEC_APPLY_TTY_STDIN = 1 << 2,
+ EXEC_PASS_LOG_UNIT = 1 << 3, /* Whether to pass the unit name to the service's journal stream connection */
+ EXEC_CHOWN_DIRECTORIES = 1 << 4, /* chown() the runtime/state/cache/log directories to the user we run as, under all conditions */
+ EXEC_NSS_BYPASS_BUS = 1 << 5, /* Set the SYSTEMD_NSS_BYPASS_BUS environment variable, to disable nss-systemd for dbus */
+ EXEC_CGROUP_DELEGATE = 1 << 6,
+ EXEC_IS_CONTROL = 1 << 7,
+ EXEC_CONTROL_CGROUP = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */
+ EXEC_WRITE_CREDENTIALS = 1 << 9, /* Set up the credential store logic */
/* The following are not used by execute.c, but by consumers internally */
- EXEC_PASS_FDS = 1 << 10,
- EXEC_SETENV_RESULT = 1 << 11,
- EXEC_SET_WATCHDOG = 1 << 12,
+ EXEC_PASS_FDS = 1 << 10,
+ EXEC_SETENV_RESULT = 1 << 11,
+ EXEC_SET_WATCHDOG = 1 << 12,
+ EXEC_SETENV_MONITOR_RESULT = 1 << 13, /* Pass exit status to OnFailure= and OnSuccess= dependencies. */
} ExecFlags;
/* Parameters for a specific invocation of a command. This structure is put together right before a command is
diff --git a/src/core/job.c b/src/core/job.c
index 94ab381626..67c3c8fdd2 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -99,6 +99,13 @@ Job* job_free(Job *j) {
assert(!j->subject_list);
assert(!j->object_list);
+ do {
+ Unit *tu = NULL;
+
+ LIST_FOREACH(triggered_by, tu, j->triggered_by)
+ LIST_REMOVE(triggered_by, j->triggered_by, tu);
+ } while (!LIST_IS_EMPTY(j->triggered_by));
+
job_unlink(j);
sd_bus_track_unref(j->bus_track);
@@ -107,6 +114,13 @@ Job* job_free(Job *j) {
return mfree(j);
}
+void job_add_triggering_unit(Job *j, Unit *u) {
+ assert(j);
+ assert(u);
+
+ LIST_APPEND(triggered_by, j->triggered_by, u);
+}
+
static void job_set_state(Job *j, JobState state) {
assert(j);
assert(state >= 0);
@@ -187,6 +201,8 @@ static void job_merge_into_installed(Job *j, Job *other) {
j->irreversible = j->irreversible || other->irreversible;
j->ignore_order = j->ignore_order || other->ignore_order;
+ if (other->triggered_by)
+ LIST_JOIN(triggered_by, j->triggered_by, other->triggered_by);
}
Job* job_install(Job *j) {
diff --git a/src/core/job.h b/src/core/job.h
index a66e5985b8..762b0bb19b 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -124,6 +124,8 @@ struct Job {
LIST_HEAD(JobDependency, subject_list);
LIST_HEAD(JobDependency, object_list);
+ LIST_HEAD(Unit, triggered_by);
+
/* Used for graph algs as a "I have been here" marker */
Job* marker;
unsigned generation;
@@ -244,3 +246,5 @@ JobResult job_result_from_string(const char *s) _pure_;
const char* job_type_to_access_method(JobType t);
int job_compare(Job *a, Job *b, UnitDependencyAtom assume_dep);
+
+void job_add_triggering_unit(Job *j, Unit *u);
diff --git a/src/core/service.c b/src/core/service.c
index 49579f7998..b8430eae96 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1440,6 +1440,93 @@ static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) {
return s->notify_access != NOTIFY_NONE;
}
+static int service_create_monitor_md_env(Job *j, char **ret) {
+ _cleanup_free_ char *var = NULL;
+ const char *list_delim = ";";
+ bool first = true;
+ Unit *tu;
+
+ assert(j);
+ assert(ret);
+
+ /* Create an environment variable 'MONITOR_METADATA', if creation is successful
+ * a pointer to it is returned via ret.
+ *
+ * This variable contains a space separated set of fields which relate to
+ * the service(s) which triggered job 'j'. Job 'j' is the JOB_START job for
+ * an OnFailure= or OnSuccess= dependency. Format of the MONITOR_METADATA
+ * variable is as follows:
+ *
+ * MONITOR_METADATA="SERVICE_RESULT=<result-string0>,EXIT_CODE=<exit-code0>,EXIT_STATUS=<exit-status0>,
+ * INVOCATION_ID=<id>,UNIT=<triggering-unit0.service>;
+ * SERVICE_RESULT=<result-stringN>,EXIT_CODE=<exit-codeN>,EXIT_STATUS=<exit-statusN>,
+ * INVOCATION_ID=<id>,UNIT=<triggering-unitN.service>"
+ *
+ * Multiple results may be passed as in the above example if jobs are merged, i.e.
+ * some services a and b contain an OnFailure= or OnSuccess= dependency on the same
+ * service.
+ *
+ * For example:
+ *
+ * MONITOR_METADATA="SERVICE_RESULT=exit-code,EXIT_CODE=exited,EXIT_STATUS=1,INVOCATION_ID=02dd868af2f344b18edaf74b618b2f90,UNIT=failer.service;
+ * SERVICE_RESULT=exit-code,EXIT_CODE=exited,EXIT_STATUS=1,INVOCATION_ID=80cb228bd7344f77a090eda603a3cfe2,UNIT=failer2.service"
+ */
+
+ LIST_FOREACH(triggered_by, tu, j->triggered_by) {
+ Service *env_source = SERVICE(tu);
+ int r;
+
+ if (!env_source)
+ continue;
+
+ if (first) {
+ /* Add the environment variable name first. */
+ r = strextendf(&var, "MONITOR_METADATA=");
+ if (r < 0)
+ return r;
+
+ }
+
+ r = strextendf(&var, "%sSERVICE_RESULT=%s",
+ !first ? list_delim : "", service_result_to_string(env_source->result));
+ if (r < 0)
+ return r;
+
+ first = false;
+
+ if (env_source->main_exec_status.pid > 0 &&
+ dual_timestamp_is_set(&env_source->main_exec_status.exit_timestamp)) {
+ r = strextendf(&var, ",EXIT_CODE=%s",
+ sigchld_code_to_string(env_source->main_exec_status.code));
+ if (r < 0)
+ return r;
+
+ if (env_source->main_exec_status.code == CLD_EXITED)
+ r = strextendf(&var, ",EXIT_STATUS=%i",
+ env_source->main_exec_status.status);
+ else
+ r = strextendf(&var, ",EXIT_STATUS=%s",
+ signal_to_string(env_source->main_exec_status.status));
+ if (r < 0)
+ return r;
+ }
+
+ if (!sd_id128_is_null(UNIT(env_source)->invocation_id)) {
+ r = strextendf(&var, ",INVOCATION_ID=" SD_ID128_FORMAT_STR,
+ SD_ID128_FORMAT_VAL(UNIT(env_source)->invocation_id));
+ if (r < 0)
+ return r;
+ }
+
+ r = strextendf(&var, ",UNIT=%s", UNIT(env_source)->id);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(var);
+ return 0;
+}
+
static int service_spawn(
Service *s,
ExecCommand *c,
@@ -1574,9 +1661,18 @@ static int service_spawn(
r = asprintf(our_env + n_env++, "EXIT_STATUS=%i", s->main_exec_status.status);
else
r = asprintf(our_env + n_env++, "EXIT_STATUS=%s", signal_to_string(s->main_exec_status.status));
+
if (r < 0)
return -ENOMEM;
}
+
+ } else if (flags & EXEC_SETENV_MONITOR_RESULT) {
+ Job *j = UNIT(s)->job;
+ if (j) {
+ r = service_create_monitor_md_env(j, our_env + n_env++);
+ if (r < 0)
+ return r;
+ }
}
r = unit_set_exec_params(UNIT(s), &exec_params);
@@ -2164,7 +2260,7 @@ static void service_enter_start(Service *s) {
r = service_spawn(s,
c,
timeout,
- EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_WRITE_CREDENTIALS,
+ EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_WRITE_CREDENTIALS|EXEC_SETENV_MONITOR_RESULT,
&pid);
if (r < 0)
goto fail;
@@ -2222,7 +2318,7 @@ static void service_enter_start_pre(Service *s) {
r = service_spawn(s,
s->control_command,
s->timeout_start_usec,
- EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN,
+ EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN|EXEC_SETENV_MONITOR_RESULT,
&s->control_pid);
if (r < 0)
goto fail;
diff --git a/src/core/unit-dependency-atom.c b/src/core/unit-dependency-atom.c
index 761835828a..333eea6c3d 100644
--- a/src/core/unit-dependency-atom.c
+++ b/src/core/unit-dependency-atom.c
@@ -82,11 +82,15 @@ static const UnitDependencyAtom atom_map[_UNIT_DEPENDENCY_MAX] = {
[UNIT_PROPAGATES_STOP_TO] = UNIT_ATOM_RETROACTIVE_STOP_ON_STOP |
UNIT_ATOM_PROPAGATE_STOP,
+ [UNIT_ON_FAILURE] = UNIT_ATOM_ON_FAILURE |
+ UNIT_ATOM_BACK_REFERENCE_IMPLIED,
+
+ [UNIT_ON_SUCCESS] = UNIT_ATOM_ON_SUCCESS |
+ UNIT_ATOM_BACK_REFERENCE_IMPLIED,
+
/* 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,
[UNIT_PROPAGATES_RELOAD_TO] = UNIT_ATOM_PROPAGATES_RELOAD_TO,
@@ -196,6 +200,16 @@ UnitDependency unit_dependency_from_unique_atom(UnitDependencyAtom atom) {
case UNIT_ATOM_PROPAGATE_STOP_FAILURE:
return UNIT_CONFLICTED_BY;
+ case UNIT_ATOM_ON_FAILURE |
+ UNIT_ATOM_BACK_REFERENCE_IMPLIED:
+ case UNIT_ATOM_ON_FAILURE:
+ return UNIT_ON_FAILURE;
+
+ case UNIT_ATOM_ON_SUCCESS |
+ UNIT_ATOM_BACK_REFERENCE_IMPLIED:
+ case UNIT_ATOM_ON_SUCCESS:
+ return UNIT_ON_SUCCESS;
+
/* And now, the simple ones */
case UNIT_ATOM_BEFORE:
@@ -204,12 +218,6 @@ 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;
-
case UNIT_ATOM_TRIGGERS:
return UNIT_TRIGGERS;
diff --git a/src/core/unit-dependency-atom.h b/src/core/unit-dependency-atom.h
index c5f8f5d400..1cf9795786 100644
--- a/src/core/unit-dependency-atom.h
+++ b/src/core/unit-dependency-atom.h
@@ -66,20 +66,27 @@ typedef enum UnitDependencyAtom {
/* Recheck default target deps on other units (which are target units) */
UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES = UINT64_C(1) << 21,
+ /* Dependencies which include this atom automatically get a reverse
+ * REFERENCES/REFERENCED_BY dependency. */
+ UNIT_ATOM_BACK_REFERENCE_IMPLIED = UINT64_C(1) << 22,
+
+ /* Trigger a dependency on successful service exit. */
+ UNIT_ATOM_ON_SUCCESS = UINT64_C(1) << 23,
+ /* Trigger a dependency on unsuccessful service exit. */
+ UNIT_ATOM_ON_FAILURE = UINT64_C(1) << 24,
+
/* 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,
- UNIT_ATOM_PROPAGATES_RELOAD_TO = UINT64_C(1) << 28,
- UNIT_ATOM_JOINS_NAMESPACE_OF = UINT64_C(1) << 29,
- UNIT_ATOM_REFERENCES = UINT64_C(1) << 30,
- UNIT_ATOM_REFERENCED_BY = UINT64_C(1) << 31,
- UNIT_ATOM_IN_SLICE = UINT64_C(1) << 32,
- UNIT_ATOM_SLICE_OF = UINT64_C(1) << 33,
- _UNIT_DEPENDENCY_ATOM_MAX = (UINT64_C(1) << 34) - 1,
+ UNIT_ATOM_BEFORE = UINT64_C(1) << 25,
+ UNIT_ATOM_AFTER = UINT64_C(1) << 26,
+ UNIT_ATOM_TRIGGERS = UINT64_C(1) << 27,
+ UNIT_ATOM_TRIGGERED_BY = UINT64_C(1) << 28,
+ UNIT_ATOM_PROPAGATES_RELOAD_TO = UINT64_C(1) << 29,
+ UNIT_ATOM_JOINS_NAMESPACE_OF = UINT64_C(1) << 30,
+ UNIT_ATOM_REFERENCES = UINT64_C(1) << 31,
+ UNIT_ATOM_REFERENCED_BY = UINT64_C(1) << 32,
+ UNIT_ATOM_IN_SLICE = UINT64_C(1) << 33,
+ UNIT_ATOM_SLICE_OF = UINT64_C(1) << 34,
+ _UNIT_DEPENDENCY_ATOM_MAX = (UINT64_C(1) << 35) - 1,
_UNIT_DEPENDENCY_ATOM_INVALID = -EINVAL,
} UnitDependencyAtom;
diff --git a/src/core/unit.c b/src/core/unit.c
index b1f1f5c82c..046e376b8a 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -2222,17 +2222,24 @@ void unit_start_on_failure(
UNIT_FOREACH_DEPENDENCY(other, u, atom) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ Job *job = NULL;
if (!logged) {
log_unit_info(u, "Triggering %s dependencies.", dependency_name);
logged = true;
}
- r = manager_add_job(u->manager, JOB_START, other, job_mode, NULL, &error, NULL);
+ r = manager_add_job(u->manager, JOB_START, other, job_mode, NULL, &error, &job);
if (r < 0)
log_unit_warning_errno(
u, r, "Failed to enqueue %s job, ignoring: %s",
dependency_name, bus_error_message(&error, r));
+ else if (job)
+ /* u will be kept pinned since both UNIT_ON_FAILURE and UNIT_ON_SUCCESS includes
+ * UNIT_ATOM_BACK_REFERENCE_IMPLIED. We save the triggering unit here since we
+ * want to be able to reference it when we come to run the OnFailure= or OnSuccess=
+ * dependency. */
+ job_add_triggering_unit(job, u);
}
if (logged)
@@ -3114,6 +3121,20 @@ int unit_add_dependency(
noop = false;
}
+ if (FLAGS_SET(a, UNIT_ATOM_BACK_REFERENCE_IMPLIED)) {
+ r = unit_add_dependency_hashmap(&other->dependencies, UNIT_REFERENCES, u, 0, mask);
+ if (r < 0)
+ return r;
+ if (r)
+ noop = false;
+
+ r = unit_add_dependency_hashmap(&u->dependencies, UNIT_REFERENCED_BY, other, 0, mask);
+ if (r < 0)
+ return r;
+ if (r)
+ noop = false;
+ }
+
if (add_reference) {
r = unit_add_dependency_hashmap(&u->dependencies, UNIT_REFERENCES, other, mask, 0);
if (r < 0)
diff --git a/src/core/unit.h b/src/core/unit.h
index 94f2180951..786c15d623 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -242,6 +242,9 @@ typedef struct Unit {
/* Queue of units that have a BindTo= dependency on some other unit, and should possibly be shut down */
LIST_FIELDS(Unit, stop_when_bound_queue);
+ /* Queue of units which have triggered an OnFailure= or OnSuccess= dependency job. */
+ LIST_FIELDS(Unit, triggered_by);
+
/* PIDs we keep an eye on. Note that a unit might have many
* more, but these are the ones we care enough about to
* process SIGCHLD for */
diff --git a/test/TEST-68-PROPAGATE-EXIT-STATUS/Makefile b/test/TEST-68-PROPAGATE-EXIT-STATUS/Makefile
new file mode 120000
index 0000000000..e9f93b1104
--- /dev/null
+++ b/test/TEST-68-PROPAGATE-EXIT-STATUS/Makefile
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile \ No newline at end of file
diff --git a/test/TEST-68-PROPAGATE-EXIT-STATUS/test.sh b/test/TEST-68-PROPAGATE-EXIT-STATUS/test.sh
new file mode 100755
index 0000000000..eecd56959f
--- /dev/null
+++ b/test/TEST-68-PROPAGATE-EXIT-STATUS/test.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+
+TEST_DESCRIPTION="Test propagation of exit status to On{Failure,Success}= dependencies"
+TEST_NO_QEMU=1
+
+# shellcheck source=test/test-functions
+. "$TEST_BASE_DIR/test-functions"
+
+do_test "$@"
diff --git a/test/units/testsuite-68.service b/test/units/testsuite-68.service
new file mode 100644
index 0000000000..2d86e1fedc
--- /dev/null
+++ b/test/units/testsuite-68.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS
+
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
diff --git a/test/units/testsuite-68.sh b/test/units/testsuite-68.sh
new file mode 100755
index 0000000000..1ac244bc5f
--- /dev/null
+++ b/test/units/testsuite-68.sh
@@ -0,0 +1,230 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# Wait for a service to enter a state within a timeout period, if it doesn't
+# enter the desired state within the timeout period then this function will
+# exit the test case with a non zero exit code.
+wait_on_state_or_fail () {
+ service=$1
+ expected_state=$2
+ timeout=$3
+
+ state=$(systemctl show "$service" --property=ActiveState --value)
+ while [ "$state" != "$expected_state" ]; do
+ if [ "$timeout" = "0" ]; then
+ systemd-analyze log-level info
+ exit 1
+ fi
+ timeout=$((timeout - 1))
+ sleep 1
+ state=$(systemctl show "$service" --property=ActiveState --value)
+ done
+}
+
+systemd-analyze log-level debug
+systemd-analyze log-target console
+
+# Trigger testservice-failure-exit-handler-68.service
+cat >/run/systemd/system/testservice-failure-68.service <<EOF
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS with OnFailure= trigger
+OnFailure=testservice-failure-exit-handler-68.service
+
+[Service]
+ExecStart=/bin/bash -c "exit 1"
+EOF
+
+# Another service which triggers testservice-failure-exit-handler-68.service
+cat >/run/systemd/system/testservice-failure-68-additional.service <<EOF
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS Additonal service with OnFailure= trigger
+OnFailure=testservice-failure-exit-handler-68.service
+
+[Service]
+ExecStart=/bin/bash -c "exit 1"
+EOF
+
+# Trigger testservice-success-exit-handler-68.service
+cat >/run/systemd/system/testservice-success-68.service <<EOF
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS with OnSuccess= trigger
+OnSuccess=testservice-success-exit-handler-68.service
+
+[Service]
+ExecStart=/bin/bash -c "exit 0"
+EOF
+
+# Trigger testservice-success-exit-handler-68.service
+cat >/run/systemd/system/testservice-success-68-additional.service <<EOF
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS Addition service with OnSuccess= trigger
+OnSuccess=testservice-success-exit-handler-68.service
+
+[Service]
+ExecStart=/bin/bash -c "exit 0"
+EOF
+
+# Script to check that when an OnSuccess= dependency fires, the correct
+# MONITOR* env variables are passed. This script handles the case where
+# multiple services triggered the unit that calls this script. In this
+# case we need to check the MONITOR_METADATA variable for >= 1 service
+# details since jobs may merge.
+cat >/tmp/check_on_success.sh <<EOF
+#!/usr/bin/env bash
+
+set -ex
+
+echo "MONITOR_METADATA=\$MONITOR_METADATA"
+
+IFS=';' read -ra ALL_SERVICE_MD <<< "\$MONITOR_METADATA"
+for SERVICE_MD in "\${ALL_SERVICE_MD[@]}"; do
+ IFS=',' read -ra METADATA <<< "\$SERVICE_MD"
+ IFS='=' read -ra SERVICE_RESULT <<< "\${METADATA[0]}"
+ SERVICE_RESULT=\${SERVICE_RESULT[1]}
+ IFS='=' read -ra EXIT_CODE <<< "\${METADATA[1]}"
+ EXIT_CODE=\${EXIT_CODE[1]}
+ IFS='=' read -ra EXIT_STATUS <<< "\${METADATA[2]}"
+ EXIT_STATUS=\${EXIT_STATUS[1]}
+ IFS='=' read -ra INVOCATION_ID <<< "\${METADATA[3]}"
+ INVOCATION_ID=\${INVOCATION_ID[1]}
+ IFS='=' read -ra UNIT <<< "\${METADATA[4]}"
+ UNIT=\${UNIT[1]}
+
+ if [ "\$SERVICE_RESULT" != "success" ]; then
+ echo 'SERVICE_RESULT was "\$SERVICE_RESULT", expected "success"';
+ exit 1;
+ fi
+
+ if [ "\$EXIT_CODE" != "exited" ]; then
+ echo 'EXIT_CODE was "\$EXIT_CODE", expected "exited"';
+ exit 1;
+ fi
+
+ if [ "\$EXIT_STATUS" != "0" ]; then
+ echo 'EXIT_STATUS was "\$EXIT_STATUS", expected "0"';
+ exit 1;
+ fi
+
+ if [ -z "\$INVOCATION_ID" ]; then
+ echo 'INVOCATION_ID unset';
+ exit 1;
+ fi
+
+ if [[ "\$UNIT" != "testservice-success-68.service" && "\$UNIT" != "testservice-success-68-additional.service" && "\$UNIT" != "testservice-transient-success-68.service" ]]; then
+ echo 'UNIT was "\$UNIT", expected "testservice-success-68{-additional,-transient}.service"';
+ exit 1;
+ fi
+done
+
+exit 0;
+EOF
+chmod +x /tmp/check_on_success.sh
+
+# Handle testservice-failure-exit-handler-68.service exiting with success.
+cat >/run/systemd/system/testservice-success-exit-handler-68.service <<EOF
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS handle service exiting in success
+
+[Service]
+ExecStartPre=/tmp/check_on_success.sh
+ExecStart=/tmp/check_on_success.sh
+EOF
+
+# Script to check that when an OnFailure= dependency fires, the correct
+# MONITOR* env variables are passed. This script handles the case where
+# multiple services triggered the unit that calls this script. In this
+# case we need to check the MONITOR_METADATA variable for >=1 service
+# details since jobs may merge.
+cat >/tmp/check_on_failure.sh <<EOF
+#!/usr/bin/env bash
+
+set -ex
+
+echo "MONITOR_METADATA=\$MONITOR_METADATA"
+
+IFS=';' read -ra ALL_SERVICE_MD <<< "\$MONITOR_METADATA"
+for SERVICE_MD in "\${ALL_SERVICE_MD[@]}"; do
+ IFS=',' read -ra METADATA <<< "\$SERVICE_MD"
+ IFS='=' read -ra SERVICE_RESULT <<< "\${METADATA[0]}"
+ SERVICE_RESULT=\${SERVICE_RESULT[1]}
+ IFS='=' read -ra EXIT_CODE <<< "\${METADATA[1]}"
+ EXIT_CODE=\${EXIT_CODE[1]}
+ IFS='=' read -ra EXIT_STATUS <<< "\${METADATA[2]}"
+ EXIT_STATUS=\${EXIT_STATUS[1]}
+ IFS='=' read -ra INVOCATION_ID <<< "\${METADATA[3]}"
+ INVOCATION_ID=\${INVOCATION_ID[1]}
+ IFS='=' read -ra UNIT <<< "\${METADATA[4]}"
+ UNIT=\${UNIT[1]}
+
+ if [ "\$SERVICE_RESULT" != "exit-code" ]; then
+ echo 'SERVICE_RESULT was "\$SERVICE_RESULT", expected "exit-code"';
+ exit 1;
+ fi
+
+ if [ "\$EXIT_CODE" != "exited" ]; then
+ echo 'EXIT_CODE was "\$EXIT_CODE", expected "exited"';
+ exit 1;
+ fi
+
+ if [ "\$EXIT_STATUS" != "1" ]; then
+ echo 'EXIT_STATUS was "\$EXIT_STATUS", expected "1"';
+ exit 1;
+ fi
+
+ if [ -z "\$INVOCATION_ID" ]; then
+ echo 'INVOCATION_ID unset';
+ exit 1;
+ fi
+
+ if [[ "\$UNIT" != "testservice-failure-68.service" && "\$UNIT" != "testservice-failure-68-additional.service" && "\$UNIT" != "testservice-transient-failure-68.service" ]]; then
+ echo 'UNIT was "\$UNIT", expected "testservice-failure-68{-additional,-transient}.service"';
+ exit 1;
+ fi
+done
+
+exit 0;
+EOF
+chmod +x /tmp/check_on_failure.sh
+
+
+# Handle testservice-failure-exit-handler-68.service exiting with failure.
+cat >/run/systemd/system/testservice-failure-exit-handler-68.service <<EOF
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS handle service exiting in failure
+
+[Service]
+ExecStartPre=/tmp/check_on_failure.sh
+ExecStart=/tmp/check_on_failure.sh
+EOF
+
+systemctl daemon-reload
+
+# The running of the OnFailure= and OnSuccess= jobs for all of these services
+# may result in jobs being merged.
+systemctl start testservice-failure-68.service
+wait_on_state_or_fail "testservice-failure-exit-handler-68.service" "inactive" "10"
+systemctl start testservice-failure-68-additional.service
+wait_on_state_or_fail "testservice-failure-exit-handler-68.service" "inactive" "10"
+systemctl start testservice-success-68.service
+wait_on_state_or_fail "testservice-success-exit-handler-68.service" "inactive" "10"
+systemctl start testservice-success-68-additional.service
+wait_on_state_or_fail "testservice-success-exit-handler-68.service" "inactive" "10"
+
+# Test some transient units since these exit very quickly.
+systemd-run --unit=testservice-transient-success-68 --property=OnSuccess=testservice-success-exit-handler-68.service /bin/bash -c "exit 0;"
+wait_on_state_or_fail "testservice-success-exit-handler-68.service" "inactive" "10"
+systemd-run --unit=testservice-transient-failure-68 --property=OnFailure=testservice-failure-exit-handler-68.service /bin/bash -c "exit 1;"
+wait_on_state_or_fail "testservice-failure-exit-handler-68.service" "inactive" "10"
+
+# These yield a higher chance of resulting in jobs merging.
+systemctl start testservice-failure-68.service testservice-failure-68-additional.service --no-block
+wait_on_state_or_fail "testservice-failure-exit-handler-68.service" "inactive" "10"
+systemctl start testservice-success-68.service testservice-success-68-additional.service --no-block
+wait_on_state_or_fail "testservice-success-exit-handler-68.service" "inactive" "10"
+
+systemd-analyze log-level info
+echo OK >/testok
+
+exit 0