1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <stdbool.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6
7 #include "alloc-util.h"
8 #include "all-units.h"
9 #include "fd-util.h"
10 #include "fs-util.h"
11 #include "macro.h"
12 #include "manager.h"
13 #include "mkdir.h"
14 #include "path-util.h"
15 #include "rm-rf.h"
16 #include "string-util.h"
17 #include "strv.h"
18 #include "tests.h"
19 #include "unit.h"
20 #include "util.h"
21
22 typedef void (*test_function_t)(Manager *m);
23
setup_test(Manager ** m)24 static int setup_test(Manager **m) {
25 char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit",
26 "directorynotempty", "makedirectory");
27 Manager *tmp = NULL;
28 int r;
29
30 assert_se(m);
31
32 r = enter_cgroup_subroot(NULL);
33 if (r == -ENOMEDIUM)
34 return log_tests_skipped("cgroupfs not available");
35
36 r = manager_new(LOOKUP_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &tmp);
37 if (manager_errno_skip_test(r))
38 return log_tests_skipped_errno(r, "manager_new");
39 assert_se(r >= 0);
40 assert_se(manager_startup(tmp, NULL, NULL, NULL) >= 0);
41
42 STRV_FOREACH(test_path, tests_path) {
43 _cleanup_free_ char *p = NULL;
44
45 p = strjoin("/tmp/test-path_", *test_path);
46 assert_se(p);
47
48 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
49 }
50
51 *m = tmp;
52
53 return 0;
54 }
55
shutdown_test(Manager * m)56 static void shutdown_test(Manager *m) {
57 assert_se(m);
58
59 manager_free(m);
60 }
61
service_for_path(Manager * m,Path * path,const char * service_name)62 static Service *service_for_path(Manager *m, Path *path, const char *service_name) {
63 _cleanup_free_ char *tmp = NULL;
64 Unit *service_unit = NULL;
65
66 assert_se(m);
67 assert_se(path);
68
69 if (!service_name) {
70 assert_se(tmp = strreplace(UNIT(path)->id, ".path", ".service"));
71 service_unit = manager_get_unit(m, tmp);
72 } else
73 service_unit = manager_get_unit(m, service_name);
74 assert_se(service_unit);
75
76 return SERVICE(service_unit);
77 }
78
_check_states(unsigned line,Manager * m,Path * path,Service * service,PathState path_state,ServiceState service_state)79 static int _check_states(unsigned line,
80 Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) {
81 assert_se(m);
82 assert_se(service);
83
84 usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC;
85
86 while (path->state != path_state || service->state != service_state ||
87 path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) {
88
89 assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0);
90
91 usec_t n = now(CLOCK_MONOTONIC);
92 log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")",
93 line,
94 UNIT(path)->id,
95 path_state_to_string(path->state),
96 path_result_to_string(path->result),
97 end - n);
98 log_info("line %u: %s: state = %s; result = %s",
99 line,
100 UNIT(service)->id,
101 service_state_to_string(service->state),
102 service_result_to_string(service->result));
103
104 if (service->state == SERVICE_FAILED &&
105 service->main_exec_status.status == EXIT_CGROUP &&
106 !ci_environment())
107 /* On a general purpose system we may fail to start the service for reasons which are
108 * not under our control: permission limits, resource exhaustion, etc. Let's skip the
109 * test in those cases. On developer machines we require proper setup. */
110 return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED),
111 "Failed to start service %s, aborting test: %s/%s",
112 UNIT(service)->id,
113 service_state_to_string(service->state),
114 service_result_to_string(service->result));
115
116 if (n >= end) {
117 log_error("Test timeout when testing %s", UNIT(path)->id);
118 exit(EXIT_FAILURE);
119 }
120 }
121
122 return 0;
123 }
124 #define check_states(...) _check_states(__LINE__, __VA_ARGS__)
125
test_path_exists(Manager * m)126 static void test_path_exists(Manager *m) {
127 const char *test_path = "/tmp/test-path_exists";
128 Unit *unit = NULL;
129 Path *path = NULL;
130 Service *service = NULL;
131
132 assert_se(m);
133
134 assert_se(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit) >= 0);
135
136 path = PATH(unit);
137 service = service_for_path(m, path, NULL);
138
139 assert_se(unit_start(unit) >= 0);
140 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
141 return;
142
143 assert_se(touch(test_path) >= 0);
144 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
145 return;
146
147 /* Service restarts if file still exists */
148 assert_se(unit_stop(UNIT(service)) >= 0);
149 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
150 return;
151
152 assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
153 assert_se(unit_stop(UNIT(service)) >= 0);
154 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
155 return;
156
157 assert_se(unit_stop(unit) >= 0);
158 }
159
test_path_existsglob(Manager * m)160 static void test_path_existsglob(Manager *m) {
161 const char *test_path = "/tmp/test-path_existsglobFOOBAR";
162 Unit *unit = NULL;
163 Path *path = NULL;
164 Service *service = NULL;
165
166 assert_se(m);
167
168 assert_se(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit) >= 0);
169
170 path = PATH(unit);
171 service = service_for_path(m, path, NULL);
172
173 assert_se(unit_start(unit) >= 0);
174 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
175 return;
176
177 assert_se(touch(test_path) >= 0);
178 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
179 return;
180
181 /* Service restarts if file still exists */
182 assert_se(unit_stop(UNIT(service)) >= 0);
183 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
184 return;
185
186 assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
187 assert_se(unit_stop(UNIT(service)) >= 0);
188 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
189 return;
190
191 assert_se(unit_stop(unit) >= 0);
192 }
193
test_path_changed(Manager * m)194 static void test_path_changed(Manager *m) {
195 const char *test_path = "/tmp/test-path_changed";
196 FILE *f;
197 Unit *unit = NULL;
198 Path *path = NULL;
199 Service *service = NULL;
200
201 assert_se(m);
202
203 assert_se(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit) >= 0);
204
205 path = PATH(unit);
206 service = service_for_path(m, path, NULL);
207
208 assert_se(unit_start(unit) >= 0);
209 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
210 return;
211
212 assert_se(touch(test_path) >= 0);
213 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
214 return;
215
216 /* Service does not restart if file still exists */
217 assert_se(unit_stop(UNIT(service)) >= 0);
218 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
219 return;
220
221 f = fopen(test_path, "w");
222 assert_se(f);
223 fclose(f);
224
225 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
226 return;
227
228 assert_se(unit_stop(UNIT(service)) >= 0);
229 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
230 return;
231
232 (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL);
233 assert_se(unit_stop(unit) >= 0);
234 }
235
test_path_modified(Manager * m)236 static void test_path_modified(Manager *m) {
237 _cleanup_fclose_ FILE *f = NULL;
238 const char *test_path = "/tmp/test-path_modified";
239 Unit *unit = NULL;
240 Path *path = NULL;
241 Service *service = NULL;
242
243 assert_se(m);
244
245 assert_se(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit) >= 0);
246
247 path = PATH(unit);
248 service = service_for_path(m, path, NULL);
249
250 assert_se(unit_start(unit) >= 0);
251 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
252 return;
253
254 assert_se(touch(test_path) >= 0);
255 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
256 return;
257
258 /* Service does not restart if file still exists */
259 assert_se(unit_stop(UNIT(service)) >= 0);
260 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
261 return;
262
263 f = fopen(test_path, "w");
264 assert_se(f);
265 fputs("test", f);
266
267 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
268 return;
269
270 assert_se(unit_stop(UNIT(service)) >= 0);
271 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
272 return;
273
274 (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL);
275 assert_se(unit_stop(unit) >= 0);
276 }
277
test_path_unit(Manager * m)278 static void test_path_unit(Manager *m) {
279 const char *test_path = "/tmp/test-path_unit";
280 Unit *unit = NULL;
281 Path *path = NULL;
282 Service *service = NULL;
283
284 assert_se(m);
285
286 assert_se(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit) >= 0);
287
288 path = PATH(unit);
289 service = service_for_path(m, path, "path-mycustomunit.service");
290
291 assert_se(unit_start(unit) >= 0);
292 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
293 return;
294
295 assert_se(touch(test_path) >= 0);
296 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
297 return;
298
299 assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
300 assert_se(unit_stop(UNIT(service)) >= 0);
301 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
302 return;
303
304 assert_se(unit_stop(unit) >= 0);
305 }
306
test_path_directorynotempty(Manager * m)307 static void test_path_directorynotempty(Manager *m) {
308 const char *test_file, *test_path = "/tmp/test-path_directorynotempty/";
309 Unit *unit = NULL;
310 Path *path = NULL;
311 Service *service = NULL;
312
313 assert_se(m);
314
315 assert_se(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit) >= 0);
316
317 path = PATH(unit);
318 service = service_for_path(m, path, NULL);
319
320 assert_se(access(test_path, F_OK) < 0);
321
322 assert_se(unit_start(unit) >= 0);
323 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
324 return;
325
326 /* MakeDirectory default to no */
327 assert_se(access(test_path, F_OK) < 0);
328
329 assert_se(mkdir_p(test_path, 0755) >= 0);
330 test_file = strjoina(test_path, "test_file");
331 assert_se(touch(test_file) >= 0);
332 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
333 return;
334
335 /* Service restarts if directory is still not empty */
336 assert_se(unit_stop(UNIT(service)) >= 0);
337 if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
338 return;
339
340 assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
341 assert_se(unit_stop(UNIT(service)) >= 0);
342 if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
343 return;
344
345 assert_se(unit_stop(unit) >= 0);
346 }
347
test_path_makedirectory_directorymode(Manager * m)348 static void test_path_makedirectory_directorymode(Manager *m) {
349 const char *test_path = "/tmp/test-path_makedirectory/";
350 Unit *unit = NULL;
351 struct stat s;
352
353 assert_se(m);
354
355 assert_se(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit) >= 0);
356
357 assert_se(access(test_path, F_OK) < 0);
358
359 assert_se(unit_start(unit) >= 0);
360
361 /* Check if the directory has been created */
362 assert_se(access(test_path, F_OK) >= 0);
363
364 /* Check the mode we specified with DirectoryMode=0744 */
365 assert_se(stat(test_path, &s) >= 0);
366 assert_se((s.st_mode & S_IRWXU) == 0700);
367 assert_se((s.st_mode & S_IRWXG) == 0040);
368 assert_se((s.st_mode & S_IRWXO) == 0004);
369
370 assert_se(unit_stop(unit) >= 0);
371 (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL);
372 }
373
main(int argc,char * argv[])374 int main(int argc, char *argv[]) {
375 static const test_function_t tests[] = {
376 test_path_exists,
377 test_path_existsglob,
378 test_path_changed,
379 test_path_modified,
380 test_path_unit,
381 test_path_directorynotempty,
382 test_path_makedirectory_directorymode,
383 NULL,
384 };
385
386 _cleanup_free_ char *test_path = NULL;
387 _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
388
389 umask(022);
390
391 test_setup_logging(LOG_INFO);
392
393 assert_se(get_testdata_dir("test-path", &test_path) >= 0);
394 assert_se(set_unit_path(test_path) >= 0);
395 assert_se(runtime_dir = setup_fake_runtime_dir());
396
397 for (const test_function_t *test = tests; *test; test++) {
398 Manager *m = NULL;
399 int r;
400
401 /* We create a clean environment for each test */
402 r = setup_test(&m);
403 if (r != 0)
404 return r;
405
406 (*test)(m);
407
408 shutdown_test(m);
409 }
410
411 return 0;
412 }
413