diff options
-rw-r--r-- | man/systemd.exec.xml | 106 | ||||
-rw-r--r-- | src/core/execute.h | 27 | ||||
-rw-r--r-- | src/core/job.c | 16 | ||||
-rw-r--r-- | src/core/job.h | 4 | ||||
-rw-r--r-- | src/core/service.c | 100 | ||||
-rw-r--r-- | src/core/unit-dependency-atom.c | 24 | ||||
-rw-r--r-- | src/core/unit-dependency-atom.h | 33 | ||||
-rw-r--r-- | src/core/unit.c | 23 | ||||
-rw-r--r-- | src/core/unit.h | 3 | ||||
l--------- | test/TEST-68-PROPAGATE-EXIT-STATUS/Makefile | 1 | ||||
-rwxr-xr-x | test/TEST-68-PROPAGATE-EXIT-STATUS/test.sh | 11 | ||||
-rw-r--r-- | test/units/testsuite-68.service | 7 | ||||
-rwxr-xr-x | test/units/testsuite-68.sh | 230 |
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 |