service: set TRIGGER_UNIT= and TRIGGER_PATH= on activation by path unit

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.
This commit is contained in:
Luca Boccassi 2022-08-02 20:07:35 +01:00
parent 48b92b37ac
commit 4c42032854
9 changed files with 184 additions and 11 deletions

View 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

View file

@ -208,6 +208,10 @@
<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>,

View 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,
};

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

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

View file

@ -0,0 +1,3 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Path]
PathExistsGlob=/tmp/test63-glob*

View file

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

View 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

View file

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