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