mirror of
https://github.com/systemd/systemd.git
synced 2025-01-09 01:18:19 +03:00
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:
parent
48b92b37ac
commit
4c42032854
@ -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
|
||||
|
@ -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>,
|
||||
|
144
src/core/path.c
144
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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
3
test/testsuite-63.units/test63-glob.path
Normal file
3
test/testsuite-63.units/test63-glob.path
Normal file
@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Path]
|
||||
PathExistsGlob=/tmp/test63-glob*
|
7
test/testsuite-63.units/test63-glob.service
Normal file
7
test/testsuite-63.units/test63-glob.service
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user