]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/timer.c
core: annotate event sources
[thirdparty/systemd.git] / src / core / timer.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 <errno.h>
23
24 #include "unit.h"
25 #include "unit-name.h"
26 #include "timer.h"
27 #include "dbus-timer.h"
28 #include "special.h"
29 #include "bus-util.h"
30 #include "bus-error.h"
31
32 static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = {
33 [TIMER_DEAD] = UNIT_INACTIVE,
34 [TIMER_WAITING] = UNIT_ACTIVE,
35 [TIMER_RUNNING] = UNIT_ACTIVE,
36 [TIMER_ELAPSED] = UNIT_ACTIVE,
37 [TIMER_FAILED] = UNIT_FAILED
38 };
39
40 static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata);
41
42 static void timer_init(Unit *u) {
43 Timer *t = TIMER(u);
44
45 assert(u);
46 assert(u->load_state == UNIT_STUB);
47
48 t->next_elapse_monotonic_or_boottime = USEC_INFINITY;
49 t->next_elapse_realtime = USEC_INFINITY;
50 t->accuracy_usec = u->manager->default_timer_accuracy_usec;
51 }
52
53 void timer_free_values(Timer *t) {
54 TimerValue *v;
55
56 assert(t);
57
58 while ((v = t->values)) {
59 LIST_REMOVE(value, t->values, v);
60
61 if (v->calendar_spec)
62 calendar_spec_free(v->calendar_spec);
63
64 free(v);
65 }
66 }
67
68 static void timer_done(Unit *u) {
69 Timer *t = TIMER(u);
70
71 assert(t);
72
73 timer_free_values(t);
74
75 t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source);
76 t->realtime_event_source = sd_event_source_unref(t->realtime_event_source);
77
78 free(t->stamp_path);
79 }
80
81 static int timer_verify(Timer *t) {
82 assert(t);
83
84 if (UNIT(t)->load_state != UNIT_LOADED)
85 return 0;
86
87 if (!t->values) {
88 log_unit_error(UNIT(t)->id, "%s lacks value setting. Refusing.", UNIT(t)->id);
89 return -EINVAL;
90 }
91
92 return 0;
93 }
94
95 static int timer_add_default_dependencies(Timer *t) {
96 int r;
97 TimerValue *v;
98
99 assert(t);
100
101 r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_TIMERS_TARGET, NULL, true);
102 if (r < 0)
103 return r;
104
105 if (UNIT(t)->manager->running_as == SYSTEMD_SYSTEM) {
106 r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true);
107 if (r < 0)
108 return r;
109
110 LIST_FOREACH(value, v, t->values) {
111 if (v->base == TIMER_CALENDAR) {
112 r = unit_add_dependency_by_name(UNIT(t), UNIT_AFTER, SPECIAL_TIME_SYNC_TARGET, NULL, true);
113 if (r < 0)
114 return r;
115 break;
116 }
117 }
118 }
119
120 return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
121 }
122
123 static int timer_setup_persistent(Timer *t) {
124 int r;
125
126 assert(t);
127
128 if (!t->persistent)
129 return 0;
130
131 if (UNIT(t)->manager->running_as == SYSTEMD_SYSTEM) {
132
133 r = unit_require_mounts_for(UNIT(t), "/var/lib/systemd/timers");
134 if (r < 0)
135 return r;
136
137 t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id);
138 } else {
139 const char *e;
140
141 e = getenv("XDG_DATA_HOME");
142 if (e)
143 t->stamp_path = strjoin(e, "/systemd/timers/stamp-", UNIT(t)->id, NULL);
144 else {
145
146 _cleanup_free_ char *h = NULL;
147
148 r = get_home_dir(&h);
149 if (r < 0)
150 return log_error_errno(r, "Failed to determine home directory: %m");
151
152 t->stamp_path = strjoin(h, "/.local/share/systemd/timers/stamp-", UNIT(t)->id, NULL);
153 }
154 }
155
156 if (!t->stamp_path)
157 return log_oom();
158
159 return 0;
160 }
161
162 static int timer_load(Unit *u) {
163 Timer *t = TIMER(u);
164 int r;
165
166 assert(u);
167 assert(u->load_state == UNIT_STUB);
168
169 r = unit_load_fragment_and_dropin(u);
170 if (r < 0)
171 return r;
172
173 if (u->load_state == UNIT_LOADED) {
174
175 if (set_isempty(u->dependencies[UNIT_TRIGGERS])) {
176 Unit *x;
177
178 r = unit_load_related_unit(u, ".service", &x);
179 if (r < 0)
180 return r;
181
182 r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true);
183 if (r < 0)
184 return r;
185 }
186
187 r = timer_setup_persistent(t);
188 if (r < 0)
189 return r;
190
191 if (u->default_dependencies) {
192 r = timer_add_default_dependencies(t);
193 if (r < 0)
194 return r;
195 }
196 }
197
198 return timer_verify(t);
199 }
200
201 static void timer_dump(Unit *u, FILE *f, const char *prefix) {
202 char buf[FORMAT_TIMESPAN_MAX];
203 Timer *t = TIMER(u);
204 Unit *trigger;
205 TimerValue *v;
206
207 trigger = UNIT_TRIGGER(u);
208
209 fprintf(f,
210 "%sTimer State: %s\n"
211 "%sResult: %s\n"
212 "%sUnit: %s\n"
213 "%sPersistent: %s\n"
214 "%sWakeSystem: %s\n"
215 "%sAccuracy: %s\n",
216 prefix, timer_state_to_string(t->state),
217 prefix, timer_result_to_string(t->result),
218 prefix, trigger ? trigger->id : "n/a",
219 prefix, yes_no(t->persistent),
220 prefix, yes_no(t->wake_system),
221 prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1));
222
223 LIST_FOREACH(value, v, t->values) {
224
225 if (v->base == TIMER_CALENDAR) {
226 _cleanup_free_ char *p = NULL;
227
228 calendar_spec_to_string(v->calendar_spec, &p);
229
230 fprintf(f,
231 "%s%s: %s\n",
232 prefix,
233 timer_base_to_string(v->base),
234 strna(p));
235 } else {
236 char timespan1[FORMAT_TIMESPAN_MAX];
237
238 fprintf(f,
239 "%s%s: %s\n",
240 prefix,
241 timer_base_to_string(v->base),
242 format_timespan(timespan1, sizeof(timespan1), v->value, 0));
243 }
244 }
245 }
246
247 static void timer_set_state(Timer *t, TimerState state) {
248 TimerState old_state;
249 assert(t);
250
251 old_state = t->state;
252 t->state = state;
253
254 if (state != TIMER_WAITING) {
255 t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source);
256 t->realtime_event_source = sd_event_source_unref(t->realtime_event_source);
257 }
258
259 if (state != old_state)
260 log_unit_debug(UNIT(t)->id,
261 "%s changed %s -> %s", UNIT(t)->id,
262 timer_state_to_string(old_state),
263 timer_state_to_string(state));
264
265 unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true);
266 }
267
268 static void timer_enter_waiting(Timer *t, bool initial);
269
270 static int timer_coldplug(Unit *u) {
271 Timer *t = TIMER(u);
272
273 assert(t);
274 assert(t->state == TIMER_DEAD);
275
276 if (t->deserialized_state != t->state) {
277
278 if (t->deserialized_state == TIMER_WAITING)
279 timer_enter_waiting(t, false);
280 else
281 timer_set_state(t, t->deserialized_state);
282 }
283
284 return 0;
285 }
286
287 static void timer_enter_dead(Timer *t, TimerResult f) {
288 assert(t);
289
290 if (f != TIMER_SUCCESS)
291 t->result = f;
292
293 timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD);
294 }
295
296 static usec_t monotonic_to_boottime(usec_t t) {
297 usec_t a, b;
298
299 if (t <= 0)
300 return 0;
301
302 a = now(CLOCK_BOOTTIME);
303 b = now(CLOCK_MONOTONIC);
304
305 if (t + a > b)
306 return t + a - b;
307 else
308 return 0;
309 }
310
311 static void timer_enter_waiting(Timer *t, bool initial) {
312 bool found_monotonic = false, found_realtime = false;
313 usec_t ts_realtime, ts_monotonic;
314 usec_t base = 0;
315 TimerValue *v;
316 int r;
317
318 /* If we shall wake the system we use the boottime clock
319 * rather than the monotonic clock. */
320
321 ts_realtime = now(CLOCK_REALTIME);
322 ts_monotonic = now(t->wake_system ? CLOCK_BOOTTIME : CLOCK_MONOTONIC);
323 t->next_elapse_monotonic_or_boottime = t->next_elapse_realtime = 0;
324
325 LIST_FOREACH(value, v, t->values) {
326
327 if (v->disabled)
328 continue;
329
330 if (v->base == TIMER_CALENDAR) {
331 usec_t b;
332
333 /* If we know the last time this was
334 * triggered, schedule the job based relative
335 * to that. If we don't just start from
336 * now. */
337
338 b = t->last_trigger.realtime > 0 ? t->last_trigger.realtime : ts_realtime;
339
340 r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse);
341 if (r < 0)
342 continue;
343
344 if (!found_realtime)
345 t->next_elapse_realtime = v->next_elapse;
346 else
347 t->next_elapse_realtime = MIN(t->next_elapse_realtime, v->next_elapse);
348
349 found_realtime = true;
350
351 } else {
352 switch (v->base) {
353
354 case TIMER_ACTIVE:
355 if (state_translation_table[t->state] == UNIT_ACTIVE)
356 base = UNIT(t)->inactive_exit_timestamp.monotonic;
357 else
358 base = ts_monotonic;
359 break;
360
361 case TIMER_BOOT:
362 /* CLOCK_MONOTONIC equals the uptime on Linux */
363 base = 0;
364 break;
365
366 case TIMER_STARTUP:
367 base = UNIT(t)->manager->userspace_timestamp.monotonic;
368 break;
369
370 case TIMER_UNIT_ACTIVE:
371
372 base = UNIT_TRIGGER(UNIT(t))->inactive_exit_timestamp.monotonic;
373
374 if (base <= 0)
375 base = t->last_trigger.monotonic;
376
377 if (base <= 0)
378 continue;
379
380 break;
381
382 case TIMER_UNIT_INACTIVE:
383
384 base = UNIT_TRIGGER(UNIT(t))->inactive_enter_timestamp.monotonic;
385
386 if (base <= 0)
387 base = t->last_trigger.monotonic;
388
389 if (base <= 0)
390 continue;
391
392 break;
393
394 default:
395 assert_not_reached("Unknown timer base");
396 }
397
398 if (t->wake_system)
399 base = monotonic_to_boottime(base);
400
401 v->next_elapse = base + v->value;
402
403 if (!initial && v->next_elapse < ts_monotonic && IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) {
404 /* This is a one time trigger, disable it now */
405 v->disabled = true;
406 continue;
407 }
408
409 if (!found_monotonic)
410 t->next_elapse_monotonic_or_boottime = v->next_elapse;
411 else
412 t->next_elapse_monotonic_or_boottime = MIN(t->next_elapse_monotonic_or_boottime, v->next_elapse);
413
414 found_monotonic = true;
415 }
416 }
417
418 if (!found_monotonic && !found_realtime) {
419 log_unit_debug(UNIT(t)->id, "%s: Timer is elapsed.", UNIT(t)->id);
420 timer_set_state(t, TIMER_ELAPSED);
421 return;
422 }
423
424 if (found_monotonic) {
425 char buf[FORMAT_TIMESPAN_MAX];
426
427 log_unit_debug(UNIT(t)->id, "%s: Monotonic timer elapses in %s.",
428 UNIT(t)->id,
429 format_timespan(buf, sizeof(buf), t->next_elapse_monotonic_or_boottime > ts_monotonic ? t->next_elapse_monotonic_or_boottime - ts_monotonic : 0, 0));
430
431 if (t->monotonic_event_source) {
432 r = sd_event_source_set_time(t->monotonic_event_source, t->next_elapse_monotonic_or_boottime);
433 if (r < 0)
434 goto fail;
435
436 r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_ONESHOT);
437 } else
438 r = sd_event_add_time(
439 UNIT(t)->manager->event,
440 &t->monotonic_event_source,
441 t->wake_system ? CLOCK_BOOTTIME_ALARM : CLOCK_MONOTONIC,
442 t->next_elapse_monotonic_or_boottime, t->accuracy_usec,
443 timer_dispatch, t);
444 if (r < 0)
445 goto fail;
446
447 (void) sd_event_source_set_description(t->monotonic_event_source, "timer-monotonic");
448
449 } else if (t->monotonic_event_source) {
450
451 r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_OFF);
452 if (r < 0)
453 goto fail;
454 }
455
456 if (found_realtime) {
457 char buf[FORMAT_TIMESTAMP_MAX];
458 log_unit_debug(UNIT(t)->id, "%s: Realtime timer elapses at %s.", UNIT(t)->id, format_timestamp(buf, sizeof(buf), t->next_elapse_realtime));
459
460 if (t->realtime_event_source) {
461 r = sd_event_source_set_time(t->realtime_event_source, t->next_elapse_realtime);
462 if (r < 0)
463 goto fail;
464
465 r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_ONESHOT);
466 } else
467 r = sd_event_add_time(
468 UNIT(t)->manager->event,
469 &t->realtime_event_source,
470 t->wake_system ? CLOCK_REALTIME_ALARM : CLOCK_REALTIME,
471 t->next_elapse_realtime, t->accuracy_usec,
472 timer_dispatch, t);
473 if (r < 0)
474 goto fail;
475
476 (void) sd_event_source_set_description(t->realtime_event_source, "timer-realtime");
477
478 } else if (t->realtime_event_source) {
479
480 r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_OFF);
481 if (r < 0)
482 goto fail;
483 }
484
485 timer_set_state(t, TIMER_WAITING);
486 return;
487
488 fail:
489 log_unit_warning_errno(UNIT(t)->id, r, "%s failed to enter waiting state: %m", UNIT(t)->id);
490 timer_enter_dead(t, TIMER_FAILURE_RESOURCES);
491 }
492
493 static void timer_enter_running(Timer *t) {
494 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
495 int r;
496
497 assert(t);
498
499 /* Don't start job if we are supposed to go down */
500 if (unit_stop_pending(UNIT(t)))
501 return;
502
503 r = manager_add_job(UNIT(t)->manager, JOB_START, UNIT_TRIGGER(UNIT(t)),
504 JOB_REPLACE, true, &error, NULL);
505 if (r < 0)
506 goto fail;
507
508 dual_timestamp_get(&t->last_trigger);
509
510 if (t->stamp_path)
511 touch_file(t->stamp_path, true, t->last_trigger.realtime, UID_INVALID, GID_INVALID, 0);
512
513 timer_set_state(t, TIMER_RUNNING);
514 return;
515
516 fail:
517 log_unit_warning(UNIT(t)->id,
518 "%s failed to queue unit startup job: %s",
519 UNIT(t)->id, bus_error_message(&error, r));
520 timer_enter_dead(t, TIMER_FAILURE_RESOURCES);
521 }
522
523 static int timer_start(Unit *u) {
524 Timer *t = TIMER(u);
525 TimerValue *v;
526
527 assert(t);
528 assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED);
529
530 if (UNIT_TRIGGER(u)->load_state != UNIT_LOADED)
531 return -ENOENT;
532
533 t->last_trigger = DUAL_TIMESTAMP_NULL;
534
535 /* Reenable all timers that depend on unit activation time */
536 LIST_FOREACH(value, v, t->values)
537 if (v->base == TIMER_ACTIVE)
538 v->disabled = false;
539
540 if (t->stamp_path) {
541 struct stat st;
542
543 if (stat(t->stamp_path, &st) >= 0)
544 t->last_trigger.realtime = timespec_load(&st.st_atim);
545 else if (errno == ENOENT)
546 /* The timer has never run before,
547 * make sure a stamp file exists.
548 */
549 touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0);
550 }
551
552 t->result = TIMER_SUCCESS;
553 timer_enter_waiting(t, true);
554 return 1;
555 }
556
557 static int timer_stop(Unit *u) {
558 Timer *t = TIMER(u);
559
560 assert(t);
561 assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED);
562
563 timer_enter_dead(t, TIMER_SUCCESS);
564 return 1;
565 }
566
567 static int timer_serialize(Unit *u, FILE *f, FDSet *fds) {
568 Timer *t = TIMER(u);
569
570 assert(u);
571 assert(f);
572 assert(fds);
573
574 unit_serialize_item(u, f, "state", timer_state_to_string(t->state));
575 unit_serialize_item(u, f, "result", timer_result_to_string(t->result));
576
577 if (t->last_trigger.realtime > 0)
578 unit_serialize_item_format(u, f, "last-trigger-realtime", "%" PRIu64, t->last_trigger.realtime);
579
580 if (t->last_trigger.monotonic > 0)
581 unit_serialize_item_format(u, f, "last-trigger-monotonic", "%" PRIu64, t->last_trigger.monotonic);
582
583 return 0;
584 }
585
586 static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
587 Timer *t = TIMER(u);
588 int r;
589
590 assert(u);
591 assert(key);
592 assert(value);
593 assert(fds);
594
595 if (streq(key, "state")) {
596 TimerState state;
597
598 state = timer_state_from_string(value);
599 if (state < 0)
600 log_unit_debug(u->id, "Failed to parse state value %s", value);
601 else
602 t->deserialized_state = state;
603 } else if (streq(key, "result")) {
604 TimerResult f;
605
606 f = timer_result_from_string(value);
607 if (f < 0)
608 log_unit_debug(u->id, "Failed to parse result value %s", value);
609 else if (f != TIMER_SUCCESS)
610 t->result = f;
611 } else if (streq(key, "last-trigger-realtime")) {
612
613 r = safe_atou64(value, &t->last_trigger.realtime);
614 if (r < 0)
615 log_unit_debug(u->id, "Failed to parse last-trigger-realtime value %s", value);
616
617 } else if (streq(key, "last-trigger-monotonic")) {
618
619 r = safe_atou64(value, &t->last_trigger.monotonic);
620 if (r < 0)
621 log_unit_debug(u->id, "Failed to parse last-trigger-monotonic value %s", value);
622
623 } else
624 log_unit_debug(u->id, "Unknown serialization key '%s'", key);
625
626 return 0;
627 }
628
629 _pure_ static UnitActiveState timer_active_state(Unit *u) {
630 assert(u);
631
632 return state_translation_table[TIMER(u)->state];
633 }
634
635 _pure_ static const char *timer_sub_state_to_string(Unit *u) {
636 assert(u);
637
638 return timer_state_to_string(TIMER(u)->state);
639 }
640
641 static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) {
642 Timer *t = TIMER(userdata);
643
644 assert(t);
645
646 if (t->state != TIMER_WAITING)
647 return 0;
648
649 log_unit_debug(UNIT(t)->id, "Timer elapsed on %s", UNIT(t)->id);
650 timer_enter_running(t);
651 return 0;
652 }
653
654 static void timer_trigger_notify(Unit *u, Unit *other) {
655 Timer *t = TIMER(u);
656 TimerValue *v;
657
658 assert(u);
659 assert(other);
660
661 if (other->load_state != UNIT_LOADED)
662 return;
663
664 /* Reenable all timers that depend on unit state */
665 LIST_FOREACH(value, v, t->values)
666 if (v->base == TIMER_UNIT_ACTIVE ||
667 v->base == TIMER_UNIT_INACTIVE)
668 v->disabled = false;
669
670 switch (t->state) {
671
672 case TIMER_WAITING:
673 case TIMER_ELAPSED:
674
675 /* Recalculate sleep time */
676 timer_enter_waiting(t, false);
677 break;
678
679 case TIMER_RUNNING:
680
681 if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
682 log_unit_debug(UNIT(t)->id, "%s got notified about unit deactivation.", UNIT(t)->id);
683 timer_enter_waiting(t, false);
684 }
685 break;
686
687 case TIMER_DEAD:
688 case TIMER_FAILED:
689 break;
690
691 default:
692 assert_not_reached("Unknown timer state");
693 }
694 }
695
696 static void timer_reset_failed(Unit *u) {
697 Timer *t = TIMER(u);
698
699 assert(t);
700
701 if (t->state == TIMER_FAILED)
702 timer_set_state(t, TIMER_DEAD);
703
704 t->result = TIMER_SUCCESS;
705 }
706
707 static void timer_time_change(Unit *u) {
708 Timer *t = TIMER(u);
709
710 assert(u);
711
712 if (t->state != TIMER_WAITING)
713 return;
714
715 log_unit_debug(u->id, "%s: time change, recalculating next elapse.", u->id);
716 timer_enter_waiting(t, false);
717 }
718
719 static const char* const timer_state_table[_TIMER_STATE_MAX] = {
720 [TIMER_DEAD] = "dead",
721 [TIMER_WAITING] = "waiting",
722 [TIMER_RUNNING] = "running",
723 [TIMER_ELAPSED] = "elapsed",
724 [TIMER_FAILED] = "failed"
725 };
726
727 DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState);
728
729 static const char* const timer_base_table[_TIMER_BASE_MAX] = {
730 [TIMER_ACTIVE] = "OnActiveSec",
731 [TIMER_BOOT] = "OnBootSec",
732 [TIMER_STARTUP] = "OnStartupSec",
733 [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec",
734 [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec",
735 [TIMER_CALENDAR] = "OnCalendar"
736 };
737
738 DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase);
739
740 static const char* const timer_result_table[_TIMER_RESULT_MAX] = {
741 [TIMER_SUCCESS] = "success",
742 [TIMER_FAILURE_RESOURCES] = "resources"
743 };
744
745 DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult);
746
747 const UnitVTable timer_vtable = {
748 .object_size = sizeof(Timer),
749
750 .sections =
751 "Unit\0"
752 "Timer\0"
753 "Install\0",
754 .private_section = "Timer",
755
756 .init = timer_init,
757 .done = timer_done,
758 .load = timer_load,
759
760 .coldplug = timer_coldplug,
761
762 .dump = timer_dump,
763
764 .start = timer_start,
765 .stop = timer_stop,
766
767 .serialize = timer_serialize,
768 .deserialize_item = timer_deserialize_item,
769
770 .active_state = timer_active_state,
771 .sub_state_to_string = timer_sub_state_to_string,
772
773 .trigger_notify = timer_trigger_notify,
774
775 .reset_failed = timer_reset_failed,
776 .time_change = timer_time_change,
777
778 .bus_interface = "org.freedesktop.systemd1.Timer",
779 .bus_vtable = bus_timer_vtable,
780 .bus_set_property = bus_timer_set_property,
781
782 .can_transient = true,
783 };