diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index d9da864bc21..2acb737c38f 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3695,6 +3695,19 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX system. + + $TRIGGER_UNIT + $TRIGGER_PATH + + 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. + + + For system services, when PAMName= is enabled and pam_systemd is part diff --git a/man/systemd.path.xml b/man/systemd.path.xml index 4e4cd9137a6..f143208cb46 100644 --- a/man/systemd.path.xml +++ b/man/systemd.path.xml @@ -208,6 +208,10 @@ See Also + Environment variables with details on the trigger will be set for triggered units. See the + Environment Variables Set on Triggered Units section in + systemd.exec1 + for more details. systemd1, systemctl1, diff --git a/src/core/path.c b/src/core/path.c index 69bbddf1585..2810e30573d 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -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, +}; diff --git a/src/core/path.h b/src/core/path.h index d835c241660..c76103cc129 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -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); diff --git a/src/core/unit.c b/src/core/unit.c index 18d9ba85de3..4255e0cf1f4 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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 index 00000000000..5f237a9fcf8 --- /dev/null +++ b/test/testsuite-63.units/test63-glob.path @@ -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 index 00000000000..3f49dd4620c --- /dev/null +++ b/test/testsuite-63.units/test63-glob.service @@ -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 diff --git a/test/testsuite-63.units/test63.service b/test/testsuite-63.units/test63.service index 1a8721d82c7..01a928b8d6d 100644 --- a/test/testsuite-63.units/test63.service +++ b/test/testsuite-63.units/test63.service @@ -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 diff --git a/test/units/testsuite-63.sh b/test/units/testsuite-63.sh index 20d93936b33..7ee7fc15139 100755 --- a/test/units/testsuite-63.sh +++ b/test/units/testsuite-63.sh @@ -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