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"
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
45 int path_spec_watch(PathSpec *s, Unit *u) {
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
56 char _cleanup_free_ *k = NULL;
63 path_spec_unwatch(s, u);
69 s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
70 if (s->inotify_fd < 0) {
75 r = unit_watch_fd(u, s->inotify_fd, EPOLLIN, &s->watch);
79 s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type]);
80 if (s->primary_wd >= 0)
82 else if (errno != EACCES && errno != ENOENT) {
83 log_error("Failed to add watch on %s: %m", k);
91 /* This assumes the path was passed through path_kill_slashes()! */
92 slash = strrchr(k, '/');
96 /* Trim the path at the last slash. Keep the slash if it's the root dir. */
97 slash[slash == k] = 0;
101 flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
103 if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
105 else if (errno != EACCES && errno != ENOENT) {
106 log_error("Failed to add watch on %s: %m", k);
110 } while (slash != k);
113 log_error("Failed to add watch on any of the components of %s: %m",
122 path_spec_unwatch(s, u);
126 void path_spec_unwatch(PathSpec *s, Unit *u) {
128 if (s->inotify_fd < 0)
131 unit_unwatch_fd(u, &s->watch);
133 close_nointr_nofail(s->inotify_fd);
137 int path_spec_fd_event(PathSpec *s, uint32_t events) {
138 uint8_t _cleanup_free_ *buf = NULL;
139 struct inotify_event *e;
144 if (events != EPOLLIN) {
145 log_error("Got invalid poll event on inotify.");
149 if (ioctl(s->inotify_fd, FIONREAD, &l) < 0) {
150 log_error("FIONREAD failed: %m");
160 k = read(s->inotify_fd, buf, l);
162 log_error("Failed to read inotify event: %m");
166 e = (struct inotify_event*) buf;
171 if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) &&
172 s->primary_wd == e->wd)
175 step = sizeof(struct inotify_event) + e->len;
176 assert(step <= (size_t) k);
178 e = (struct inotify_event*) ((uint8_t*) e + step);
185 static bool path_spec_check_good(PathSpec *s, bool initial) {
191 good = access(s->path, F_OK) >= 0;
194 case PATH_EXISTS_GLOB:
195 good = glob_exists(s->path) > 0;
198 case PATH_DIRECTORY_NOT_EMPTY: {
201 k = dir_is_empty(s->path);
202 good = !(k == -ENOENT || k > 0);
207 case PATH_MODIFIED: {
210 b = access(s->path, F_OK) >= 0;
211 good = !initial && b != s->previous_exists;
212 s->previous_exists = b;
223 static bool path_spec_startswith(PathSpec *s, const char *what) {
224 return path_startswith(s->path, what);
227 static void path_spec_mkdir(PathSpec *s, mode_t mode) {
230 if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB)
233 r = mkdir_p_label(s->path, mode);
235 log_warning("mkdir(%s) failed: %s", s->path, strerror(-r));
238 static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
242 path_type_to_string(s->type),
246 void path_spec_done(PathSpec *s) {
248 assert(s->inotify_fd == -1);
253 static void path_init(Unit *u) {
257 assert(u->load_state == UNIT_STUB);
259 p->directory_mode = 0755;
262 void path_free_specs(Path *p) {
267 while ((s = p->specs)) {
268 path_spec_unwatch(s, UNIT(p));
269 LIST_REMOVE(PathSpec, spec, p->specs, s);
275 static void path_done(Unit *u) {
280 unit_ref_unset(&p->unit);
284 int path_add_one_mount_link(Path *p, Mount *m) {
291 if (UNIT(p)->load_state != UNIT_LOADED ||
292 UNIT(m)->load_state != UNIT_LOADED)
295 LIST_FOREACH(spec, s, p->specs) {
296 if (!path_spec_startswith(s, m->where))
299 r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES,
308 static int path_add_mount_links(Path *p) {
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));
323 static int path_verify(Path *p) {
326 if (UNIT(p)->load_state != UNIT_LOADED)
330 log_error_unit(UNIT(p)->id,
331 "%s lacks path setting. Refusing.", UNIT(p)->id);
338 static int path_add_default_dependencies(Path *p) {
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);
349 r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES,
350 SPECIAL_SYSINIT_TARGET, NULL, true);
355 return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS,
356 SPECIAL_SHUTDOWN_TARGET, NULL, true);
359 static int path_load(Unit *u) {
364 assert(u->load_state == UNIT_STUB);
366 r = unit_load_fragment_and_dropin(u);
370 if (u->load_state == UNIT_LOADED) {
372 if (!UNIT_DEREF(p->unit)) {
375 r = unit_load_related_unit(u, ".service", &x);
379 unit_ref_set(&p->unit, x);
382 r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS,
383 UNIT_DEREF(p->unit), true);
387 r = path_add_mount_links(p);
391 if (UNIT(p)->default_dependencies) {
392 r = path_add_default_dependencies(p);
398 return path_verify(p);
401 static void path_dump(Unit *u, FILE *f, const char *prefix) {
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);
420 LIST_FOREACH(spec, s, p->specs)
421 path_spec_dump(s, f, prefix);
424 static void path_unwatch(Path *p) {
429 LIST_FOREACH(spec, s, p->specs)
430 path_spec_unwatch(s, UNIT(p));
433 static int path_watch(Path *p) {
439 LIST_FOREACH(spec, s, p->specs) {
440 r = path_spec_watch(s, UNIT(p));
448 static void path_set_state(Path *p, PathState state) {
452 old_state = p->state;
455 if (state != PATH_WAITING &&
456 (state != PATH_RUNNING || p->inotify_triggered))
459 if (state != old_state)
460 log_debug("%s changed %s -> %s",
462 path_state_to_string(old_state),
463 path_state_to_string(state));
465 unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true);
468 static void path_enter_waiting(Path *p, bool initial, bool recheck);
470 static int path_coldplug(Unit *u) {
474 assert(p->state == PATH_DEAD);
476 if (p->deserialized_state != p->state) {
478 if (p->deserialized_state == PATH_WAITING ||
479 p->deserialized_state == PATH_RUNNING)
480 path_enter_waiting(p, true, true);
482 path_set_state(p, p->deserialized_state);
488 static void path_enter_dead(Path *p, PathResult f) {
491 if (f != PATH_SUCCESS)
494 path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
497 static void path_enter_running(Path *p) {
502 dbus_error_init(&error);
504 /* Don't start job if we are supposed to go down */
505 if (UNIT(p)->job && UNIT(p)->job->type == JOB_STOP)
508 r = manager_add_job(UNIT(p)->manager, JOB_START, UNIT_DEREF(p->unit),
509 JOB_REPLACE, true, &error, NULL);
513 p->inotify_triggered = false;
519 path_set_state(p, PATH_RUNNING);
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);
527 dbus_error_free(&error);
530 static bool path_check_good(Path *p, bool initial) {
536 LIST_FOREACH(spec, s, p->specs) {
537 good = path_spec_check_good(s, initial);
546 static void path_enter_waiting(Path *p, bool initial, bool recheck) {
550 if (path_check_good(p, initial)) {
551 log_debug("%s got triggered.", UNIT(p)->id);
552 path_enter_running(p);
560 /* Hmm, so now we have created inotify watches, but the file
561 * might have appeared/been removed by now, so we must
565 if (path_check_good(p, false)) {
566 log_debug("%s got triggered.", UNIT(p)->id);
567 path_enter_running(p);
571 path_set_state(p, PATH_WAITING);
575 log_warning("%s failed to enter waiting state: %s",
576 UNIT(p)->id, strerror(-r));
577 path_enter_dead(p, PATH_FAILURE_RESOURCES);
580 static void path_mkdir(Path *p) {
585 if (!p->make_directory)
588 LIST_FOREACH(spec, s, p->specs)
589 path_spec_mkdir(s, p->directory_mode);
592 static int path_start(Unit *u) {
596 assert(p->state == PATH_DEAD || p->state == PATH_FAILED);
598 if (UNIT_DEREF(p->unit)->load_state != UNIT_LOADED)
603 p->result = PATH_SUCCESS;
604 path_enter_waiting(p, true, true);
609 static int path_stop(Unit *u) {
613 assert(p->state == PATH_WAITING || p->state == PATH_RUNNING);
615 path_enter_dead(p, PATH_SUCCESS);
619 static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
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));
632 static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
640 if (streq(key, "state")) {
643 state = path_state_from_string(value);
645 log_debug("Failed to parse state value %s", value);
647 p->deserialized_state = state;
649 } else if (streq(key, "result")) {
652 f = path_result_from_string(value);
654 log_debug("Failed to parse result value %s", value);
655 else if (f != PATH_SUCCESS)
659 log_debug("Unknown serialization key '%s'", key);
664 static UnitActiveState path_active_state(Unit *u) {
667 return state_translation_table[PATH(u)->state];
670 static const char *path_sub_state_to_string(Unit *u) {
673 return path_state_to_string(PATH(u)->state);
676 static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
684 if (p->state != PATH_WAITING &&
685 p->state != PATH_RUNNING)
688 /* log_debug("inotify wakeup on %s.", u->id); */
690 LIST_FOREACH(spec, s, p->specs)
691 if (path_spec_owns_inotify_fd(s, fd))
695 log_error("Got event on unknown fd.");
699 changed = path_spec_fd_event(s, events);
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;
709 path_enter_running(p);
711 path_enter_waiting(p, false, true);
716 path_enter_dead(p, PATH_FAILURE_RESOURCES);
719 void path_unit_notify(Unit *u, UnitActiveState new_state) {
723 if (u->type == UNIT_PATH)
726 SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) {
729 if (k->type != UNIT_PATH)
732 if (k->load_state != UNIT_LOADED)
737 if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) {
738 log_debug("%s got notified about unit deactivation.",
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);
749 static void path_reset_failed(Unit *u) {
754 if (p->state == PATH_FAILED)
755 path_set_state(p, PATH_DEAD);
757 p->result = PATH_SUCCESS;
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"
767 DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
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"
777 DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
779 static const char* const path_result_table[_PATH_RESULT_MAX] = {
780 [PATH_SUCCESS] = "success",
781 [PATH_FAILURE_RESOURCES] = "resources"
784 DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
786 const UnitVTable path_vtable = {
787 .object_size = sizeof(Path),
797 .coldplug = path_coldplug,
804 .serialize = path_serialize,
805 .deserialize_item = path_deserialize_item,
807 .active_state = path_active_state,
808 .sub_state_to_string = path_sub_state_to_string,
810 .fd_event = path_fd_event,
812 .reset_failed = path_reset_failed,
814 .bus_interface = "org.freedesktop.systemd1.Path",
815 .bus_message_handler = bus_path_message_handler,
816 .bus_invalidating_properties = bus_path_invalidating_properties