]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
service: set TRIGGER_UNIT= and TRIGGER_PATH= on activation by path unit
authorLuca Boccassi <bluca@debian.org>
Tue, 2 Aug 2022 19:07:35 +0000 (20:07 +0100)
committerLuca Boccassi <bluca@debian.org>
Tue, 23 Aug 2022 19:38:08 +0000 (20:38 +0100)
When a service is triggered by a path unit, pass the
path unit name and the path that triggered it via env vars
to the spawned processes.
Note that this is best-effort, as there might be many triggers
at the same time, but we only get woken up by one.

man/systemd.exec.xml
man/systemd.path.xml
src/core/path.c
src/core/path.h
src/core/unit.c
test/testsuite-63.units/test63-glob.path [new file with mode: 0644]
test/testsuite-63.units/test63-glob.service [new file with mode: 0644]
test/testsuite-63.units/test63.service
test/units/testsuite-63.sh

index d9da864bc21d167badd25a641ed3e0d894580d7d..2acb737c38f5f6e71ef920dbda048589f5f191ee 100644 (file)
@@ -3695,6 +3695,19 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
           system.</para></listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>$TRIGGER_UNIT</varname></term>
+          <term><varname>$TRIGGER_PATH</varname></term>
+
+          <listitem><para>If the unit was activated dynamically (e.g.: a corresponding path unit), the
+          unit that triggered it and other type-dependent information will be passed via these variables. Note that
+          this information is provided in a best-effort way. For example, multiple triggers happening one after
+          another will be coalesced and only one will be reported, with no guarantee as to which one it will be.
+          Because of this, in most cases this variable will be primarily informational, i.e. useful for debugging
+          purposes, is lossy, and should not be relied upon to propagate a comprehensive reason for activation.
+          </para></listitem>
+        </varlistentry>
+
       </variablelist>
 
       <para>For system services, when <varname>PAMName=</varname> is enabled and <command>pam_systemd</command> is part
index 4e4cd9137a6cc098487ddcb0aa17c3499c10c713..f143208cb465b05e57c14551f9a4ccaad709f645 100644 (file)
 
   <refsect1>
       <title>See Also</title>
+      <para>Environment variables with details on the trigger will be set for triggered units. See the
+      <literal>Environment Variables Set on Triggered Units</literal> section in
+      <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+      for more details.</para>
       <para>
         <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
         <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
index 69bbddf1585cba8f310020517bace9aa4882abe1..2810e30573d826d9c8b96e606f596a4407e8b908 100644 (file)
@@ -197,9 +197,13 @@ int path_spec_fd_event(PathSpec *s, uint32_t revents) {
         return 0;
 }
 
-static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_notify) {
+static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_notify, char **ret_trigger_path) {
+        _cleanup_free_ char *trigger = NULL;
         bool b, good = false;
 
+        assert(s);
+        assert(ret_trigger_path);
+
         switch (s->type) {
 
         case PATH_EXISTS:
@@ -207,7 +211,7 @@ static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_no
                 break;
 
         case PATH_EXISTS_GLOB:
-                good = glob_exists(s->path) > 0;
+                good = glob_first(s->path, &trigger) > 0;
                 break;
 
         case PATH_DIRECTORY_NOT_EMPTY: {
@@ -229,6 +233,15 @@ static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_no
                 ;
         }
 
+        if (good) {
+                if (!trigger) {
+                        trigger = strdup(s->path);
+                        if (!trigger)
+                                (void) log_oom_debug();
+                }
+                *ret_trigger_path = TAKE_PTR(trigger);
+        }
+
         return good;
 }
 
@@ -494,9 +507,11 @@ static void path_enter_dead(Path *p, PathResult f) {
         path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
 }
 
-static void path_enter_running(Path *p) {
+static void path_enter_running(Path *p, char *trigger_path) {
+        _cleanup_(activation_details_unrefp) ActivationDetails *details = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         Unit *trigger;
+        Job *job;
         int r;
 
         assert(p);
@@ -518,10 +533,22 @@ static void path_enter_running(Path *p) {
                 return;
         }
 
-        r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, NULL);
+        details = activation_details_new(UNIT(p));
+        if (!details) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        r = free_and_strdup(&(ACTIVATION_DETAILS_PATH(details))->trigger_path_filename, trigger_path);
         if (r < 0)
                 goto fail;
 
+        r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, &job);
+        if (r < 0)
+                goto fail;
+
+        job_set_activation_details(job, details);
+
         path_set_state(p, PATH_RUNNING);
         path_unwatch(p);
 
@@ -532,17 +559,19 @@ fail:
         path_enter_dead(p, PATH_FAILURE_RESOURCES);
 }
 
-static bool path_check_good(Path *p, bool initial, bool from_trigger_notify) {
+static bool path_check_good(Path *p, bool initial, bool from_trigger_notify, char **ret_trigger_path) {
         assert(p);
+        assert(ret_trigger_path);
 
         LIST_FOREACH(spec, s, p->specs)
-                if (path_spec_check_good(s, initial, from_trigger_notify))
+                if (path_spec_check_good(s, initial, from_trigger_notify, ret_trigger_path))
                         return true;
 
         return false;
 }
 
 static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify) {
+        _cleanup_free_ char *trigger_path = NULL;
         Unit *trigger;
         int r;
 
@@ -554,9 +583,9 @@ static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify)
                 return;
         }
 
-        if (path_check_good(p, initial, from_trigger_notify)) {
+        if (path_check_good(p, initial, from_trigger_notify, &trigger_path)) {
                 log_unit_debug(UNIT(p), "Got triggered.");
-                path_enter_running(p);
+                path_enter_running(p, trigger_path);
                 return;
         }
 
@@ -568,9 +597,9 @@ static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify)
          * might have appeared/been removed by now, so we must
          * recheck */
 
-        if (path_check_good(p, false, from_trigger_notify)) {
+        if (path_check_good(p, false, from_trigger_notify, &trigger_path)) {
                 log_unit_debug(UNIT(p), "Got triggered.");
-                path_enter_running(p);
+                path_enter_running(p, trigger_path);
                 return;
         }
 
@@ -759,7 +788,7 @@ static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, v
                 goto fail;
 
         if (changed)
-                path_enter_running(p);
+                path_enter_running(p, found->path);
         else
                 path_enter_waiting(p, false, false);
 
@@ -832,6 +861,89 @@ static int path_can_start(Unit *u) {
         return 1;
 }
 
+static void activation_details_path_done(ActivationDetails *details) {
+        ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
+
+        p->trigger_path_filename = mfree(p->trigger_path_filename);
+}
+
+static void activation_details_path_serialize(ActivationDetails *details, FILE *f) {
+        ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
+
+        assert(f);
+
+        if (p->trigger_path_filename)
+                (void) serialize_item(f, "activation-details-path-filename", p->trigger_path_filename);
+}
+
+static int activation_details_path_deserialize(const char *key, const char *value, ActivationDetails **details) {
+        int r;
+
+        assert(key);
+        assert(value);
+
+        if (!details || !*details)
+                return -EINVAL;
+
+        ActivationDetailsPath *p = ACTIVATION_DETAILS_PATH(*details);
+        if (!p)
+                return -EINVAL;
+
+        if (!streq(key, "activation-details-path-filename"))
+                return -EINVAL;
+
+        r = free_and_strdup(&p->trigger_path_filename, value);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int activation_details_path_append_env(ActivationDetails *details, char ***strv) {
+        ActivationDetailsPath *p = ACTIVATION_DETAILS_PATH(details);
+        char *s;
+        int r;
+
+        assert(details);
+        assert(strv);
+        assert(p);
+
+        if (isempty(p->trigger_path_filename))
+                return 0;
+
+        s = strjoin("TRIGGER_PATH=", p->trigger_path_filename);
+        if (!s)
+                return -ENOMEM;
+
+        r = strv_consume(strv, TAKE_PTR(s));
+        if (r < 0)
+                return r;
+
+        return 1; /* Return the number of variables added to the env block */
+}
+
+static int activation_details_path_append_pair(ActivationDetails *details, char ***strv) {
+        ActivationDetailsPath *p = ACTIVATION_DETAILS_PATH(details);
+        int r;
+
+        assert(details);
+        assert(strv);
+        assert(p);
+
+        if (isempty(p->trigger_path_filename))
+                return 0;
+
+        r = strv_extend(strv, "trigger_path");
+        if (r < 0)
+                return r;
+
+        r = strv_extend(strv, p->trigger_path_filename);
+        if (r < 0)
+                return r;
+
+        return 1; /* Return the number of pairs added to the env block */
+}
+
 static const char* const path_type_table[_PATH_TYPE_MAX] = {
         [PATH_EXISTS]              = "PathExists",
         [PATH_EXISTS_GLOB]         = "PathExistsGlob",
@@ -890,3 +1002,13 @@ const UnitVTable path_vtable = {
 
         .can_start = path_can_start,
 };
+
+const ActivationDetailsVTable activation_details_path_vtable = {
+        .object_size = sizeof(ActivationDetailsPath),
+
+        .done = activation_details_path_done,
+        .serialize = activation_details_path_serialize,
+        .deserialize = activation_details_path_deserialize,
+        .append_env = activation_details_path_append_env,
+        .append_pair = activation_details_path_append_pair,
+};
index d835c241660b530974864c35340ce4b552e57532..c76103cc12944d1ece2896f000d3e3fd79595e7e 100644 (file)
@@ -3,6 +3,7 @@
 
 typedef struct Path Path;
 typedef struct PathSpec PathSpec;
+typedef struct ActivationDetailsPath ActivationDetailsPath;
 
 #include "unit.h"
 
@@ -66,9 +67,15 @@ struct Path {
         RateLimit trigger_limit;
 };
 
+struct ActivationDetailsPath {
+        ActivationDetails meta;
+        char *trigger_path_filename;
+};
+
 void path_free_specs(Path *p);
 
 extern const UnitVTable path_vtable;
+extern const ActivationDetailsVTable activation_details_path_vtable;
 
 const char* path_type_to_string(PathType i) _const_;
 PathType path_type_from_string(const char *s) _pure_;
@@ -77,3 +84,4 @@ const char* path_result_to_string(PathResult i) _const_;
 PathResult path_result_from_string(const char *s) _pure_;
 
 DEFINE_CAST(PATH, Path);
+DEFINE_ACTIVATION_DETAILS_CAST(ACTIVATION_DETAILS_PATH, ActivationDetailsPath, PATH);
index 18d9ba85de3e60486e9c23c3cfdbf608d87288e2..4255e0cf1f43659b50534daeb11e9153f5c42474 100644 (file)
@@ -5930,6 +5930,7 @@ int unit_get_dependency_array(const Unit *u, UnitDependencyAtom atom, Unit ***re
 }
 
 const ActivationDetailsVTable * const activation_details_vtable[_UNIT_TYPE_MAX] = {
+        [UNIT_PATH] = &activation_details_path_vtable,
 };
 
 ActivationDetails *activation_details_new(Unit *trigger_unit) {
diff --git a/test/testsuite-63.units/test63-glob.path b/test/testsuite-63.units/test63-glob.path
new file mode 100644 (file)
index 0000000..5f237a9
--- /dev/null
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Path]
+PathExistsGlob=/tmp/test63-glob*
diff --git a/test/testsuite-63.units/test63-glob.service b/test/testsuite-63.units/test63-glob.service
new file mode 100644 (file)
index 0000000..3f49dd4
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStartPre=sh -c 'test "$TRIGGER_PATH" = /tmp/test63-glob-foo'
+ExecStartPre=sh -c 'test "$TRIGGER_UNIT" = test63-glob.path'
+ExecStart=systemd-notify --ready
+RemainAfterExit=yes
+Type=notify
index 1a8721d82c70da920f69d9820fe998992e97b260..01a928b8d6dea2e0a5cd71d58d44acc6fdc143a8 100644 (file)
@@ -3,4 +3,6 @@
 ConditionPathExists=/tmp/nonexistent
 
 [Service]
+ExecStartPre=sh -c 'test "$TRIGGER_PATH" = /tmp/test63'
+ExecStartPre=sh -c 'test "$TRIGGER_UNIT" = test63.path'
 ExecStart=true
index 20d93936b33c4ab0e74f5cad5d6ebdd595360107..7ee7fc15139b513a0a733064de373ec6546e1eb1 100755 (executable)
@@ -28,6 +28,19 @@ test "$(systemctl show test63.service -P Result)" = success
 test "$(systemctl show test63.path -P ActiveState)" = active
 test "$(systemctl show test63.path -P Result)" = success
 
+# Test that glob matching works too, with $TRIGGER_PATH
+systemctl start test63-glob.path
+touch /tmp/test63-glob-foo
+timeout 60 bash -c 'while ! systemctl -q is-active test63-glob.service; do sleep .2; done'
+test "$(systemctl show test63-glob.service -P ActiveState)" = active
+test "$(systemctl show test63-glob.service -P Result)" = success
+
+test "$(busctl --json=short get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test63_2dglob_2eservice org.freedesktop.systemd1.Unit ActivationDetails)" = '{"type":"a(ss)","data":[["trigger_unit","test63-glob.path"],["trigger_path","/tmp/test63-glob-foo"]]}'
+
+systemctl stop test63-glob.path test63-glob.service
+
+test "$(busctl --json=short get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test63_2dglob_2eservice org.freedesktop.systemd1.Unit ActivationDetails)" = '{"type":"a(ss)","data":[]}'
+
 systemctl log-level info
 
 echo OK >/testok