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