From: Peter Morrow Date: Thu, 23 Sep 2021 15:54:32 +0000 (+0100) Subject: service: pass service exit status to spawned On{Failure,Success}= dependency X-Git-Tag: v251-rc1~648^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cdebedb4d40277aad62a7734ba920b4033228197;p=thirdparty%2Fsystemd.git service: pass service exit status to spawned On{Failure,Success}= dependency When a service exits and triggers either an OnFailure= or OnSuccess= dependency we now set a new environment variable for the ExecStart= and ExecStartPre= process. This variable $MONITOR_METADATA exposes the metadata relating to the service which triggered the dependency. MONITOR_METADATA takes the following form: MONITOR_METADATA="SERVICE_RESULT=,EXIT_CODE=,EXIT_STATUS=,INVOCATION_ID=,UNIT=;SERVICE_RESULT=,EXIT_CODE=,=EXIT_STATUS=,INVOCATION_ID=,UNIT=" MONITOR_METADATA is space separated set of metadata relating to the service(s) which triggered the dependency. This is a list since if we have 2 services which trigger the same dependency then the dependency start job may be merged. In this case we need to pass both service metadata to the triggered service. If there is no job merging then MONITOR_METADATA will be a single entry. For example, in the case we had a service "failer.service" which triggers "failer-handler.service", the following variable is exported to the ExecStart= and ExecStartPre= processes in failer-handler.service: MONITOR_METADATA="SERVICE_RESULT=exit-code,EXIT_CODE=exited,EXIT_STATUS=1,INVOCATION_ID=67c657ed7b34466ea369abdf994c6393,UNIT=failer.service" In another example where we have failer.service and failer2.service which both also trigger failer-handler.service then the start job for failer-handler.service may be merged and we might get the following: MONITOR_METADATA="SERVICE_RESULT=exit-code,EXIT_CODE=exited,EXIT_STATUS=1,INVOCATION_ID=16a93ad196c94109990fb8b9aa5eef5f,UNIT=failer.service;SERVICE_RESULT=exit-code,EXIT_CODE=exited,EXIT_STATUS=1,INVOCATION_ID=ff70131e4cc145e994fb621de25a3e8f,UNIT=failer2.service" --- 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 a599e393a8c..357242e434c 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 76701519c2c..fa420bbf516 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 */