]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
service: pass service exit status to spawned On{Failure,Success}= dependency
authorPeter Morrow <pemorrow@linux.microsoft.com>
Thu, 23 Sep 2021 15:54:32 +0000 (16:54 +0100)
committerPeter Morrow <pemorrow@linux.microsoft.com>
Mon, 13 Dec 2021 11:25:57 +0000 (11:25 +0000)
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"

src/core/execute.h
src/core/job.c
src/core/job.h
src/core/service.c
src/core/unit-dependency-atom.c
src/core/unit-dependency-atom.h
src/core/unit.c
src/core/unit.h

index b0da375def71d40a6adfaee1e4d5862ac9a43717..805e9b476537a2c30a35b09155048098fe390379 100644 (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
index 94ab381626475d7a0519290dbda93903f0c4c353..67c3c8fdd240ecda49bed9c97d891d80cca1d3ff 100644 (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) {
index a66e5985b8520d3cdea0cec1332c965cd7d4c713..762b0bb19bc395ef8b064ae5a7b62e1f296e1792 100644 (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);
index 49579f799858fdedfcc4d0ab20cc5baa30fe5ab0..b8430eae96550cdd7c3c87e6c8c5281e4e73491d 100644 (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;
index 761835828a7b4c09021d1204a715d9b007a03cde..333eea6c3d368d037594059b340112d6b69b6078 100644 (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;
 
index c5f8f5d40036303b889f0d7b55aade92d5edc33f..1cf97957869a662244c55949b40ae6a7a5b9b46d 100644 (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;
 
index a599e393a8c0602c4e0c337892ced43a93fe6b2b..357242e434cde2ec1671b9a51b1f55e9f3390047 100644 (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)
index 76701519c2c7ffcfaf345cbb6552f7fc9bc16a80..fa420bbf516d19783ba6371b9265f6a02ea7c368 100644 (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 */