1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
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.
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.
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/>.
22 #include <sys/inotify.h>
23 #include <sys/epoll.h>
24 #include <sys/ioctl.h>
29 #include "unit-name.h"
32 #include "dbus-path.h"
34 #include "bus-errors.h"
35 #include "path-util.h"
37 static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
38 [PATH_DEAD] = UNIT_INACTIVE,
39 [PATH_WAITING] = UNIT_ACTIVE,
40 [PATH_RUNNING] = UNIT_ACTIVE,
41 [PATH_FAILED] = UNIT_FAILED
44 int path_spec_watch(PathSpec *s, Unit *u) {
46 static const int flags_table[_PATH_TYPE_MAX] = {
47 [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
48 [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
49 [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
50 [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,
51 [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO
61 path_spec_unwatch(s, u);
63 if (!(k = strdup(s->path)))
66 if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) {
71 if (unit_watch_fd(u, s->inotify_fd, EPOLLIN, &s->watch) < 0) {
76 if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0)
82 /* This assumes the path was passed through path_kill_slashes()! */
83 if (!(slash = strrchr(k, '/')))
86 /* Trim the path at the last slash. Keep the slash if it's the root dir. */
87 slash[slash == k] = 0;
91 flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
93 if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
102 path_spec_unwatch(s, u);
106 void path_spec_unwatch(PathSpec *s, Unit *u) {
108 if (s->inotify_fd < 0)
111 unit_unwatch_fd(u, &s->watch);
113 close_nointr_nofail(s->inotify_fd);
117 int path_spec_fd_event(PathSpec *s, uint32_t events) {
119 struct inotify_event *e;
124 if (events != EPOLLIN) {
125 log_error("Got Invalid poll event on inotify.");
130 if (ioctl(s->inotify_fd, FIONREAD, &l) < 0) {
131 log_error("FIONREAD failed: %m");
138 if (!(buf = malloc(l))) {
139 log_error("Failed to allocate buffer: %m");
144 if ((k = read(s->inotify_fd, buf, l)) < 0) {
145 log_error("Failed to read inotify event: %m");
150 e = (struct inotify_event*) buf;
155 if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) &&
156 s->primary_wd == e->wd)
159 step = sizeof(struct inotify_event) + e->len;
160 assert(step <= (size_t) k);
162 e = (struct inotify_event*) ((uint8_t*) e + step);
170 static bool path_spec_check_good(PathSpec *s, bool initial) {
176 good = access(s->path, F_OK) >= 0;
179 case PATH_EXISTS_GLOB:
180 good = glob_exists(s->path) > 0;
183 case PATH_DIRECTORY_NOT_EMPTY: {
186 k = dir_is_empty(s->path);
187 good = !(k == -ENOENT || k > 0);
192 case PATH_MODIFIED: {
195 b = access(s->path, F_OK) >= 0;
196 good = !initial && b != s->previous_exists;
197 s->previous_exists = b;
208 static bool path_spec_startswith(PathSpec *s, const char *what) {
209 return path_startswith(s->path, what);
212 static void path_spec_mkdir(PathSpec *s, mode_t mode) {
215 if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB)
218 if ((r = mkdir_p_label(s->path, mode)) < 0)
219 log_warning("mkdir(%s) failed: %s", s->path, strerror(-r));
222 static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
226 path_type_to_string(s->type),
230 void path_spec_done(PathSpec *s) {
232 assert(s->inotify_fd == -1);
237 static void path_init(Unit *u) {
241 assert(u->load_state == UNIT_STUB);
243 p->directory_mode = 0755;
246 static void path_done(Unit *u) {
252 unit_ref_unset(&p->unit);
254 while ((s = p->specs)) {
255 path_spec_unwatch(s, u);
256 LIST_REMOVE(PathSpec, spec, p->specs, s);
262 int path_add_one_mount_link(Path *p, Mount *m) {
269 if (UNIT(p)->load_state != UNIT_LOADED ||
270 UNIT(m)->load_state != UNIT_LOADED)
273 LIST_FOREACH(spec, s, p->specs) {
275 if (!path_spec_startswith(s, m->where))
278 if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0)
285 static int path_add_mount_links(Path *p) {
291 LIST_FOREACH(units_by_type, other, UNIT(p)->manager->units_by_type[UNIT_MOUNT])
292 if ((r = path_add_one_mount_link(p, MOUNT(other))) < 0)
298 static int path_verify(Path *p) {
301 if (UNIT(p)->load_state != UNIT_LOADED)
305 log_error("%s lacks path setting. Refusing.", UNIT(p)->id);
312 static int path_add_default_dependencies(Path *p) {
317 if (UNIT(p)->manager->running_as == MANAGER_SYSTEM) {
318 if ((r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
321 if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
325 return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
328 static int path_load(Unit *u) {
333 assert(u->load_state == UNIT_STUB);
335 if ((r = unit_load_fragment_and_dropin(u)) < 0)
338 if (u->load_state == UNIT_LOADED) {
340 if (!UNIT_DEREF(p->unit)) {
343 r = unit_load_related_unit(u, ".service", &x);
347 unit_ref_set(&p->unit, x);
350 r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(p->unit), true);
354 if ((r = path_add_mount_links(p)) < 0)
357 if (UNIT(p)->default_dependencies)
358 if ((r = path_add_default_dependencies(p)) < 0)
362 return path_verify(p);
365 static void path_dump(Unit *u, FILE *f, const char *prefix) {
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, UNIT_DEREF(p->unit)->id,
381 prefix, yes_no(p->make_directory),
382 prefix, p->directory_mode);
384 LIST_FOREACH(spec, s, p->specs)
385 path_spec_dump(s, f, prefix);
388 static void path_unwatch(Path *p) {
393 LIST_FOREACH(spec, s, p->specs)
394 path_spec_unwatch(s, UNIT(p));
397 static int path_watch(Path *p) {
403 LIST_FOREACH(spec, s, p->specs)
404 if ((r = path_spec_watch(s, UNIT(p))) < 0)
410 static void path_set_state(Path *p, PathState state) {
414 old_state = p->state;
417 if (state != PATH_WAITING &&
418 (state != PATH_RUNNING || p->inotify_triggered))
421 if (state != old_state)
422 log_debug("%s changed %s -> %s",
424 path_state_to_string(old_state),
425 path_state_to_string(state));
427 unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true);
430 static void path_enter_waiting(Path *p, bool initial, bool recheck);
432 static int path_coldplug(Unit *u) {
436 assert(p->state == PATH_DEAD);
438 if (p->deserialized_state != p->state) {
440 if (p->deserialized_state == PATH_WAITING ||
441 p->deserialized_state == PATH_RUNNING)
442 path_enter_waiting(p, true, true);
444 path_set_state(p, p->deserialized_state);
450 static void path_enter_dead(Path *p, PathResult f) {
453 if (f != PATH_SUCCESS)
456 path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
459 static void path_enter_running(Path *p) {
464 dbus_error_init(&error);
466 /* Don't start job if we are supposed to go down */
467 if (UNIT(p)->job && UNIT(p)->job->type == JOB_STOP)
470 if ((r = manager_add_job(UNIT(p)->manager, JOB_START, UNIT_DEREF(p->unit), JOB_REPLACE, true, &error, NULL)) < 0)
473 p->inotify_triggered = false;
475 if ((r = path_watch(p)) < 0)
478 path_set_state(p, PATH_RUNNING);
482 log_warning("%s failed to queue unit startup job: %s", UNIT(p)->id, bus_error(&error, r));
483 path_enter_dead(p, PATH_FAILURE_RESOURCES);
485 dbus_error_free(&error);
488 static bool path_check_good(Path *p, bool initial) {
494 LIST_FOREACH(spec, s, p->specs) {
495 good = path_spec_check_good(s, initial);
504 static void path_enter_waiting(Path *p, bool initial, bool recheck) {
508 if (path_check_good(p, initial)) {
509 log_debug("%s got triggered.", UNIT(p)->id);
510 path_enter_running(p);
514 if ((r = path_watch(p)) < 0)
517 /* Hmm, so now we have created inotify watches, but the file
518 * might have appeared/been removed by now, so we must
522 if (path_check_good(p, false)) {
523 log_debug("%s got triggered.", UNIT(p)->id);
524 path_enter_running(p);
528 path_set_state(p, PATH_WAITING);
532 log_warning("%s failed to enter waiting state: %s", UNIT(p)->id, strerror(-r));
533 path_enter_dead(p, PATH_FAILURE_RESOURCES);
536 static void path_mkdir(Path *p) {
541 if (!p->make_directory)
544 LIST_FOREACH(spec, s, p->specs)
545 path_spec_mkdir(s, p->directory_mode);
548 static int path_start(Unit *u) {
552 assert(p->state == PATH_DEAD || p->state == PATH_FAILED);
554 if (UNIT_DEREF(p->unit)->load_state != UNIT_LOADED)
559 p->result = PATH_SUCCESS;
560 path_enter_waiting(p, true, true);
565 static int path_stop(Unit *u) {
569 assert(p->state == PATH_WAITING || p->state == PATH_RUNNING);
571 path_enter_dead(p, PATH_SUCCESS);
575 static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
582 unit_serialize_item(u, f, "state", path_state_to_string(p->state));
583 unit_serialize_item(u, f, "result", path_result_to_string(p->result));
588 static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
596 if (streq(key, "state")) {
599 if ((state = path_state_from_string(value)) < 0)
600 log_debug("Failed to parse state value %s", value);
602 p->deserialized_state = state;
604 } else if (streq(key, "result")) {
607 f = path_result_from_string(value);
609 log_debug("Failed to parse result value %s", value);
610 else if (f != PATH_SUCCESS)
614 log_debug("Unknown serialization key '%s'", key);
619 static UnitActiveState path_active_state(Unit *u) {
622 return state_translation_table[PATH(u)->state];
625 static const char *path_sub_state_to_string(Unit *u) {
628 return path_state_to_string(PATH(u)->state);
631 static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
639 if (p->state != PATH_WAITING &&
640 p->state != PATH_RUNNING)
643 /* log_debug("inotify wakeup on %s.", u->id); */
645 LIST_FOREACH(spec, s, p->specs)
646 if (path_spec_owns_inotify_fd(s, fd))
650 log_error("Got event on unknown fd.");
654 changed = path_spec_fd_event(s, events);
658 /* If we are already running, then remember that one event was
659 * dispatched so that we restart the service only if something
660 * actually changed on disk */
661 p->inotify_triggered = true;
664 path_enter_running(p);
666 path_enter_waiting(p, false, true);
671 path_enter_dead(p, PATH_FAILURE_RESOURCES);
674 void path_unit_notify(Unit *u, UnitActiveState new_state) {
678 if (u->type == UNIT_PATH)
681 SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) {
684 if (k->type != UNIT_PATH)
687 if (k->load_state != UNIT_LOADED)
692 if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) {
693 log_debug("%s got notified about unit deactivation.", UNIT(p)->id);
695 /* Hmm, so inotify was triggered since the
696 * last activation, so I guess we need to
697 * recheck what is going on. */
698 path_enter_waiting(p, false, p->inotify_triggered);
703 static void path_reset_failed(Unit *u) {
708 if (p->state == PATH_FAILED)
709 path_set_state(p, PATH_DEAD);
711 p->result = PATH_SUCCESS;
714 static const char* const path_state_table[_PATH_STATE_MAX] = {
715 [PATH_DEAD] = "dead",
716 [PATH_WAITING] = "waiting",
717 [PATH_RUNNING] = "running",
718 [PATH_FAILED] = "failed"
721 DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
723 static const char* const path_type_table[_PATH_TYPE_MAX] = {
724 [PATH_EXISTS] = "PathExists",
725 [PATH_EXISTS_GLOB] = "PathExistsGlob",
726 [PATH_CHANGED] = "PathChanged",
727 [PATH_MODIFIED] = "PathModified",
728 [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty"
731 DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
733 static const char* const path_result_table[_PATH_RESULT_MAX] = {
734 [PATH_SUCCESS] = "success",
735 [PATH_FAILURE_RESOURCES] = "resources"
738 DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
740 const UnitVTable path_vtable = {
741 .object_size = sizeof(Path),
751 .coldplug = path_coldplug,
758 .serialize = path_serialize,
759 .deserialize_item = path_deserialize_item,
761 .active_state = path_active_state,
762 .sub_state_to_string = path_sub_state_to_string,
764 .fd_event = path_fd_event,
766 .reset_failed = path_reset_failed,
768 .bus_interface = "org.freedesktop.systemd1.Path",
769 .bus_message_handler = bus_path_message_handler,
770 .bus_invalidating_properties = bus_path_invalidating_properties