]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/path.c
core: fix compilation with gcc -O3
[thirdparty/systemd.git] / src / core / path.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <sys/epoll.h>
5 #include <sys/inotify.h>
6 #include <unistd.h>
7
8 #include "bus-error.h"
9 #include "bus-util.h"
10 #include "dbus-path.h"
11 #include "dbus-unit.h"
12 #include "escape.h"
13 #include "fd-util.h"
14 #include "fs-util.h"
15 #include "glob-util.h"
16 #include "macro.h"
17 #include "mkdir.h"
18 #include "path.h"
19 #include "path-util.h"
20 #include "serialize.h"
21 #include "special.h"
22 #include "stat-util.h"
23 #include "string-table.h"
24 #include "string-util.h"
25 #include "unit-name.h"
26 #include "unit.h"
27
28 static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
29 [PATH_DEAD] = UNIT_INACTIVE,
30 [PATH_WAITING] = UNIT_ACTIVE,
31 [PATH_RUNNING] = UNIT_ACTIVE,
32 [PATH_FAILED] = UNIT_FAILED,
33 };
34
35 static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
36
37 int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
38 static const int flags_table[_PATH_TYPE_MAX] = {
39 [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
40 [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
41 [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
42 [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,
43 [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO,
44 };
45
46 bool exists = false;
47 char *slash, *oldslash = NULL;
48 int r;
49
50 assert(s);
51 assert(s->unit);
52 assert(handler);
53
54 path_spec_unwatch(s);
55
56 s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
57 if (s->inotify_fd < 0) {
58 r = -errno;
59 goto fail;
60 }
61
62 r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s);
63 if (r < 0)
64 goto fail;
65
66 (void) sd_event_source_set_description(s->event_source, "path");
67
68 /* This function assumes the path was passed through path_simplify()! */
69 assert(!strstr(s->path, "//"));
70
71 for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) {
72 char *cut = NULL;
73 int flags;
74 char tmp;
75
76 if (slash) {
77 cut = slash + (slash == s->path);
78 tmp = *cut;
79 *cut = '\0';
80
81 flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
82 } else
83 flags = flags_table[s->type];
84
85 r = inotify_add_watch(s->inotify_fd, s->path, flags);
86 if (r < 0) {
87 if (IN_SET(errno, EACCES, ENOENT)) {
88 if (cut)
89 *cut = tmp;
90 break;
91 }
92
93 /* This second call to inotify_add_watch() should fail like the previous
94 * one and is done for logging the error in a comprehensive way. */
95 r = inotify_add_watch_and_warn(s->inotify_fd, s->path, flags);
96 if (r < 0) {
97 if (cut)
98 *cut = tmp;
99 goto fail;
100 }
101
102 /* Hmm, we succeeded in adding the watch this time... let's continue. */
103 }
104 exists = true;
105
106 /* Path exists, we don't need to watch parent too closely. */
107 if (oldslash) {
108 char *cut2 = oldslash + (oldslash == s->path);
109 char tmp2 = *cut2;
110 *cut2 = '\0';
111
112 (void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF);
113 /* Error is ignored, the worst can happen is we get spurious events. */
114
115 *cut2 = tmp2;
116 }
117
118 if (cut)
119 *cut = tmp;
120
121 if (slash)
122 oldslash = slash;
123 else {
124 /* whole path has been iterated over */
125 s->primary_wd = r;
126 break;
127 }
128 }
129
130 if (!exists) {
131 r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path);
132 /* either EACCESS or ENOENT */
133 goto fail;
134 }
135
136 return 0;
137
138 fail:
139 path_spec_unwatch(s);
140 return r;
141 }
142
143 void path_spec_unwatch(PathSpec *s) {
144 assert(s);
145
146 s->event_source = sd_event_source_unref(s->event_source);
147 s->inotify_fd = safe_close(s->inotify_fd);
148 }
149
150 int path_spec_fd_event(PathSpec *s, uint32_t revents) {
151 union inotify_event_buffer buffer;
152 struct inotify_event *e;
153 ssize_t l;
154 int r = 0;
155
156 if (revents != EPOLLIN)
157 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
158 "Got invalid poll event on inotify.");
159
160 l = read(s->inotify_fd, &buffer, sizeof(buffer));
161 if (l < 0) {
162 if (IN_SET(errno, EAGAIN, EINTR))
163 return 0;
164
165 return log_error_errno(errno, "Failed to read inotify event: %m");
166 }
167
168 FOREACH_INOTIFY_EVENT(e, buffer, l) {
169 if (IN_SET(s->type, PATH_CHANGED, PATH_MODIFIED) &&
170 s->primary_wd == e->wd)
171 r = 1;
172 }
173
174 return r;
175 }
176
177 static bool path_spec_check_good(PathSpec *s, bool initial) {
178 bool b, good = false;
179
180 switch (s->type) {
181
182 case PATH_EXISTS:
183 b = access(s->path, F_OK) >= 0;
184 good = b && !s->previous_exists;
185 s->previous_exists = b;
186 break;
187
188 case PATH_EXISTS_GLOB:
189 good = glob_exists(s->path) > 0;
190 break;
191
192 case PATH_DIRECTORY_NOT_EMPTY: {
193 int k;
194
195 k = dir_is_empty(s->path);
196 good = !(k == -ENOENT || k > 0);
197 break;
198 }
199
200 case PATH_CHANGED:
201 case PATH_MODIFIED:
202 b = access(s->path, F_OK) >= 0;
203 good = !initial && b != s->previous_exists;
204 s->previous_exists = b;
205 break;
206
207 default:
208 ;
209 }
210
211 return good;
212 }
213
214 static void path_spec_mkdir(PathSpec *s, mode_t mode) {
215 int r;
216
217 if (IN_SET(s->type, PATH_EXISTS, PATH_EXISTS_GLOB))
218 return;
219
220 r = mkdir_p_label(s->path, mode);
221 if (r < 0)
222 log_warning_errno(r, "mkdir(%s) failed: %m", s->path);
223 }
224
225 static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
226 const char *type;
227
228 assert_se(type = path_type_to_string(s->type));
229 fprintf(f, "%s%s: %s\n", prefix, type, s->path);
230 }
231
232 void path_spec_done(PathSpec *s) {
233 assert(s);
234 assert(s->inotify_fd == -1);
235
236 free(s->path);
237 }
238
239 static void path_init(Unit *u) {
240 Path *p = PATH(u);
241
242 assert(u);
243 assert(u->load_state == UNIT_STUB);
244
245 p->directory_mode = 0755;
246 }
247
248 void path_free_specs(Path *p) {
249 PathSpec *s;
250
251 assert(p);
252
253 while ((s = p->specs)) {
254 path_spec_unwatch(s);
255 LIST_REMOVE(spec, p->specs, s);
256 path_spec_done(s);
257 free(s);
258 }
259 }
260
261 static void path_done(Unit *u) {
262 Path *p = PATH(u);
263
264 assert(p);
265
266 path_free_specs(p);
267 }
268
269 static int path_add_mount_dependencies(Path *p) {
270 PathSpec *s;
271 int r;
272
273 assert(p);
274
275 LIST_FOREACH(spec, s, p->specs) {
276 r = unit_require_mounts_for(UNIT(p), s->path, UNIT_DEPENDENCY_FILE);
277 if (r < 0)
278 return r;
279 }
280
281 return 0;
282 }
283
284 static int path_verify(Path *p) {
285 assert(p);
286 assert(UNIT(p)->load_state == UNIT_LOADED);
287
288 if (!p->specs) {
289 log_unit_error(UNIT(p), "Path unit lacks path setting. Refusing.");
290 return -ENOEXEC;
291 }
292
293 return 0;
294 }
295
296 static int path_add_default_dependencies(Path *p) {
297 int r;
298
299 assert(p);
300
301 if (!UNIT(p)->default_dependencies)
302 return 0;
303
304 r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
305 if (r < 0)
306 return r;
307
308 if (MANAGER_IS_SYSTEM(UNIT(p)->manager)) {
309 r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
310 if (r < 0)
311 return r;
312 }
313
314 return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
315 }
316
317 static int path_add_trigger_dependencies(Path *p) {
318 Unit *x;
319 int r;
320
321 assert(p);
322
323 if (!hashmap_isempty(UNIT(p)->dependencies[UNIT_TRIGGERS]))
324 return 0;
325
326 r = unit_load_related_unit(UNIT(p), ".service", &x);
327 if (r < 0)
328 return r;
329
330 return unit_add_two_dependencies(UNIT(p), UNIT_BEFORE, UNIT_TRIGGERS, x, true, UNIT_DEPENDENCY_IMPLICIT);
331 }
332
333 static int path_add_extras(Path *p) {
334 int r;
335
336 r = path_add_trigger_dependencies(p);
337 if (r < 0)
338 return r;
339
340 r = path_add_mount_dependencies(p);
341 if (r < 0)
342 return r;
343
344 return path_add_default_dependencies(p);
345 }
346
347 static int path_load(Unit *u) {
348 Path *p = PATH(u);
349 int r;
350
351 assert(u);
352 assert(u->load_state == UNIT_STUB);
353
354 r = unit_load_fragment_and_dropin(u, true);
355 if (r < 0)
356 return r;
357
358 if (u->load_state != UNIT_LOADED)
359 return 0;
360
361 r = path_add_extras(p);
362 if (r < 0)
363 return r;
364
365 return path_verify(p);
366 }
367
368 static void path_dump(Unit *u, FILE *f, const char *prefix) {
369 Path *p = PATH(u);
370 Unit *trigger;
371 PathSpec *s;
372
373 assert(p);
374 assert(f);
375
376 trigger = UNIT_TRIGGER(u);
377
378 fprintf(f,
379 "%sPath State: %s\n"
380 "%sResult: %s\n"
381 "%sUnit: %s\n"
382 "%sMakeDirectory: %s\n"
383 "%sDirectoryMode: %04o\n",
384 prefix, path_state_to_string(p->state),
385 prefix, path_result_to_string(p->result),
386 prefix, trigger ? trigger->id : "n/a",
387 prefix, yes_no(p->make_directory),
388 prefix, p->directory_mode);
389
390 LIST_FOREACH(spec, s, p->specs)
391 path_spec_dump(s, f, prefix);
392 }
393
394 static void path_unwatch(Path *p) {
395 PathSpec *s;
396
397 assert(p);
398
399 LIST_FOREACH(spec, s, p->specs)
400 path_spec_unwatch(s);
401 }
402
403 static int path_watch(Path *p) {
404 int r;
405 PathSpec *s;
406
407 assert(p);
408
409 LIST_FOREACH(spec, s, p->specs) {
410 r = path_spec_watch(s, path_dispatch_io);
411 if (r < 0)
412 return r;
413 }
414
415 return 0;
416 }
417
418 static void path_set_state(Path *p, PathState state) {
419 PathState old_state;
420 assert(p);
421
422 if (p->state != state)
423 bus_unit_send_pending_change_signal(UNIT(p), false);
424
425 old_state = p->state;
426 p->state = state;
427
428 if (state != PATH_WAITING &&
429 (state != PATH_RUNNING || p->inotify_triggered))
430 path_unwatch(p);
431
432 if (state != old_state)
433 log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state));
434
435 unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], 0);
436 }
437
438 static void path_enter_waiting(Path *p, bool initial, bool recheck);
439
440 static int path_coldplug(Unit *u) {
441 Path *p = PATH(u);
442
443 assert(p);
444 assert(p->state == PATH_DEAD);
445
446 if (p->deserialized_state != p->state) {
447
448 if (IN_SET(p->deserialized_state, PATH_WAITING, PATH_RUNNING))
449 path_enter_waiting(p, true, true);
450 else
451 path_set_state(p, p->deserialized_state);
452 }
453
454 return 0;
455 }
456
457 static void path_enter_dead(Path *p, PathResult f) {
458 assert(p);
459
460 if (p->result == PATH_SUCCESS)
461 p->result = f;
462
463 unit_log_result(UNIT(p), p->result == PATH_SUCCESS, path_result_to_string(p->result));
464 path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
465 }
466
467 static void path_enter_running(Path *p) {
468 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
469 Unit *trigger;
470 int r;
471
472 assert(p);
473
474 /* Don't start job if we are supposed to go down */
475 if (unit_stop_pending(UNIT(p)))
476 return;
477
478 trigger = UNIT_TRIGGER(UNIT(p));
479 if (!trigger) {
480 log_unit_error(UNIT(p), "Unit to trigger vanished.");
481 path_enter_dead(p, PATH_FAILURE_RESOURCES);
482 return;
483 }
484
485 r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, NULL);
486 if (r < 0)
487 goto fail;
488
489 p->inotify_triggered = false;
490
491 path_set_state(p, PATH_RUNNING);
492 path_unwatch(p);
493
494 return;
495
496 fail:
497 log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r));
498 path_enter_dead(p, PATH_FAILURE_RESOURCES);
499 }
500
501 static bool path_check_good(Path *p, bool initial) {
502 PathSpec *s;
503
504 assert(p);
505
506 LIST_FOREACH(spec, s, p->specs)
507 if (path_spec_check_good(s, initial))
508 return true;
509
510 return false;
511 }
512
513 static void path_enter_waiting(Path *p, bool initial, bool recheck) {
514 int r;
515
516 if (recheck)
517 if (path_check_good(p, initial)) {
518 log_unit_debug(UNIT(p), "Got triggered.");
519 path_enter_running(p);
520 return;
521 }
522
523 r = path_watch(p);
524 if (r < 0)
525 goto fail;
526
527 /* Hmm, so now we have created inotify watches, but the file
528 * might have appeared/been removed by now, so we must
529 * recheck */
530
531 if (recheck)
532 if (path_check_good(p, false)) {
533 log_unit_debug(UNIT(p), "Got triggered.");
534 path_enter_running(p);
535 return;
536 }
537
538 path_set_state(p, PATH_WAITING);
539 return;
540
541 fail:
542 log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m");
543 path_enter_dead(p, PATH_FAILURE_RESOURCES);
544 }
545
546 static void path_mkdir(Path *p) {
547 PathSpec *s;
548
549 assert(p);
550
551 if (!p->make_directory)
552 return;
553
554 LIST_FOREACH(spec, s, p->specs)
555 path_spec_mkdir(s, p->directory_mode);
556 }
557
558 static int path_start(Unit *u) {
559 Path *p = PATH(u);
560 int r;
561
562 assert(p);
563 assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED));
564
565 r = unit_test_trigger_loaded(u);
566 if (r < 0)
567 return r;
568
569 r = unit_test_start_limit(u);
570 if (r < 0) {
571 path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);
572 return r;
573 }
574
575 r = unit_acquire_invocation_id(u);
576 if (r < 0)
577 return r;
578
579 path_mkdir(p);
580
581 p->result = PATH_SUCCESS;
582 path_enter_waiting(p, true, true);
583
584 return 1;
585 }
586
587 static int path_stop(Unit *u) {
588 Path *p = PATH(u);
589
590 assert(p);
591 assert(IN_SET(p->state, PATH_WAITING, PATH_RUNNING));
592
593 path_enter_dead(p, PATH_SUCCESS);
594 return 1;
595 }
596
597 static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
598 Path *p = PATH(u);
599 PathSpec *s;
600
601 assert(u);
602 assert(f);
603 assert(fds);
604
605 (void) serialize_item(f, "state", path_state_to_string(p->state));
606 (void) serialize_item(f, "result", path_result_to_string(p->result));
607
608 LIST_FOREACH(spec, s, p->specs) {
609 const char *type;
610 _cleanup_free_ char *escaped = NULL;
611
612 escaped = cescape(s->path);
613 if (!escaped)
614 return log_oom();
615
616 assert_se(type = path_type_to_string(s->type));
617 (void) serialize_item_format(f, "path-spec", "%s %i %s",
618 type,
619 s->previous_exists,
620 s->path);
621 }
622
623 return 0;
624 }
625
626 static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
627 Path *p = PATH(u);
628
629 assert(u);
630 assert(key);
631 assert(value);
632 assert(fds);
633
634 if (streq(key, "state")) {
635 PathState state;
636
637 state = path_state_from_string(value);
638 if (state < 0)
639 log_unit_debug(u, "Failed to parse state value: %s", value);
640 else
641 p->deserialized_state = state;
642
643 } else if (streq(key, "result")) {
644 PathResult f;
645
646 f = path_result_from_string(value);
647 if (f < 0)
648 log_unit_debug(u, "Failed to parse result value: %s", value);
649 else if (f != PATH_SUCCESS)
650 p->result = f;
651
652 } else if (streq(key, "path-spec")) {
653 int previous_exists, skip = 0, r;
654 _cleanup_free_ char *type_str = NULL;
655
656 if (sscanf(value, "%ms %i %n", &type_str, &previous_exists, &skip) < 2)
657 log_unit_debug(u, "Failed to parse path-spec value: %s", value);
658 else {
659 _cleanup_free_ char *unescaped = NULL;
660 PathType type;
661 PathSpec *s;
662
663 type = path_type_from_string(type_str);
664 if (type < 0) {
665 log_unit_warning(u, "Unknown path type \"%s\", ignoring.", type_str);
666 return 0;
667 }
668
669 r = cunescape(value+skip, 0, &unescaped);
670 if (r < 0) {
671 log_unit_warning_errno(u, r, "Failed to unescape serialize path: %m");
672 return 0;
673 }
674
675 LIST_FOREACH(spec, s, p->specs)
676 if (s->type == type &&
677 path_equal(s->path, unescaped)) {
678
679 s->previous_exists = previous_exists;
680 break;
681 }
682 }
683
684 } else
685 log_unit_debug(u, "Unknown serialization key: %s", key);
686
687 return 0;
688 }
689
690 _pure_ static UnitActiveState path_active_state(Unit *u) {
691 assert(u);
692
693 return state_translation_table[PATH(u)->state];
694 }
695
696 _pure_ static const char *path_sub_state_to_string(Unit *u) {
697 assert(u);
698
699 return path_state_to_string(PATH(u)->state);
700 }
701
702 static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
703 PathSpec *s = userdata;
704 Path *p;
705 int changed;
706
707 assert(s);
708 assert(s->unit);
709 assert(fd >= 0);
710
711 p = PATH(s->unit);
712
713 if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
714 return 0;
715
716 /* log_debug("inotify wakeup on %s.", UNIT(p)->id); */
717
718 LIST_FOREACH(spec, s, p->specs)
719 if (path_spec_owns_inotify_fd(s, fd))
720 break;
721
722 if (!s) {
723 log_error("Got event on unknown fd.");
724 goto fail;
725 }
726
727 changed = path_spec_fd_event(s, revents);
728 if (changed < 0)
729 goto fail;
730
731 /* If we are already running, then remember that one event was
732 * dispatched so that we restart the service only if something
733 * actually changed on disk */
734 p->inotify_triggered = true;
735
736 if (changed)
737 path_enter_running(p);
738 else
739 path_enter_waiting(p, false, true);
740
741 return 0;
742
743 fail:
744 path_enter_dead(p, PATH_FAILURE_RESOURCES);
745 return 0;
746 }
747
748 static void path_trigger_notify(Unit *u, Unit *other) {
749 Path *p = PATH(u);
750
751 assert(u);
752 assert(other);
753
754 /* Invoked whenever the unit we trigger changes state or gains
755 * or loses a job */
756
757 if (other->load_state != UNIT_LOADED)
758 return;
759
760 if (p->state == PATH_RUNNING &&
761 UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
762 log_unit_debug(UNIT(p), "Got notified about unit deactivation.");
763
764 /* Hmm, so inotify was triggered since the
765 * last activation, so I guess we need to
766 * recheck what is going on. */
767 path_enter_waiting(p, false, p->inotify_triggered);
768 }
769 }
770
771 static void path_reset_failed(Unit *u) {
772 Path *p = PATH(u);
773
774 assert(p);
775
776 if (p->state == PATH_FAILED)
777 path_set_state(p, PATH_DEAD);
778
779 p->result = PATH_SUCCESS;
780 }
781
782 static const char* const path_type_table[_PATH_TYPE_MAX] = {
783 [PATH_EXISTS] = "PathExists",
784 [PATH_EXISTS_GLOB] = "PathExistsGlob",
785 [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty",
786 [PATH_CHANGED] = "PathChanged",
787 [PATH_MODIFIED] = "PathModified",
788 };
789
790 DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
791
792 static const char* const path_result_table[_PATH_RESULT_MAX] = {
793 [PATH_SUCCESS] = "success",
794 [PATH_FAILURE_RESOURCES] = "resources",
795 [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit",
796 };
797
798 DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
799
800 const UnitVTable path_vtable = {
801 .object_size = sizeof(Path),
802
803 .sections =
804 "Unit\0"
805 "Path\0"
806 "Install\0",
807 .private_section = "Path",
808
809 .can_transient = true,
810 .can_fail = true,
811 .can_trigger = true,
812
813 .init = path_init,
814 .done = path_done,
815 .load = path_load,
816
817 .coldplug = path_coldplug,
818
819 .dump = path_dump,
820
821 .start = path_start,
822 .stop = path_stop,
823
824 .serialize = path_serialize,
825 .deserialize_item = path_deserialize_item,
826
827 .active_state = path_active_state,
828 .sub_state_to_string = path_sub_state_to_string,
829
830 .trigger_notify = path_trigger_notify,
831
832 .reset_failed = path_reset_failed,
833
834 .bus_set_property = bus_path_set_property,
835 };