1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-31 14:50:15 +03:00

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=<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>"

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"
This commit is contained in:
Peter Morrow 2021-09-23 16:54:32 +01:00
parent a0630d46a5
commit cdebedb4d4
8 changed files with 193 additions and 37 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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 */