diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index cd21d5b28d0..3378689b759 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3546,6 +3546,41 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy + + $MONITOR_METADATA + + Only defined for the service unit type, this environment variable is passed to all + ExecStart= and ExecStartPre= processes which run in services + triggered by OnFailure= or OnSuccess= dependencies. + + + 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 OnFailure= or OnSuccess= + dependency the following fields will be set: + + + + SERVICE_RESULT + EXIT_CODE + EXIT_STATUS + INVOCATION_ID + UNIT + + + The fields SERVICE_RESULT, EXIT_CODE and + EXIT_STATUS may take the same values that are allowed when set for + ExecStop= and ExecStopPost= processes. The fields + INVOCATION_ID and UNIT are the invocaton id and unit + name of the service which triggered the dependency. Each field is comma separated, i.e. + + +SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=triggering.service + + + + + + $PIDFILE @@ -3983,6 +4018,77 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy + + Examples + + + <varname>$MONITOR_METADATA</varname> usage + + A service myfailer.service which can trigger an + OnFailure= dependency. + + +[Unit] +Description=Service which can trigger an OnFailure= dependency +OnFailure=myhandler.service + +[Service] +ExecStart=/bin/myprogram + + + A service mysuccess.service which can trigger an + OnSuccess= dependency. + + +[Unit] +Description=Service which can trigger an OnSuccess= dependency +OnSuccess=myhandler.service + +[Service] +ExecStart=/bin/mysecondprogram + + + A service myhandler.service which can be triggered + by any of the above services. + + +[Unit] +Description=Acts on service failing or succeeding + +[Service] +ExecStart=/bin/bash -c "echo $MONITOR_METADATA" + + + If myfailer.service were to run and exit in failure, + then myhandler.service would be triggered and the + $MONITOR_METADATA variable would be set as follows: + + +MONITOR_METADATA=SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=myfailer.service + + + If mysuccess.service were to run and exit in success, + then myhandler.service would be triggered and the + $MONITOR_METADATA variable would be set as follows: + + +MONITOR_METADATA=SERVICE_RESULT=result-string,EXIT_CODE=exit-code,EXIT_STATUS=exit-status,INVOCATION_ID=invocation-id,UNIT=mysuccess.service + + + If myfailer.service and mysuccess.service were to run and exit, + there is a chance that the triggered dependency start job might be merged. Thus only a single invocation of + myhandler.service would be triggered. In this case the $MONITOR_METADATA variable + would be a list containing exit metadata for both of myfailer.service + and mysuccess.service. + + +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 + + + + + + See Also diff --git a/src/core/execute.h b/src/core/execute.h index b0da375def7..805e9b47653 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 94ab3816264..67c3c8fdd24 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 a66e5985b85..762b0bb19bc 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 49579f79985..b8430eae965 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=,EXIT_CODE=,EXIT_STATUS=, + * INVOCATION_ID=,UNIT=; + * SERVICE_RESULT=,EXIT_CODE=,EXIT_STATUS=, + * INVOCATION_ID=,UNIT=" + * + * 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 761835828a7..333eea6c3d3 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 c5f8f5d4003..1cf97957869 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 b1f1f5c82cf..046e376b8a6 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 94f2180951c..786c15d6233 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 00000000000..e9f93b1104c --- /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 00000000000..eecd56959f2 --- /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 00000000000..2d86e1fedcb --- /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 00000000000..1ac244bc5f3 --- /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 </run/systemd/system/testservice-failure-68-additional.service </run/systemd/system/testservice-success-68.service </run/systemd/system/testservice-success-68-additional.service <= 1 service +# details since jobs may merge. +cat >/tmp/check_on_success.sh </run/systemd/system/testservice-success-exit-handler-68.service <=1 service +# details since jobs may merge. +cat >/tmp/check_on_failure.sh </run/systemd/system/testservice-failure-exit-handler-68.service </testok + +exit 0