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"
}
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
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);
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);
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) {
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;
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);
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,
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);
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;
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;
[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,
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:
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;
/* 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;
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)
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)
/* 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 */