]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/path.c
dbus: make errors reported via D-Bus more useful
[thirdparty/systemd.git] / src / path.c
CommitLineData
01f78473
LP
1/*-*- Mode: C; c-basic-offset: 8 -*-*/
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,
032ff4af 39 [PATH_MAINTENANCE] = UNIT_MAINTENANCE
01f78473
LP
40};
41
42static void path_done(Unit *u) {
43 Path *p = PATH(u);
44 PathSpec *s;
45
46 assert(p);
47
48 while ((s = p->specs)) {
49 LIST_REMOVE(PathSpec, spec, p->specs, s);
50 free(s);
51 }
52}
53
54int path_add_one_mount_link(Path *p, Mount *m) {
55 PathSpec *s;
56 int r;
57
58 assert(p);
59 assert(m);
60
61 if (p->meta.load_state != UNIT_LOADED ||
62 m->meta.load_state != UNIT_LOADED)
63 return 0;
64
65 LIST_FOREACH(spec, s, p->specs) {
66
67 if (!path_startswith(s->path, m->where))
68 continue;
69
2c966c03 70 if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0)
01f78473
LP
71 return r;
72 }
73
74 return 0;
75}
76
77static int path_add_mount_links(Path *p) {
78 Meta *other;
79 int r;
80
81 assert(p);
82
83 LIST_FOREACH(units_per_type, other, p->meta.manager->units_per_type[UNIT_MOUNT])
84 if ((r = path_add_one_mount_link(p, (Mount*) other)) < 0)
85 return r;
86
87 return 0;
88}
89
90static int path_verify(Path *p) {
91 assert(p);
92
4cd1fbcc 93 if (p->meta.load_state != UNIT_LOADED)
01f78473
LP
94 return 0;
95
96 if (!p->specs) {
97 log_error("%s lacks path setting. Refusing.", p->meta.id);
98 return -EINVAL;
99 }
100
101 return 0;
102}
103
104static int path_load(Unit *u) {
105 Path *p = PATH(u);
106 int r;
107
108 assert(u);
109 assert(u->meta.load_state == UNIT_STUB);
110
111 if ((r = unit_load_fragment_and_dropin(u)) < 0)
112 return r;
113
114 if (u->meta.load_state == UNIT_LOADED) {
115
116 if (!p->unit)
117 if ((r = unit_load_related_unit(u, ".service", &p->unit)))
118 return r;
119
120 if ((r = unit_add_dependency(u, UNIT_BEFORE, p->unit, true)) < 0)
121 return r;
122
123 if ((r = path_add_mount_links(p)) < 0)
124 return r;
a40eb732
LP
125
126 /* Path units shouldn't stay around on shutdown */
127 if (p->meta.default_dependencies)
128 if ((r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0)
129 return r;
01f78473
LP
130 }
131
132 return path_verify(p);
133}
134
135static void path_dump(Unit *u, FILE *f, const char *prefix) {
136 Path *p = PATH(u);
137 const char *prefix2;
138 char *p2;
139 PathSpec *s;
140
141 p2 = strappend(prefix, "\t");
142 prefix2 = p2 ? p2 : prefix;
143
144 fprintf(f,
145 "%sPath State: %s\n"
146 "%sUnit: %s\n",
147 prefix, path_state_to_string(p->state),
148 prefix, p->unit->meta.id);
149
150 LIST_FOREACH(spec, s, p->specs)
151 fprintf(f,
152 "%s%s: %s\n",
153 prefix,
154 path_type_to_string(s->type),
155 s->path);
156
157 free(p2);
158}
159
160static void path_unwatch_one(Path *p, PathSpec *s) {
161
162 if (s->inotify_fd < 0)
163 return;
164
165 unit_unwatch_fd(UNIT(p), &s->watch);
166
167 close_nointr_nofail(s->inotify_fd);
168 s->inotify_fd = -1;
169}
170
171static int path_watch_one(Path *p, PathSpec *s) {
172 static const int flags_table[_PATH_TYPE_MAX] = {
173 [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF,
174 [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
175 [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_CREATE|IN_MOVED_TO
176 };
177
178 bool exists = false;
179 char *k;
180 int r;
181
182 assert(p);
183 assert(s);
184
185 path_unwatch_one(p, s);
186
187 if (!(k = strdup(s->path)))
188 return -ENOMEM;
189
190 if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) {
191 r = -errno;
192 goto fail;
193 }
194
195 if (unit_watch_fd(UNIT(p), s->inotify_fd, EPOLLIN, &s->watch) < 0) {
196 r = -errno;
197 goto fail;
198 }
199
200 if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0)
201 exists = true;
202
203 for (;;) {
204 int flags;
205 char *slash;
206
207 /* This assumes the path was passed through path_kill_slashes()! */
208 if (!(slash = strrchr(k, '/')))
209 break;
210
211 *slash = 0;
212
213 flags = IN_DELETE_SELF|IN_MOVE_SELF;
214 if (!exists)
215 flags |= IN_CREATE | IN_MOVED_TO | IN_ATTRIB;
216
217 if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
218 exists = true;
219 }
220
221 return 0;
222
223fail:
224 free(k);
225
226 path_unwatch_one(p, s);
227 return r;
228}
229
230static void path_unwatch(Path *p) {
231 PathSpec *s;
232
233 assert(p);
234
235 LIST_FOREACH(spec, s, p->specs)
236 path_unwatch_one(p, s);
237}
238
239static int path_watch(Path *p) {
240 int r;
241 PathSpec *s;
242
243 assert(p);
244
245 LIST_FOREACH(spec, s, p->specs)
246 if ((r = path_watch_one(p, s)) < 0)
247 return r;
248
249 return 0;
250}
251
252static void path_set_state(Path *p, PathState state) {
253 PathState old_state;
254 assert(p);
255
256 old_state = p->state;
257 p->state = state;
258
259 if (state != PATH_WAITING)
260 path_unwatch(p);
261
262 if (state != old_state)
263 log_debug("%s changed %s -> %s",
264 p->meta.id,
265 path_state_to_string(old_state),
266 path_state_to_string(state));
267
268 unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state]);
269}
270
271static void path_enter_waiting(Path *p, bool initial);
272
273static int path_coldplug(Unit *u) {
274 Path *p = PATH(u);
275
276 assert(p);
277 assert(p->state == PATH_DEAD);
278
279 if (p->deserialized_state != p->state) {
280
281 if (p->deserialized_state == PATH_WAITING ||
282 p->deserialized_state == PATH_RUNNING)
283 path_enter_waiting(p, true);
284 else
285 path_set_state(p, p->deserialized_state);
286 }
287
288 return 0;
289}
290
291static void path_enter_dead(Path *p, bool success) {
292 assert(p);
293
294 if (!success)
295 p->failure = true;
296
18c78fb1 297 path_set_state(p, p->failure ? PATH_MAINTENANCE : PATH_DEAD);
01f78473
LP
298}
299
300static void path_enter_running(Path *p) {
301 int r;
398ef8ba
LP
302 DBusError error;
303
01f78473 304 assert(p);
398ef8ba 305 dbus_error_init(&error);
01f78473 306
398ef8ba 307 if ((r = manager_add_job(p->meta.manager, JOB_START, p->unit, JOB_REPLACE, true, &error, NULL)) < 0)
01f78473
LP
308 goto fail;
309
310 path_set_state(p, PATH_RUNNING);
311 return;
312
313fail:
398ef8ba 314 log_warning("%s failed to queue unit startup job: %s", p->meta.id, bus_error(&error, r));
01f78473 315 path_enter_dead(p, false);
398ef8ba
LP
316
317 dbus_error_free(&error);
01f78473
LP
318}
319
320
321static void path_enter_waiting(Path *p, bool initial) {
322 PathSpec *s;
323 int r;
324 bool good = false;
325
326 LIST_FOREACH(spec, s, p->specs) {
327
328 switch (s->type) {
329
330 case PATH_EXISTS:
331 good = access(s->path, F_OK) >= 0;
332 break;
333
334 case PATH_DIRECTORY_NOT_EMPTY:
335 good = dir_is_empty(s->path) == 0;
336 break;
337
338 case PATH_CHANGED: {
339 bool b;
340
341 b = access(s->path, F_OK) >= 0;
342 good = !initial && b != s->previous_exists;
343 s->previous_exists = b;
344 break;
345 }
346
347 default:
348 ;
349 }
350
351 if (good)
352 break;
353 }
354
355 if (good) {
356 path_enter_running(p);
357 return;
358 }
359
360 if ((r = path_watch(p)) < 0)
361 goto fail;
362
363 path_set_state(p, PATH_WAITING);
364 return;
365
366fail:
367 log_warning("%s failed to enter waiting state: %s", p->meta.id, strerror(-r));
368 path_enter_dead(p, false);
369}
370
371static int path_start(Unit *u) {
372 Path *p = PATH(u);
373
374 assert(p);
18c78fb1 375 assert(p->state == PATH_DEAD || p->state == PATH_MAINTENANCE);
01f78473
LP
376
377 if (p->unit->meta.load_state != UNIT_LOADED)
378 return -ENOENT;
379
380 p->failure = false;
381path_enter_waiting(p, true);
382 return 0;
383}
384
385static int path_stop(Unit *u) {
386 Path *p = PATH(u);
387
388 assert(p);
389 assert(p->state == PATH_WAITING || p->state == PATH_RUNNING);
390
391 path_enter_dead(p, true);
392 return 0;
393}
394
395static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
396 Path *p = PATH(u);
397
398 assert(u);
399 assert(f);
400 assert(fds);
401
402 unit_serialize_item(u, f, "state", path_state_to_string(p->state));
403
404 return 0;
405}
406
407static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
408 Path *p = PATH(u);
409
410 assert(u);
411 assert(key);
412 assert(value);
413 assert(fds);
414
415 if (streq(key, "state")) {
416 PathState state;
417
418 if ((state = path_state_from_string(value)) < 0)
419 log_debug("Failed to parse state value %s", value);
420 else
421 p->deserialized_state = state;
422 } else
423 log_debug("Unknown serialization key '%s'", key);
424
425 return 0;
426}
427
428static UnitActiveState path_active_state(Unit *u) {
429 assert(u);
430
431 return state_translation_table[PATH(u)->state];
432}
433
434static const char *path_sub_state_to_string(Unit *u) {
435 assert(u);
436
437 return path_state_to_string(PATH(u)->state);
438}
439
440static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
441 Path *p = PATH(u);
442 int l;
443 ssize_t k;
444 struct inotify_event *buf = NULL;
445 PathSpec *s;
446
447 assert(p);
448 assert(fd >= 0);
449
450 if (p->state != PATH_WAITING)
451 return;
452
453 log_debug("inotify wakeup on %s.", u->meta.id);
454
455 if (events != EPOLLIN) {
456 log_error("Got Invalid poll event on inotify.");
457 goto fail;
458 }
459
460 LIST_FOREACH(spec, s, p->specs)
461 if (s->inotify_fd == fd)
462 break;
463
464 if (!s) {
465 log_error("Got event on unknown fd.");
466 goto fail;
467 }
468
469 if (ioctl(fd, FIONREAD, &l) < 0) {
470 log_error("FIONREAD failed: %s", strerror(errno));
471 goto fail;
472 }
473
474 if (!(buf = malloc(l))) {
475 log_error("Failed to allocate buffer: %s", strerror(-ENOMEM));
476 goto fail;
477 }
478
479 if ((k = read(fd, buf, l)) < 0) {
480 log_error("Failed to read inotify event: %s", strerror(-errno));
481 goto fail;
482 }
483
484 if ((size_t) k < sizeof(struct inotify_event) ||
485 (size_t) k < sizeof(struct inotify_event) + buf->len) {
486 log_error("inotify event too small.");
487 goto fail;
488 }
489
490 if (s->type == PATH_CHANGED && s->primary_wd == buf->wd)
491 path_enter_running(p);
492 else
493 path_enter_waiting(p, false);
494
495 free(buf);
496
497 return;
498
499fail:
500 free(buf);
501 path_enter_dead(p, false);
502}
503
504void path_unit_notify(Unit *u, UnitActiveState new_state) {
505 char *n;
506 int r;
507 Iterator i;
508
509 if (u->meta.type == UNIT_PATH)
510 return;
511
512 SET_FOREACH(n, u->meta.names, i) {
513 char *k;
514 Unit *t;
515 Path *p;
516
517 if (!(k = unit_name_change_suffix(n, ".path"))) {
518 r = -ENOMEM;
519 goto fail;
520 }
521
522 t = manager_get_unit(u->meta.manager, k);
523 free(k);
524
525 if (!t)
526 continue;
527
528 if (t->meta.load_state != UNIT_LOADED)
529 continue;
530
531 p = PATH(t);
532
533 if (p->unit != u)
534 continue;
535
536 if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) {
537 log_debug("%s got notified about unit deactivation.", p->meta.id);
538 path_enter_waiting(p, false);
539 }
540 }
541
542 return;
543
544fail:
545 log_error("Failed find path unit: %s", strerror(-r));
546}
547
548static const char* const path_state_table[_PATH_STATE_MAX] = {
549 [PATH_DEAD] = "dead",
550 [PATH_WAITING] = "waiting",
551 [PATH_RUNNING] = "running",
18c78fb1 552 [PATH_MAINTENANCE] = "maintenance"
01f78473
LP
553};
554
555DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
556
557static const char* const path_type_table[_PATH_TYPE_MAX] = {
558 [PATH_EXISTS] = "PathExists",
559 [PATH_CHANGED] = "PathChanged",
560 [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty"
561};
562
563DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
564
565const UnitVTable path_vtable = {
566 .suffix = ".path",
567
568 .done = path_done,
569 .load = path_load,
570
571 .coldplug = path_coldplug,
572
573 .dump = path_dump,
574
575 .start = path_start,
576 .stop = path_stop,
577
578 .serialize = path_serialize,
579 .deserialize_item = path_deserialize_item,
580
581 .active_state = path_active_state,
582 .sub_state_to_string = path_sub_state_to_string,
583
584 .fd_event = path_fd_event,
585
586 .bus_message_handler = bus_path_message_handler
587};