Compare commits

...

2 Commits

Author SHA1 Message Date
James R T 67d67875b4
Merge 1a34e4709f into a526b9ddfc 2024-11-20 15:02:11 +08:00
James Raphael Tiovalen 1a34e4709f
path: Add support for path unit deactivation
Add initial support for path unit deactivation. This is done by adding a
new parameter `DeactivationToggle` that would modify the behavior of
`PathExists`, `PathExistsGlob`, and `DirectoryNotEmpty` accordingly.

If `DeactivationToggle` is enabled, whenever the predicate is no longer
satisfied, the associated service unit would be stopped and the path
unit would go to waiting state. If the predicate is satisfied again, the
service unit would be started again and the path unit would go back to
running state.

This feature would require systemd to continuously monitor the specified
paths even after the associated service unit has been triggered. As
such, this can potentially lead to a performance hit. It is advisable to
enable this feature only when necessary.

This fixes #3642.

Signed-off-by: James Raphael Tiovalen <james-raphael.tiovalen@hpe.com>
2024-10-28 23:04:44 +08:00
14 changed files with 518 additions and 9 deletions

View File

@ -498,6 +498,7 @@ Most path unit settings are available to transient units.
Unit= Unit=
✓ MakeDirectory= ✓ MakeDirectory=
✓ DirectoryMode= ✓ DirectoryMode=
✓ DeactivationToggle=
``` ```
## Install Section ## Install Section

View File

@ -10715,6 +10715,8 @@ node /org/freedesktop/systemd1/unit/cups_2epath {
readonly t TriggerLimitIntervalUSec = ...; readonly t TriggerLimitIntervalUSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const") @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u TriggerLimitBurst = ...; readonly u TriggerLimitBurst = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b DeactivationToggle = ...;
}; };
interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... }; interface org.freedesktop.DBus.Introspectable { ... };
@ -10731,6 +10733,8 @@ node /org/freedesktop/systemd1/unit/cups_2epath {
<!--property TriggerLimitBurst is not documented!--> <!--property TriggerLimitBurst is not documented!-->
<!--property DeactivationToggle is not documented!-->
<!--Autogenerated cross-references for systemd.directives, do not edit--> <!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Unit"/> <variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Unit"/>
@ -10755,6 +10759,8 @@ node /org/freedesktop/systemd1/unit/cups_2epath {
<variablelist class="dbus-property" generated="True" extra-ref="TriggerLimitBurst"/> <variablelist class="dbus-property" generated="True" extra-ref="TriggerLimitBurst"/>
<variablelist class="dbus-property" generated="True" extra-ref="DeactivationToggle"/>
<!--End of Autogenerated section--> <!--End of Autogenerated section-->
<refsect2> <refsect2>
@ -12488,6 +12494,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<title>Timer Objects</title> <title>Timer Objects</title>
<para><varname>DeferReactivation</varname> was added in version 257.</para> <para><varname>DeferReactivation</varname> was added in version 257.</para>
</refsect2> </refsect2>
<refsect2>
<title>Path Objects</title>
<para><varname>DeactivationToggle</varname> was added in version 257.</para>
</refsect2>
</refsect1> </refsect1>
<refsect1> <refsect1>

View File

@ -28,7 +28,7 @@
<para>A unit configuration file whose name ends in <para>A unit configuration file whose name ends in
<literal>.path</literal> encodes information about a path <literal>.path</literal> encodes information about a path
monitored by systemd, for path-based activation.</para> monitored by systemd, for path-based activation and deactivation.</para>
<para>This man page lists the configuration options specific to <para>This man page lists the configuration options specific to
this unit type. See this unit type. See
@ -206,6 +206,21 @@
<xi:include href="version-info.xml" xpointer="v250"/></listitem> <xi:include href="version-info.xml" xpointer="v250"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>DeactivationToggle=</varname></term>
<listitem><para>Takes a boolean argument. If true, the
triggered unit is stopped and the path unit goes back to
waiting state when the path it watches is removed for
<varname>PathExists=</varname>, when there are no matching
paths for <varname>PathExistsGlob=</varname>, or when the
directory is empty for <varname>DirectoryNotEmpty=</varname>.
This option is ignored for <varname>PathChanged=</varname> and
<varname>PathModified=</varname> settings. Defaults to
<option>false</option>.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist> </variablelist>
<xi:include href="systemd.service.xml" xpointer="shared-unit-options" /> <xi:include href="systemd.service.xml" xpointer="shared-unit-options" />

View File

@ -49,6 +49,7 @@ const sd_bus_vtable bus_path_vtable[] = {
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Path, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Path, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Path, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Path, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Path, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Path, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DeactivationToggle", "b", bus_property_get_bool, offsetof(Path, deactivation_toggle), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END SD_BUS_VTABLE_END
}; };
@ -141,6 +142,9 @@ static int bus_path_set_transient_property(
if (streq(name, "TriggerLimitIntervalUSec")) if (streq(name, "TriggerLimitIntervalUSec"))
return bus_set_transient_usec(u, name, &p->trigger_limit.interval, message, flags, error); return bus_set_transient_usec(u, name, &p->trigger_limit.interval, message, flags, error);
if (streq(name, "DeactivationToggle"))
return bus_set_transient_bool(u, name, &p->deactivation_toggle, message, flags, error);
return 0; return 0;
} }

View File

@ -586,6 +586,7 @@ Path.MakeDirectory, config_parse_bool,
Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode)
Path.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Path, trigger_limit.interval) Path.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Path, trigger_limit.interval)
Path.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Path, trigger_limit.burst) Path.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Path, trigger_limit.burst)
Path.DeactivationToggle, config_parse_bool, 0, offsetof(Path, deactivation_toggle)
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Slice') }} {{ CGROUP_CONTEXT_CONFIG_ITEMS('Slice') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Scope') }} {{ CGROUP_CONTEXT_CONFIG_ITEMS('Scope') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Scope') }} {{ KILL_CONTEXT_CONFIG_ITEMS('Scope') }}

View File

@ -47,11 +47,14 @@ int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
bool exists = false; bool exists = false;
char *slash, *oldslash = NULL; char *slash, *oldslash = NULL;
int r; int r;
Path *p;
assert(s); assert(s);
assert(s->unit); assert(s->unit);
assert(handler); assert(handler);
p = PATH(s->unit);
path_spec_unwatch(s); path_spec_unwatch(s);
s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
@ -82,9 +85,11 @@ int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
*cut = '\0'; *cut = '\0';
flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO; flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO;
SET_FLAG(flags, IN_DELETE | IN_MOVED_FROM, p && p->deactivation_toggle);
} else { } else {
cut = NULL; cut = NULL;
flags = flags_table[s->type]; flags = flags_table[s->type];
SET_FLAG(flags, IN_DELETE | IN_MOVED_FROM, p && p->deactivation_toggle && s->type == PATH_DIRECTORY_NOT_EMPTY);
} }
/* If this is a symlink watch both the symlink inode and where it points to. If the inode is /* If this is a symlink watch both the symlink inode and where it points to. If the inode is
@ -134,7 +139,10 @@ int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
char tmp2 = *cut2; char tmp2 = *cut2;
*cut2 = '\0'; *cut2 = '\0';
(void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF); flags = IN_MOVE_SELF;
SET_FLAG(flags, IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM, p && p->deactivation_toggle);
(void) inotify_add_watch(s->inotify_fd, s->path, flags);
/* Error is ignored, the worst can happen is we get spurious events. */ /* Error is ignored, the worst can happen is we get spurious events. */
*cut2 = tmp2; *cut2 = tmp2;
@ -427,14 +435,16 @@ static void path_dump(Unit *u, FILE *f, const char *prefix) {
"%sMakeDirectory: %s\n" "%sMakeDirectory: %s\n"
"%sDirectoryMode: %04o\n" "%sDirectoryMode: %04o\n"
"%sTriggerLimitIntervalSec: %s\n" "%sTriggerLimitIntervalSec: %s\n"
"%sTriggerLimitBurst: %u\n", "%sTriggerLimitBurst: %u\n"
"%sDeactivationToggle: %s\n",
prefix, path_state_to_string(p->state), prefix, path_state_to_string(p->state),
prefix, path_result_to_string(p->result), prefix, path_result_to_string(p->result),
prefix, trigger ? trigger->id : "n/a", prefix, trigger ? trigger->id : "n/a",
prefix, yes_no(p->make_directory), prefix, yes_no(p->make_directory),
prefix, p->directory_mode, prefix, p->directory_mode,
prefix, FORMAT_TIMESPAN(p->trigger_limit.interval, USEC_PER_SEC), prefix, FORMAT_TIMESPAN(p->trigger_limit.interval, USEC_PER_SEC),
prefix, p->trigger_limit.burst); prefix, p->trigger_limit.burst,
prefix, yes_no(p->deactivation_toggle));
LIST_FOREACH(spec, s, p->specs) LIST_FOREACH(spec, s, p->specs)
path_spec_dump(s, f, prefix); path_spec_dump(s, f, prefix);
@ -555,7 +565,37 @@ static void path_enter_running(Path *p, char *trigger_path) {
job_set_activation_details(job, details); job_set_activation_details(job, details);
path_set_state(p, PATH_RUNNING); path_set_state(p, PATH_RUNNING);
path_unwatch(p);
if (!p->deactivation_toggle)
path_unwatch(p);
return;
fail:
path_enter_dead(p, PATH_FAILURE_RESOURCES);
}
static void path_deactivate_unit(Path *p) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
Unit *trigger;
Job *job;
int r;
assert(p);
trigger = UNIT_TRIGGER(UNIT(p));
if (!trigger) {
log_unit_error(UNIT(p), "Unit to trigger vanished.");
goto fail;
}
r = manager_add_job(UNIT(p)->manager, JOB_STOP, trigger, JOB_REPLACE, NULL, &error, &job);
if (r < 0) {
log_unit_warning(UNIT(p), "Failed to queue unit stop job: %s", bus_error_message(&error, r));
goto fail;
}
path_set_state(p, PATH_WAITING);
return; return;
@ -578,19 +618,30 @@ static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify)
_cleanup_free_ char *trigger_path = NULL; _cleanup_free_ char *trigger_path = NULL;
Unit *trigger; Unit *trigger;
int r; int r;
bool watch_not_needed = !p->deactivation_toggle || p->state == PATH_RUNNING;
bool trigger_unit_is_running;
if (p->trigger_notify_event_source) if (p->trigger_notify_event_source)
(void) event_source_disable(p->trigger_notify_event_source); (void) event_source_disable(p->trigger_notify_event_source);
/* If the triggered unit is already running, so are we */ /* If the triggered unit is already running, so are we */
trigger = UNIT_TRIGGER(UNIT(p)); trigger = UNIT_TRIGGER(UNIT(p));
if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) { trigger_unit_is_running = trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger));
if (watch_not_needed && trigger_unit_is_running) {
if (p->deactivation_toggle && p->state == PATH_RUNNING) {
if (!path_check_good(p, true, from_trigger_notify, &trigger_path))
/* If the path is removed, stop the unit */
path_deactivate_unit(p);
return;
}
path_set_state(p, PATH_RUNNING); path_set_state(p, PATH_RUNNING);
path_unwatch(p); path_unwatch(p);
return; return;
} }
if (path_check_good(p, initial, from_trigger_notify, &trigger_path)) { if (watch_not_needed && path_check_good(p, initial, from_trigger_notify, &trigger_path)) {
log_unit_debug(UNIT(p), "Got triggered."); log_unit_debug(UNIT(p), "Got triggered.");
path_enter_running(p, trigger_path); path_enter_running(p, trigger_path);
return; return;
@ -608,9 +659,18 @@ static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify)
* recheck */ * recheck */
if (path_check_good(p, false, from_trigger_notify, &trigger_path)) { if (path_check_good(p, false, from_trigger_notify, &trigger_path)) {
if (p->deactivation_toggle && trigger_unit_is_running) {
path_set_state(p, PATH_RUNNING);
return;
}
log_unit_debug(UNIT(p), "Got triggered."); log_unit_debug(UNIT(p), "Got triggered.");
path_enter_running(p, trigger_path); path_enter_running(p, trigger_path);
return; return;
} else if (p->deactivation_toggle && trigger_unit_is_running) {
/* If the path is removed, stop the unit */
path_deactivate_unit(p);
return;
} }
path_set_state(p, PATH_WAITING); path_set_state(p, PATH_WAITING);

View File

@ -67,6 +67,8 @@ struct Path {
RateLimit trigger_limit; RateLimit trigger_limit;
sd_event_source *trigger_notify_event_source; sd_event_source *trigger_notify_event_source;
bool deactivation_toggle;
}; };
struct ActivationDetailsPath { struct ActivationDetailsPath {

View File

@ -2315,7 +2315,7 @@ static int bus_append_mount_property(sd_bus_message *m, const char *field, const
static int bus_append_path_property(sd_bus_message *m, const char *field, const char *eq) { static int bus_append_path_property(sd_bus_message *m, const char *field, const char *eq) {
int r; int r;
if (streq(field, "MakeDirectory")) if (STR_IN_SET(field, "MakeDirectory", "DeactivationToggle"))
return bus_append_parse_boolean(m, field, eq); return bus_append_parse_boolean(m, field, eq);
if (streq(field, "DirectoryMode")) if (streq(field, "DirectoryMode"))

View File

@ -22,7 +22,8 @@ typedef void (*test_function_t)(Manager *m);
static int setup_test(Manager **m) { static int setup_test(Manager **m) {
char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit", char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit",
"directorynotempty", "makedirectory"); "directorynotempty", "makedirectory", "deactivationtoggle",
"deactivationtoggle_secondary");
Manager *tmp = NULL; Manager *tmp = NULL;
int r; int r;
@ -378,6 +379,371 @@ static void test_path_makedirectory_directorymode(Manager *m) {
(void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL);
} }
static void test_path_deactivationtoggle_exists(Manager *m) {
const char *test_file, *test_file2;
const char *test_path = "/tmp/test-path_deactivationtoggle/";
const char *test_path2 = "/tmp/test-path_deactivationtoggle_secondary/";
const char *path_name = "path-deactivationtoggle-exists.path";
const char *service_name = "path-deactivationtoggle.service";
Unit *unit = NULL;
Path *path = NULL;
Service *service = NULL;
assert_se(m);
assert_se(manager_load_startable_unit_or_warn(m, path_name, NULL, &unit) >= 0);
path = PATH(unit);
service = service_for_path(m, path, service_name);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
test_file = strjoina(test_path, "test_file");
test_file2 = strjoina(test_path2, "test_file");
assert_se(mkdir_p(test_path, 0755) >= 0);
assert_se(mkdir_p(test_path2, 0755) >= 0);
assert_se(touch(test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_file2) >= 0);
assert_se(rename(test_file2, test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rename(test_file, test_file2) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(touch(test_file) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* If file exists before path is started, service is stopped when file is removed */
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(touch(test_file) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service is stopped when file is renamed */
assert_se(rename(test_file, test_file2) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(rename(test_file2, test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rename(test_file, test_file2) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_start(UNIT(service), NULL) >= 0);
if (check_states(m, path, service, PATH_DEAD, SERVICE_RUNNING) < 0)
return;
/* If service is running and file does not exist, it is stopped */
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(unit_start(UNIT(service), NULL) >= 0);
if (check_states(m, path, service, PATH_DEAD, SERVICE_RUNNING) < 0)
return;
/* If service is running and file exists, path unit is running */
assert_se(touch(test_file) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* If service is running first, service is stopped when file is removed */
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
}
static void test_path_deactivationtoggle_existsglob(Manager *m) {
const char *test_file, *test_file2, *test_file3, *test_file4;
const char *test_path = "/tmp/test-path_deactivationtoggle/";
const char *test_path2 = "/tmp/test-path_deactivationtoggle_secondary/";
const char *path_name = "path-deactivationtoggle-existsglob.path";
const char *service_name = "path-deactivationtoggle.service";
Unit *unit = NULL;
Path *path = NULL;
Service *service = NULL;
assert_se(m);
assert_se(manager_load_startable_unit_or_warn(m, path_name, NULL, &unit) >= 0);
path = PATH(unit);
service = service_for_path(m, path, service_name);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
test_file = strjoina(test_path, "test_file_globFOO");
test_file2 = strjoina(test_path, "test_file_globBAR");
test_file3 = strjoina(test_path2, "test_file_globFOO");
test_file4 = strjoina(test_path2, "test_file_globBAR");
assert_se(mkdir_p(test_path, 0755) >= 0);
assert_se(mkdir_p(test_path2, 0755) >= 0);
assert_se(touch(test_file) >= 0);
assert_se(touch(test_file2) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_file3) >= 0);
assert_se(touch(test_file4) >= 0);
assert_se(rename(test_file3, test_file) >= 0);
assert_se(rename(test_file4, test_file2) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rename(test_file, test_file3) >= 0);
assert_se(rename(test_file2, test_file4) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(rm_rf(test_file3, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(rm_rf(test_file4, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(touch(test_file) >= 0);
assert_se(touch(test_file2) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* If files exist before path is started, service is stopped when files are removed */
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(touch(test_file) >= 0);
assert_se(touch(test_file2) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service is stopped when files are renamed */
assert_se(rename(test_file, test_file3) >= 0);
assert_se(rename(test_file2, test_file4) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(rename(test_file3, test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rename(test_file, test_file3) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(rm_rf(test_file3, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(rm_rf(test_file4, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_start(UNIT(service), NULL) >= 0);
if (check_states(m, path, service, PATH_DEAD, SERVICE_RUNNING) < 0)
return;
/* If service is running and files do not exist, it is stopped */
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(unit_start(UNIT(service), NULL) >= 0);
if (check_states(m, path, service, PATH_DEAD, SERVICE_RUNNING) < 0)
return;
/* If service is running and files exist, path unit is running */
assert_se(touch(test_file) >= 0);
assert_se(touch(test_file2) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* If service is running first, service is stopped when files are removed */
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
}
static void test_path_deactivationtoggle_directorynotempty(Manager *m) {
const char *test_file, *test_file2;
const char *test_path = "/tmp/test-path_deactivationtoggle/";
const char *test_path2 = "/tmp/test-path_deactivationtoggle_secondary/";
const char *path_name = "path-deactivationtoggle-directorynotempty.path";
const char *service_name = "path-deactivationtoggle.service";
Unit *unit = NULL;
Path *path = NULL;
Service *service = NULL;
assert_se(m);
assert_se(manager_load_startable_unit_or_warn(m, path_name, NULL, &unit) >= 0);
path = PATH(unit);
service = service_for_path(m, path, service_name);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
test_file = strjoina(test_path, "test_file");
test_file2 = strjoina(test_path2, "test_file");
assert_se(mkdir_p(test_path, 0755) >= 0);
assert_se(mkdir_p(test_path2, 0755) >= 0);
assert_se(touch(test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_file2) >= 0);
assert_se(rename(test_file2, test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rename(test_file, test_file2) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(mkdir_p(test_path, 0755) >= 0);
assert_se(touch(test_file) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* If directory is not empty before path is started, service is stopped when file is removed */
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(touch(test_file) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service is stopped when directory is empty due to file rename */
assert_se(rename(test_file, test_file2) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(rename(test_file2, test_file) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rename(test_file, test_file2) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(rm_rf(test_file2, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_start(UNIT(service), NULL) >= 0);
if (check_states(m, path, service, PATH_DEAD, SERVICE_RUNNING) < 0)
return;
/* If service is running and directory is empty, it is stopped */
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
assert_se(unit_start(UNIT(service), NULL) >= 0);
if (check_states(m, path, service, PATH_DEAD, SERVICE_RUNNING) < 0)
return;
/* If service is running and directory is not empty, path unit is running */
assert_se(touch(test_file) >= 0);
assert_se(unit_start(unit, NULL) >= 0);
if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* If service is running first, service is stopped when directory is empty */
assert_se(rm_rf(test_file, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0);
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
static const test_function_t tests[] = { static const test_function_t tests[] = {
test_path_exists, test_path_exists,
@ -387,6 +753,9 @@ int main(int argc, char *argv[]) {
test_path_unit, test_path_unit,
test_path_directorynotempty, test_path_directorynotempty,
test_path_makedirectory_directorymode, test_path_makedirectory_directorymode,
test_path_deactivationtoggle_exists,
test_path_deactivationtoggle_existsglob,
test_path_deactivationtoggle_directorynotempty,
NULL, NULL,
}; };

View File

@ -85,6 +85,7 @@ ConditionSecurity=
ConditionUser= ConditionUser=
ConditionVirtualization= ConditionVirtualization=
Conflicts= Conflicts=
DeactivationToggle=
DefaultDependencies= DefaultDependencies=
DefaultInstance= DefaultInstance=
DeferAcceptSec= DeferAcceptSec=

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=Test DeactivationToggle with DirectoryNotEmpty
[Path]
DirectoryNotEmpty=/tmp/test-path_deactivationtoggle/
Unit=path-deactivationtoggle.service
DeactivationToggle=true
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=Test DeactivationToggle with PathExists
[Path]
PathExists=/tmp/test-path_deactivationtoggle/test_file
Unit=path-deactivationtoggle.service
DeactivationToggle=true
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=Test DeactivationToggle with PathExistsGlob
[Path]
PathExistsGlob=/tmp/test-path_deactivationtoggle/test_file_glob*
Unit=path-deactivationtoggle.service
DeactivationToggle=true
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=Service Test for Path units
StartLimitIntervalSec=0
[Service]
ExecStart=sleep infinity
Type=exec
RemainAfterExit=true