]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/core/path.c
tree-wide: drop 'This file is part of systemd' blurb
[thirdparty/systemd.git] / src / core / path.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1+ */
2/***
3 Copyright 2010 Lennart Poettering
4***/
5
6#include <errno.h>
7#include <sys/epoll.h>
8#include <sys/inotify.h>
9#include <unistd.h>
10
11#include "bus-error.h"
12#include "bus-util.h"
13#include "dbus-path.h"
14#include "fd-util.h"
15#include "fs-util.h"
16#include "glob-util.h"
17#include "macro.h"
18#include "mkdir.h"
19#include "path.h"
20#include "special.h"
21#include "stat-util.h"
22#include "string-table.h"
23#include "string-util.h"
24#include "unit-name.h"
25#include "unit.h"
26
27static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
28 [PATH_DEAD] = UNIT_INACTIVE,
29 [PATH_WAITING] = UNIT_ACTIVE,
30 [PATH_RUNNING] = UNIT_ACTIVE,
31 [PATH_FAILED] = UNIT_FAILED
32};
33
34static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
35
36int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
37
38 static const int flags_table[_PATH_TYPE_MAX] = {
39 [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
40 [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
41 [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
42 [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY,
43 [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO
44 };
45
46 bool exists = false;
47 char *slash, *oldslash = NULL;
48 int r;
49
50 assert(s);
51 assert(s->unit);
52 assert(handler);
53
54 path_spec_unwatch(s);
55
56 s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
57 if (s->inotify_fd < 0) {
58 r = -errno;
59 goto fail;
60 }
61
62 r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s);
63 if (r < 0)
64 goto fail;
65
66 (void) sd_event_source_set_description(s->event_source, "path");
67
68 /* This function assumes the path was passed through path_simplify()! */
69 assert(!strstr(s->path, "//"));
70
71 for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) {
72 char *cut = NULL;
73 int flags;
74 char tmp;
75
76 if (slash) {
77 cut = slash + (slash == s->path);
78 tmp = *cut;
79 *cut = '\0';
80
81 flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
82 } else
83 flags = flags_table[s->type];
84
85 r = inotify_add_watch(s->inotify_fd, s->path, flags);
86 if (r < 0) {
87 if (IN_SET(errno, EACCES, ENOENT)) {
88 if (cut)
89 *cut = tmp;
90 break;
91 }
92
93 r = log_warning_errno(errno, "Failed to add watch on %s: %s", s->path, errno == ENOSPC ? "too many watches" : strerror(-r));
94 if (cut)
95 *cut = tmp;
96 goto fail;
97 } else {
98 exists = true;
99
100 /* Path exists, we don't need to watch parent too closely. */
101 if (oldslash) {
102 char *cut2 = oldslash + (oldslash == s->path);
103 char tmp2 = *cut2;
104 *cut2 = '\0';
105
106 (void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF);
107 /* Error is ignored, the worst can happen is we get spurious events. */
108
109 *cut2 = tmp2;
110 }
111 }
112
113 if (cut)
114 *cut = tmp;
115
116 if (slash)
117 oldslash = slash;
118 else {
119 /* whole path has been iterated over */
120 s->primary_wd = r;
121 break;
122 }
123 }
124
125 if (!exists) {
126 r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path);
127 /* either EACCESS or ENOENT */
128 goto fail;
129 }
130
131 return 0;
132
133fail:
134 path_spec_unwatch(s);
135 return r;
136}
137
138void path_spec_unwatch(PathSpec *s) {
139 assert(s);
140
141 s->event_source = sd_event_source_unref(s->event_source);
142 s->inotify_fd = safe_close(s->inotify_fd);
143}
144
145int path_spec_fd_event(PathSpec *s, uint32_t revents) {
146 union inotify_event_buffer buffer;
147 struct inotify_event *e;
148 ssize_t l;
149 int r = 0;
150
151 if (revents != EPOLLIN) {
152 log_error("Got invalid poll event on inotify.");
153 return -EINVAL;
154 }
155
156 l = read(s->inotify_fd, &buffer, sizeof(buffer));
157 if (l < 0) {
158 if (IN_SET(errno, EAGAIN, EINTR))
159 return 0;
160
161 return log_error_errno(errno, "Failed to read inotify event: %m");
162 }
163
164 FOREACH_INOTIFY_EVENT(e, buffer, l) {
165 if (IN_SET(s->type, PATH_CHANGED, PATH_MODIFIED) &&
166 s->primary_wd == e->wd)
167 r = 1;
168 }
169
170 return r;
171}
172
173static bool path_spec_check_good(PathSpec *s, bool initial) {
174 bool good = false;
175
176 switch (s->type) {
177
178 case PATH_EXISTS:
179 good = access(s->path, F_OK) >= 0;
180 break;
181
182 case PATH_EXISTS_GLOB:
183 good = glob_exists(s->path) > 0;
184 break;
185
186 case PATH_DIRECTORY_NOT_EMPTY: {
187 int k;
188
189 k = dir_is_empty(s->path);
190 good = !(k == -ENOENT || k > 0);
191 break;
192 }
193
194 case PATH_CHANGED:
195 case PATH_MODIFIED: {
196 bool b;
197
198 b = access(s->path, F_OK) >= 0;
199 good = !initial && b != s->previous_exists;
200 s->previous_exists = b;
201 break;
202 }
203
204 default:
205 ;
206 }
207
208 return good;
209}
210
211static void path_spec_mkdir(PathSpec *s, mode_t mode) {
212 int r;
213
214 if (IN_SET(s->type, PATH_EXISTS, PATH_EXISTS_GLOB))
215 return;
216
217 r = mkdir_p_label(s->path, mode);
218 if (r < 0)
219 log_warning_errno(r, "mkdir(%s) failed: %m", s->path);
220}
221
222static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
223 fprintf(f,
224 "%s%s: %s\n",
225 prefix,
226 path_type_to_string(s->type),
227 s->path);
228}
229
230void path_spec_done(PathSpec *s) {
231 assert(s);
232 assert(s->inotify_fd == -1);
233
234 free(s->path);
235}
236
237static void path_init(Unit *u) {
238 Path *p = PATH(u);
239
240 assert(u);
241 assert(u->load_state == UNIT_STUB);
242
243 p->directory_mode = 0755;
244}
245
246void path_free_specs(Path *p) {
247 PathSpec *s;
248
249 assert(p);
250
251 while ((s = p->specs)) {
252 path_spec_unwatch(s);
253 LIST_REMOVE(spec, p->specs, s);
254 path_spec_done(s);
255 free(s);
256 }
257}
258
259static void path_done(Unit *u) {
260 Path *p = PATH(u);
261
262 assert(p);
263
264 path_free_specs(p);
265}
266
267static int path_add_mount_dependencies(Path *p) {
268 PathSpec *s;
269 int r;
270
271 assert(p);
272
273 LIST_FOREACH(spec, s, p->specs) {
274 r = unit_require_mounts_for(UNIT(p), s->path, UNIT_DEPENDENCY_FILE);
275 if (r < 0)
276 return r;
277 }
278
279 return 0;
280}
281
282static int path_verify(Path *p) {
283 assert(p);
284
285 if (UNIT(p)->load_state != UNIT_LOADED)
286 return 0;
287
288 if (!p->specs) {
289 log_unit_error(UNIT(p), "Path unit lacks path setting. Refusing.");
290 return -ENOEXEC;
291 }
292
293 return 0;
294}
295
296static int path_add_default_dependencies(Path *p) {
297 int r;
298
299 assert(p);
300
301 if (!UNIT(p)->default_dependencies)
302 return 0;
303
304 r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, NULL, true, UNIT_DEPENDENCY_DEFAULT);
305 if (r < 0)
306 return r;
307
308 if (MANAGER_IS_SYSTEM(UNIT(p)->manager)) {
309 r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true, UNIT_DEPENDENCY_DEFAULT);
310 if (r < 0)
311 return r;
312 }
313
314 return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true, UNIT_DEPENDENCY_DEFAULT);
315}
316
317static int path_add_trigger_dependencies(Path *p) {
318 Unit *x;
319 int r;
320
321 assert(p);
322
323 if (!hashmap_isempty(UNIT(p)->dependencies[UNIT_TRIGGERS]))
324 return 0;
325
326 r = unit_load_related_unit(UNIT(p), ".service", &x);
327 if (r < 0)
328 return r;
329
330 return unit_add_two_dependencies(UNIT(p), UNIT_BEFORE, UNIT_TRIGGERS, x, true, UNIT_DEPENDENCY_IMPLICIT);
331}
332
333static int path_load(Unit *u) {
334 Path *p = PATH(u);
335 int r;
336
337 assert(u);
338 assert(u->load_state == UNIT_STUB);
339
340 r = unit_load_fragment_and_dropin(u);
341 if (r < 0)
342 return r;
343
344 if (u->load_state == UNIT_LOADED) {
345
346 r = path_add_trigger_dependencies(p);
347 if (r < 0)
348 return r;
349
350 r = path_add_mount_dependencies(p);
351 if (r < 0)
352 return r;
353
354 r = path_add_default_dependencies(p);
355 if (r < 0)
356 return r;
357 }
358
359 return path_verify(p);
360}
361
362static void path_dump(Unit *u, FILE *f, const char *prefix) {
363 Path *p = PATH(u);
364 Unit *trigger;
365 PathSpec *s;
366
367 assert(p);
368 assert(f);
369
370 trigger = UNIT_TRIGGER(u);
371
372 fprintf(f,
373 "%sPath State: %s\n"
374 "%sResult: %s\n"
375 "%sUnit: %s\n"
376 "%sMakeDirectory: %s\n"
377 "%sDirectoryMode: %04o\n",
378 prefix, path_state_to_string(p->state),
379 prefix, path_result_to_string(p->result),
380 prefix, trigger ? trigger->id : "n/a",
381 prefix, yes_no(p->make_directory),
382 prefix, p->directory_mode);
383
384 LIST_FOREACH(spec, s, p->specs)
385 path_spec_dump(s, f, prefix);
386}
387
388static void path_unwatch(Path *p) {
389 PathSpec *s;
390
391 assert(p);
392
393 LIST_FOREACH(spec, s, p->specs)
394 path_spec_unwatch(s);
395}
396
397static int path_watch(Path *p) {
398 int r;
399 PathSpec *s;
400
401 assert(p);
402
403 LIST_FOREACH(spec, s, p->specs) {
404 r = path_spec_watch(s, path_dispatch_io);
405 if (r < 0)
406 return r;
407 }
408
409 return 0;
410}
411
412static void path_set_state(Path *p, PathState state) {
413 PathState old_state;
414 assert(p);
415
416 old_state = p->state;
417 p->state = state;
418
419 if (state != PATH_WAITING &&
420 (state != PATH_RUNNING || p->inotify_triggered))
421 path_unwatch(p);
422
423 if (state != old_state)
424 log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state));
425
426 unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], 0);
427}
428
429static void path_enter_waiting(Path *p, bool initial, bool recheck);
430
431static int path_coldplug(Unit *u) {
432 Path *p = PATH(u);
433
434 assert(p);
435 assert(p->state == PATH_DEAD);
436
437 if (p->deserialized_state != p->state) {
438
439 if (IN_SET(p->deserialized_state, PATH_WAITING, PATH_RUNNING))
440 path_enter_waiting(p, true, true);
441 else
442 path_set_state(p, p->deserialized_state);
443 }
444
445 return 0;
446}
447
448static void path_enter_dead(Path *p, PathResult f) {
449 assert(p);
450
451 if (p->result == PATH_SUCCESS)
452 p->result = f;
453
454 if (p->result != PATH_SUCCESS)
455 log_unit_warning(UNIT(p), "Failed with result '%s'.", path_result_to_string(p->result));
456
457 path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
458}
459
460static void path_enter_running(Path *p) {
461 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
462 Unit *trigger;
463 int r;
464
465 assert(p);
466
467 /* Don't start job if we are supposed to go down */
468 if (unit_stop_pending(UNIT(p)))
469 return;
470
471 trigger = UNIT_TRIGGER(UNIT(p));
472 if (!trigger) {
473 log_unit_error(UNIT(p), "Unit to trigger vanished.");
474 path_enter_dead(p, PATH_FAILURE_RESOURCES);
475 return;
476 }
477
478 r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL);
479 if (r < 0)
480 goto fail;
481
482 p->inotify_triggered = false;
483
484 r = path_watch(p);
485 if (r < 0)
486 goto fail;
487
488 path_set_state(p, PATH_RUNNING);
489 return;
490
491fail:
492 log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r));
493 path_enter_dead(p, PATH_FAILURE_RESOURCES);
494}
495
496static bool path_check_good(Path *p, bool initial) {
497 PathSpec *s;
498 bool good = false;
499
500 assert(p);
501
502 LIST_FOREACH(spec, s, p->specs) {
503 good = path_spec_check_good(s, initial);
504
505 if (good)
506 break;
507 }
508
509 return good;
510}
511
512static void path_enter_waiting(Path *p, bool initial, bool recheck) {
513 int r;
514
515 if (recheck)
516 if (path_check_good(p, initial)) {
517 log_unit_debug(UNIT(p), "Got triggered.");
518 path_enter_running(p);
519 return;
520 }
521
522 r = path_watch(p);
523 if (r < 0)
524 goto fail;
525
526 /* Hmm, so now we have created inotify watches, but the file
527 * might have appeared/been removed by now, so we must
528 * recheck */
529
530 if (recheck)
531 if (path_check_good(p, false)) {
532 log_unit_debug(UNIT(p), "Got triggered.");
533 path_enter_running(p);
534 return;
535 }
536
537 path_set_state(p, PATH_WAITING);
538 return;
539
540fail:
541 log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m");
542 path_enter_dead(p, PATH_FAILURE_RESOURCES);
543}
544
545static void path_mkdir(Path *p) {
546 PathSpec *s;
547
548 assert(p);
549
550 if (!p->make_directory)
551 return;
552
553 LIST_FOREACH(spec, s, p->specs)
554 path_spec_mkdir(s, p->directory_mode);
555}
556
557static int path_start(Unit *u) {
558 Path *p = PATH(u);
559 Unit *trigger;
560 int r;
561
562 assert(p);
563 assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED));
564
565 trigger = UNIT_TRIGGER(u);
566 if (!trigger || trigger->load_state != UNIT_LOADED) {
567 log_unit_error(u, "Refusing to start, unit to trigger not loaded.");
568 return -ENOENT;
569 }
570
571 r = unit_start_limit_test(u);
572 if (r < 0) {
573 path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);
574 return r;
575 }
576
577 r = unit_acquire_invocation_id(u);
578 if (r < 0)
579 return r;
580
581 path_mkdir(p);
582
583 p->result = PATH_SUCCESS;
584 path_enter_waiting(p, true, true);
585
586 return 1;
587}
588
589static int path_stop(Unit *u) {
590 Path *p = PATH(u);
591
592 assert(p);
593 assert(IN_SET(p->state, PATH_WAITING, PATH_RUNNING));
594
595 path_enter_dead(p, PATH_SUCCESS);
596 return 1;
597}
598
599static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
600 Path *p = PATH(u);
601
602 assert(u);
603 assert(f);
604 assert(fds);
605
606 unit_serialize_item(u, f, "state", path_state_to_string(p->state));
607 unit_serialize_item(u, f, "result", path_result_to_string(p->result));
608
609 return 0;
610}
611
612static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
613 Path *p = PATH(u);
614
615 assert(u);
616 assert(key);
617 assert(value);
618 assert(fds);
619
620 if (streq(key, "state")) {
621 PathState state;
622
623 state = path_state_from_string(value);
624 if (state < 0)
625 log_unit_debug(u, "Failed to parse state value: %s", value);
626 else
627 p->deserialized_state = state;
628
629 } else if (streq(key, "result")) {
630 PathResult f;
631
632 f = path_result_from_string(value);
633 if (f < 0)
634 log_unit_debug(u, "Failed to parse result value: %s", value);
635 else if (f != PATH_SUCCESS)
636 p->result = f;
637
638 } else
639 log_unit_debug(u, "Unknown serialization key: %s", key);
640
641 return 0;
642}
643
644_pure_ static UnitActiveState path_active_state(Unit *u) {
645 assert(u);
646
647 return state_translation_table[PATH(u)->state];
648}
649
650_pure_ static const char *path_sub_state_to_string(Unit *u) {
651 assert(u);
652
653 return path_state_to_string(PATH(u)->state);
654}
655
656static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
657 PathSpec *s = userdata;
658 Path *p;
659 int changed;
660
661 assert(s);
662 assert(s->unit);
663 assert(fd >= 0);
664
665 p = PATH(s->unit);
666
667 if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
668 return 0;
669
670 /* log_debug("inotify wakeup on %s.", u->id); */
671
672 LIST_FOREACH(spec, s, p->specs)
673 if (path_spec_owns_inotify_fd(s, fd))
674 break;
675
676 if (!s) {
677 log_error("Got event on unknown fd.");
678 goto fail;
679 }
680
681 changed = path_spec_fd_event(s, revents);
682 if (changed < 0)
683 goto fail;
684
685 /* If we are already running, then remember that one event was
686 * dispatched so that we restart the service only if something
687 * actually changed on disk */
688 p->inotify_triggered = true;
689
690 if (changed)
691 path_enter_running(p);
692 else
693 path_enter_waiting(p, false, true);
694
695 return 0;
696
697fail:
698 path_enter_dead(p, PATH_FAILURE_RESOURCES);
699 return 0;
700}
701
702static void path_trigger_notify(Unit *u, Unit *other) {
703 Path *p = PATH(u);
704
705 assert(u);
706 assert(other);
707
708 /* Invoked whenever the unit we trigger changes state or gains
709 * or loses a job */
710
711 if (other->load_state != UNIT_LOADED)
712 return;
713
714 if (p->state == PATH_RUNNING &&
715 UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
716 log_unit_debug(UNIT(p), "Got notified about unit deactivation.");
717
718 /* Hmm, so inotify was triggered since the
719 * last activation, so I guess we need to
720 * recheck what is going on. */
721 path_enter_waiting(p, false, p->inotify_triggered);
722 }
723}
724
725static void path_reset_failed(Unit *u) {
726 Path *p = PATH(u);
727
728 assert(p);
729
730 if (p->state == PATH_FAILED)
731 path_set_state(p, PATH_DEAD);
732
733 p->result = PATH_SUCCESS;
734}
735
736static const char* const path_type_table[_PATH_TYPE_MAX] = {
737 [PATH_EXISTS] = "PathExists",
738 [PATH_EXISTS_GLOB] = "PathExistsGlob",
739 [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty",
740 [PATH_CHANGED] = "PathChanged",
741 [PATH_MODIFIED] = "PathModified",
742};
743
744DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
745
746static const char* const path_result_table[_PATH_RESULT_MAX] = {
747 [PATH_SUCCESS] = "success",
748 [PATH_FAILURE_RESOURCES] = "resources",
749 [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit",
750};
751
752DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
753
754const UnitVTable path_vtable = {
755 .object_size = sizeof(Path),
756
757 .sections =
758 "Unit\0"
759 "Path\0"
760 "Install\0",
761 .private_section = "Path",
762
763 .can_transient = true,
764
765 .init = path_init,
766 .done = path_done,
767 .load = path_load,
768
769 .coldplug = path_coldplug,
770
771 .dump = path_dump,
772
773 .start = path_start,
774 .stop = path_stop,
775
776 .serialize = path_serialize,
777 .deserialize_item = path_deserialize_item,
778
779 .active_state = path_active_state,
780 .sub_state_to_string = path_sub_state_to_string,
781
782 .trigger_notify = path_trigger_notify,
783
784 .reset_failed = path_reset_failed,
785
786 .bus_vtable = bus_path_vtable,
787 .bus_set_property = bus_path_set_property,
788};