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