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