]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/path.c
update TODO
[thirdparty/systemd.git] / src / path.c
CommitLineData
d6c9574f 1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
01f78473
LP
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 General Public License as published by
10 the Free Software Foundation; either version 2 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 General Public License for more details.
17
18 You should have received a copy of the GNU 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 "dbus-path.h"
a40eb732 32#include "special.h"
398ef8ba 33#include "bus-errors.h"
01f78473
LP
34
35static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
36 [PATH_DEAD] = UNIT_INACTIVE,
37 [PATH_WAITING] = UNIT_ACTIVE,
38 [PATH_RUNNING] = UNIT_ACTIVE,
fdf20a31 39 [PATH_FAILED] = UNIT_FAILED
01f78473
LP
40};
41
0e456f97
LP
42static void path_init(Unit *u) {
43 Path *p = PATH(u);
44
45 assert(u);
46 assert(u->meta.load_state == UNIT_STUB);
47
48 p->directory_mode = 0755;
49}
50
62347bc2
LP
51static void path_unwatch_one(Path *p, PathSpec *s) {
52
53 if (s->inotify_fd < 0)
54 return;
55
56 unit_unwatch_fd(UNIT(p), &s->watch);
57
58 close_nointr_nofail(s->inotify_fd);
59 s->inotify_fd = -1;
60}
61
01f78473
LP
62static void path_done(Unit *u) {
63 Path *p = PATH(u);
64 PathSpec *s;
65
66 assert(p);
67
68 while ((s = p->specs)) {
62347bc2 69 path_unwatch_one(p, s);
01f78473 70 LIST_REMOVE(PathSpec, spec, p->specs, s);
62347bc2 71 free(s->path);
01f78473
LP
72 free(s);
73 }
74}
75
76int path_add_one_mount_link(Path *p, Mount *m) {
77 PathSpec *s;
78 int r;
79
80 assert(p);
81 assert(m);
82
83 if (p->meta.load_state != UNIT_LOADED ||
84 m->meta.load_state != UNIT_LOADED)
85 return 0;
86
87 LIST_FOREACH(spec, s, p->specs) {
88
89 if (!path_startswith(s->path, m->where))
90 continue;
91
2c966c03 92 if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0)
01f78473
LP
93 return r;
94 }
95
96 return 0;
97}
98
99static int path_add_mount_links(Path *p) {
100 Meta *other;
101 int r;
102
103 assert(p);
104
ab5c3e3f 105 LIST_FOREACH(units_by_type, other, p->meta.manager->units_by_type[UNIT_MOUNT])
01f78473
LP
106 if ((r = path_add_one_mount_link(p, (Mount*) other)) < 0)
107 return r;
108
109 return 0;
110}
111
112static int path_verify(Path *p) {
113 assert(p);
114
4cd1fbcc 115 if (p->meta.load_state != UNIT_LOADED)
01f78473
LP
116 return 0;
117
118 if (!p->specs) {
119 log_error("%s lacks path setting. Refusing.", p->meta.id);
120 return -EINVAL;
121 }
122
123 return 0;
124}
125
6c155fe3
LP
126static int path_add_default_dependencies(Path *p) {
127 int r;
128
129 assert(p);
130
2a77d31d
LP
131 if (p->meta.manager->running_as == MANAGER_SYSTEM) {
132 if ((r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
133 return r;
134
6c155fe3
LP
135 if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
136 return r;
2a77d31d 137 }
6c155fe3 138
ead8e478 139 return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
6c155fe3
LP
140}
141
01f78473
LP
142static int path_load(Unit *u) {
143 Path *p = PATH(u);
144 int r;
145
146 assert(u);
147 assert(u->meta.load_state == UNIT_STUB);
148
149 if ((r = unit_load_fragment_and_dropin(u)) < 0)
150 return r;
151
152 if (u->meta.load_state == UNIT_LOADED) {
153
154 if (!p->unit)
155 if ((r = unit_load_related_unit(u, ".service", &p->unit)))
156 return r;
157
158 if ((r = unit_add_dependency(u, UNIT_BEFORE, p->unit, true)) < 0)
159 return r;
160
161 if ((r = path_add_mount_links(p)) < 0)
162 return r;
a40eb732 163
a40eb732 164 if (p->meta.default_dependencies)
6c155fe3 165 if ((r = path_add_default_dependencies(p)) < 0)
a40eb732 166 return r;
01f78473
LP
167 }
168
169 return path_verify(p);
170}
171
172static void path_dump(Unit *u, FILE *f, const char *prefix) {
173 Path *p = PATH(u);
01f78473
LP
174 PathSpec *s;
175
e364ad06
LP
176 assert(p);
177 assert(f);
01f78473
LP
178
179 fprintf(f,
180 "%sPath State: %s\n"
0e456f97
LP
181 "%sUnit: %s\n"
182 "%sMakeDirectory: %s\n"
183 "%sDirectoryMode: %04o\n",
01f78473 184 prefix, path_state_to_string(p->state),
0e456f97
LP
185 prefix, p->unit->meta.id,
186 prefix, yes_no(p->make_directory),
187 prefix, p->directory_mode);
01f78473
LP
188
189 LIST_FOREACH(spec, s, p->specs)
190 fprintf(f,
191 "%s%s: %s\n",
192 prefix,
193 path_type_to_string(s->type),
194 s->path);
01f78473
LP
195}
196
01f78473
LP
197static int path_watch_one(Path *p, PathSpec *s) {
198 static const int flags_table[_PATH_TYPE_MAX] = {
782195a3 199 [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
01f78473 200 [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
782195a3 201 [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO
01f78473
LP
202 };
203
204 bool exists = false;
67575eef 205 char *k, *slash;
01f78473
LP
206 int r;
207
208 assert(p);
209 assert(s);
210
211 path_unwatch_one(p, s);
212
213 if (!(k = strdup(s->path)))
214 return -ENOMEM;
215
216 if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) {
217 r = -errno;
218 goto fail;
219 }
220
221 if (unit_watch_fd(UNIT(p), s->inotify_fd, EPOLLIN, &s->watch) < 0) {
222 r = -errno;
223 goto fail;
224 }
225
226 if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0)
227 exists = true;
228
67575eef 229 do {
01f78473 230 int flags;
01f78473
LP
231
232 /* This assumes the path was passed through path_kill_slashes()! */
233 if (!(slash = strrchr(k, '/')))
234 break;
235
67575eef
MS
236 /* Trim the path at the last slash. Keep the slash if it's the root dir. */
237 slash[slash == k] = 0;
01f78473 238
3fc546f9 239 flags = IN_MOVE_SELF;
01f78473 240 if (!exists)
3fc546f9 241 flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
01f78473
LP
242
243 if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
244 exists = true;
67575eef 245 } while (slash != k);
01f78473
LP
246
247 return 0;
248
249fail:
250 free(k);
251
252 path_unwatch_one(p, s);
253 return r;
254}
255
256static void path_unwatch(Path *p) {
257 PathSpec *s;
258
259 assert(p);
260
261 LIST_FOREACH(spec, s, p->specs)
262 path_unwatch_one(p, s);
263}
264
265static int path_watch(Path *p) {
266 int r;
267 PathSpec *s;
268
269 assert(p);
270
271 LIST_FOREACH(spec, s, p->specs)
272 if ((r = path_watch_one(p, s)) < 0)
273 return r;
274
275 return 0;
276}
277
278static void path_set_state(Path *p, PathState state) {
279 PathState old_state;
280 assert(p);
281
282 old_state = p->state;
283 p->state = state;
284
672028dc
LP
285 if (state != PATH_WAITING &&
286 (state != PATH_RUNNING || p->inotify_triggered))
01f78473
LP
287 path_unwatch(p);
288
289 if (state != old_state)
290 log_debug("%s changed %s -> %s",
291 p->meta.id,
292 path_state_to_string(old_state),
293 path_state_to_string(state));
294
e2f3b44c 295 unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true);
01f78473
LP
296}
297
ac3f50ca 298static void path_enter_waiting(Path *p, bool initial, bool recheck);
01f78473
LP
299
300static int path_coldplug(Unit *u) {
301 Path *p = PATH(u);
302
303 assert(p);
304 assert(p->state == PATH_DEAD);
305
306 if (p->deserialized_state != p->state) {
307
308 if (p->deserialized_state == PATH_WAITING ||
309 p->deserialized_state == PATH_RUNNING)
ac3f50ca 310 path_enter_waiting(p, true, true);
01f78473
LP
311 else
312 path_set_state(p, p->deserialized_state);
313 }
314
315 return 0;
316}
317
318static void path_enter_dead(Path *p, bool success) {
319 assert(p);
320
321 if (!success)
322 p->failure = true;
323
fdf20a31 324 path_set_state(p, p->failure ? PATH_FAILED : PATH_DEAD);
01f78473
LP
325}
326
327static void path_enter_running(Path *p) {
328 int r;
398ef8ba
LP
329 DBusError error;
330
01f78473 331 assert(p);
398ef8ba 332 dbus_error_init(&error);
01f78473 333
ba3e67a7
LP
334 /* Don't start job if we are supposed to go down */
335 if (p->meta.job && p->meta.job->type == JOB_STOP)
336 return;
337
398ef8ba 338 if ((r = manager_add_job(p->meta.manager, JOB_START, p->unit, JOB_REPLACE, true, &error, NULL)) < 0)
01f78473
LP
339 goto fail;
340
672028dc
LP
341 p->inotify_triggered = false;
342
343 if ((r = path_watch(p)) < 0)
344 goto fail;
345
01f78473
LP
346 path_set_state(p, PATH_RUNNING);
347 return;
348
349fail:
398ef8ba 350 log_warning("%s failed to queue unit startup job: %s", p->meta.id, bus_error(&error, r));
01f78473 351 path_enter_dead(p, false);
398ef8ba
LP
352
353 dbus_error_free(&error);
01f78473
LP
354}
355
ac3f50ca 356static bool path_check_good(Path *p, bool initial) {
01f78473 357 PathSpec *s;
01f78473
LP
358 bool good = false;
359
ac3f50ca 360 assert(p);
672028dc 361
01f78473
LP
362 LIST_FOREACH(spec, s, p->specs) {
363
364 switch (s->type) {
365
366 case PATH_EXISTS:
367 good = access(s->path, F_OK) >= 0;
368 break;
369
36af55d9
LP
370 case PATH_DIRECTORY_NOT_EMPTY: {
371 int k;
372
373 k = dir_is_empty(s->path);
374 good = !(k == -ENOENT || k > 0);
01f78473 375 break;
36af55d9 376 }
01f78473
LP
377
378 case PATH_CHANGED: {
379 bool b;
380
381 b = access(s->path, F_OK) >= 0;
382 good = !initial && b != s->previous_exists;
383 s->previous_exists = b;
384 break;
385 }
386
387 default:
388 ;
389 }
390
391 if (good)
392 break;
393 }
394
ac3f50ca
LP
395 return good;
396}
01f78473 397
ac3f50ca
LP
398static void path_enter_waiting(Path *p, bool initial, bool recheck) {
399 int r;
0595f9a1 400
ac3f50ca
LP
401 if (recheck)
402 if (path_check_good(p, initial)) {
403 log_debug("%s got triggered.", p->meta.id);
404 path_enter_running(p);
405 return;
406 }
407
408 if ((r = path_watch(p)) < 0)
409 goto fail;
410
411 /* Hmm, so now we have created inotify watches, but the file
412 * might have appeared/been removed by now, so we must
413 * recheck */
414
415 if (recheck)
416 if (path_check_good(p, false)) {
417 log_debug("%s got triggered.", p->meta.id);
418 path_enter_running(p);
419 return;
420 }
01f78473
LP
421
422 path_set_state(p, PATH_WAITING);
423 return;
424
425fail:
426 log_warning("%s failed to enter waiting state: %s", p->meta.id, strerror(-r));
427 path_enter_dead(p, false);
428}
429
0e456f97
LP
430static void path_mkdir(Path *p) {
431 PathSpec *s;
432
433 assert(p);
434
435 if (!p->make_directory)
436 return;
437
438 LIST_FOREACH(spec, s, p->specs) {
439 int r;
440
441 if (s->type == PATH_EXISTS)
442 continue;
443
444 if ((r = mkdir_p(s->path, p->directory_mode)) < 0)
445 log_warning("mkdir(%s) failed: %s", s->path, strerror(-r));
446 }
447}
448
01f78473
LP
449static int path_start(Unit *u) {
450 Path *p = PATH(u);
451
452 assert(p);
fdf20a31 453 assert(p->state == PATH_DEAD || p->state == PATH_FAILED);
01f78473
LP
454
455 if (p->unit->meta.load_state != UNIT_LOADED)
456 return -ENOENT;
457
0e456f97
LP
458 path_mkdir(p);
459
01f78473 460 p->failure = false;
ac3f50ca 461 path_enter_waiting(p, true, true);
00dc5d76 462
01f78473
LP
463 return 0;
464}
465
466static int path_stop(Unit *u) {
467 Path *p = PATH(u);
468
469 assert(p);
470 assert(p->state == PATH_WAITING || p->state == PATH_RUNNING);
471
472 path_enter_dead(p, true);
473 return 0;
474}
475
476static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
477 Path *p = PATH(u);
478
479 assert(u);
480 assert(f);
481 assert(fds);
482
483 unit_serialize_item(u, f, "state", path_state_to_string(p->state));
484
485 return 0;
486}
487
488static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
489 Path *p = PATH(u);
490
491 assert(u);
492 assert(key);
493 assert(value);
494 assert(fds);
495
496 if (streq(key, "state")) {
497 PathState state;
498
499 if ((state = path_state_from_string(value)) < 0)
500 log_debug("Failed to parse state value %s", value);
501 else
502 p->deserialized_state = state;
503 } else
504 log_debug("Unknown serialization key '%s'", key);
505
506 return 0;
507}
508
509static UnitActiveState path_active_state(Unit *u) {
510 assert(u);
511
512 return state_translation_table[PATH(u)->state];
513}
514
515static const char *path_sub_state_to_string(Unit *u) {
516 assert(u);
517
518 return path_state_to_string(PATH(u)->state);
519}
520
521static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
522 Path *p = PATH(u);
523 int l;
524 ssize_t k;
f601daa7
LP
525 uint8_t *buf = NULL;
526 struct inotify_event *e;
01f78473 527 PathSpec *s;
672028dc 528 bool changed;
01f78473
LP
529
530 assert(p);
531 assert(fd >= 0);
532
672028dc
LP
533 if (p->state != PATH_WAITING &&
534 p->state != PATH_RUNNING)
01f78473
LP
535 return;
536
4313fc2c 537 /* log_debug("inotify wakeup on %s.", u->meta.id); */
01f78473
LP
538
539 if (events != EPOLLIN) {
540 log_error("Got Invalid poll event on inotify.");
541 goto fail;
542 }
543
544 LIST_FOREACH(spec, s, p->specs)
545 if (s->inotify_fd == fd)
546 break;
547
548 if (!s) {
549 log_error("Got event on unknown fd.");
550 goto fail;
551 }
552
553 if (ioctl(fd, FIONREAD, &l) < 0) {
554 log_error("FIONREAD failed: %s", strerror(errno));
555 goto fail;
556 }
557
ac3f50ca
LP
558 assert(l > 0);
559
01f78473
LP
560 if (!(buf = malloc(l))) {
561 log_error("Failed to allocate buffer: %s", strerror(-ENOMEM));
562 goto fail;
563 }
564
565 if ((k = read(fd, buf, l)) < 0) {
566 log_error("Failed to read inotify event: %s", strerror(-errno));
567 goto fail;
568 }
569
672028dc
LP
570 /* If we are already running, then remember that one event was
571 * dispatched so that we restart the service only if something
572 * actually changed on disk */
573 p->inotify_triggered = true;
574
f601daa7 575 e = (struct inotify_event*) buf;
01f78473 576
672028dc 577 changed = false;
f601daa7
LP
578 while (k > 0) {
579 size_t step;
580
581 if (s->type == PATH_CHANGED && s->primary_wd == e->wd)
672028dc 582 changed = true;
f601daa7
LP
583
584 step = sizeof(struct inotify_event) + e->len;
585 assert(step <= (size_t) k);
586
587 e = (struct inotify_event*) ((uint8_t*) e + step);
588 k -= step;
589 }
01f78473 590
672028dc
LP
591 if (changed)
592 path_enter_running(p);
593 else
ac3f50ca 594 path_enter_waiting(p, false, true);
672028dc 595
01f78473
LP
596 free(buf);
597
598 return;
599
600fail:
601 free(buf);
602 path_enter_dead(p, false);
603}
604
605void path_unit_notify(Unit *u, UnitActiveState new_state) {
606 char *n;
607 int r;
608 Iterator i;
609
610 if (u->meta.type == UNIT_PATH)
611 return;
612
613 SET_FOREACH(n, u->meta.names, i) {
614 char *k;
615 Unit *t;
616 Path *p;
617
618 if (!(k = unit_name_change_suffix(n, ".path"))) {
619 r = -ENOMEM;
620 goto fail;
621 }
622
623 t = manager_get_unit(u->meta.manager, k);
624 free(k);
625
626 if (!t)
627 continue;
628
629 if (t->meta.load_state != UNIT_LOADED)
630 continue;
631
632 p = PATH(t);
633
634 if (p->unit != u)
635 continue;
636
637 if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) {
638 log_debug("%s got notified about unit deactivation.", p->meta.id);
672028dc
LP
639
640 /* Hmm, so inotify was triggered since the
641 * last activation, so I guess we need to
642 * recheck what is going on. */
ac3f50ca 643 path_enter_waiting(p, false, p->inotify_triggered);
01f78473
LP
644 }
645 }
646
647 return;
648
649fail:
650 log_error("Failed find path unit: %s", strerror(-r));
651}
652
fdf20a31 653static void path_reset_failed(Unit *u) {
5632e374
LP
654 Path *p = PATH(u);
655
656 assert(p);
657
fdf20a31 658 if (p->state == PATH_FAILED)
5632e374
LP
659 path_set_state(p, PATH_DEAD);
660
661 p->failure = false;
662}
663
01f78473
LP
664static const char* const path_state_table[_PATH_STATE_MAX] = {
665 [PATH_DEAD] = "dead",
666 [PATH_WAITING] = "waiting",
667 [PATH_RUNNING] = "running",
fdf20a31 668 [PATH_FAILED] = "failed"
01f78473
LP
669};
670
671DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
672
673static const char* const path_type_table[_PATH_TYPE_MAX] = {
674 [PATH_EXISTS] = "PathExists",
675 [PATH_CHANGED] = "PathChanged",
676 [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty"
677};
678
679DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
680
681const UnitVTable path_vtable = {
682 .suffix = ".path",
683
0e456f97 684 .init = path_init,
01f78473
LP
685 .done = path_done,
686 .load = path_load,
687
688 .coldplug = path_coldplug,
689
690 .dump = path_dump,
691
692 .start = path_start,
693 .stop = path_stop,
694
695 .serialize = path_serialize,
696 .deserialize_item = path_deserialize_item,
697
698 .active_state = path_active_state,
699 .sub_state_to_string = path_sub_state_to_string,
700
701 .fd_event = path_fd_event,
702
fdf20a31 703 .reset_failed = path_reset_failed,
5632e374 704
c4e2ceae 705 .bus_interface = "org.freedesktop.systemd1.Path",
01f78473
LP
706 .bus_message_handler = bus_path_message_handler
707};