/src/systemd/src/core/timer.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <stdlib.h> |
4 | | #include <sys/stat.h> |
5 | | #include <unistd.h> |
6 | | |
7 | | #include "sd-bus.h" |
8 | | |
9 | | #include "alloc-util.h" |
10 | | #include "bus-error.h" |
11 | | #include "calendarspec.h" |
12 | | #include "dbus-timer.h" |
13 | | #include "dbus-unit.h" |
14 | | #include "fs-util.h" |
15 | | #include "manager.h" |
16 | | #include "random-util.h" |
17 | | #include "serialize.h" |
18 | | #include "siphash24.h" |
19 | | #include "special.h" |
20 | | #include "string-table.h" |
21 | | #include "string-util.h" |
22 | | #include "strv.h" |
23 | | #include "timer.h" |
24 | | #include "unit.h" |
25 | | #include "user-util.h" |
26 | | #include "virt.h" |
27 | | |
28 | | static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { |
29 | | [TIMER_DEAD] = UNIT_INACTIVE, |
30 | | [TIMER_WAITING] = UNIT_ACTIVE, |
31 | | [TIMER_RUNNING] = UNIT_ACTIVE, |
32 | | [TIMER_ELAPSED] = UNIT_ACTIVE, |
33 | | [TIMER_FAILED] = UNIT_FAILED, |
34 | | }; |
35 | | |
36 | | static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata); |
37 | | |
38 | 3.61k | static void timer_init(Unit *u) { |
39 | 3.61k | Timer *t = ASSERT_PTR(TIMER(u)); |
40 | | |
41 | 3.61k | assert(u->load_state == UNIT_STUB); |
42 | | |
43 | 3.61k | t->next_elapse_monotonic_or_boottime = USEC_INFINITY; |
44 | 3.61k | t->next_elapse_realtime = USEC_INFINITY; |
45 | 3.61k | t->accuracy_usec = u->manager->defaults.timer_accuracy_usec; |
46 | 3.61k | t->remain_after_elapse = true; |
47 | 3.61k | } |
48 | | |
49 | 4.06k | void timer_free_values(Timer *t) { |
50 | 4.06k | TimerValue *v; |
51 | | |
52 | 4.06k | assert(t); |
53 | | |
54 | 8.49k | while ((v = LIST_POP(value, t->values))) { |
55 | 4.43k | calendar_spec_free(v->calendar_spec); |
56 | 4.43k | free(v); |
57 | 4.43k | } |
58 | 4.06k | } |
59 | | |
60 | 3.61k | static void timer_done(Unit *u) { |
61 | 3.61k | Timer *t = ASSERT_PTR(TIMER(u)); |
62 | | |
63 | 3.61k | timer_free_values(t); |
64 | | |
65 | 3.61k | t->monotonic_event_source = sd_event_source_disable_unref(t->monotonic_event_source); |
66 | 3.61k | t->realtime_event_source = sd_event_source_disable_unref(t->realtime_event_source); |
67 | | |
68 | 3.61k | t->stamp_path = mfree(t->stamp_path); |
69 | 3.61k | } |
70 | | |
71 | 0 | static int timer_verify(Timer *t) { |
72 | 0 | assert(t); |
73 | 0 | assert(UNIT(t)->load_state == UNIT_LOADED); |
74 | |
|
75 | 0 | if (!t->values && !t->on_clock_change && !t->on_timezone_change) |
76 | 0 | return log_unit_error_errno(UNIT(t), SYNTHETIC_ERRNO(ENOEXEC), "Timer unit lacks value setting. Refusing."); |
77 | | |
78 | 0 | return 0; |
79 | 0 | } |
80 | | |
81 | 0 | static int timer_add_default_dependencies(Timer *t) { |
82 | 0 | int r; |
83 | |
|
84 | 0 | assert(t); |
85 | |
|
86 | 0 | if (!UNIT(t)->default_dependencies) |
87 | 0 | return 0; |
88 | | |
89 | 0 | r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_TIMERS_TARGET, true, UNIT_DEPENDENCY_DEFAULT); |
90 | 0 | if (r < 0) |
91 | 0 | return r; |
92 | | |
93 | 0 | if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { |
94 | 0 | r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, true, UNIT_DEPENDENCY_DEFAULT); |
95 | 0 | if (r < 0) |
96 | 0 | return r; |
97 | | |
98 | 0 | LIST_FOREACH(value, v, t->values) { |
99 | 0 | if (v->base != TIMER_CALENDAR) |
100 | 0 | continue; |
101 | | |
102 | 0 | FOREACH_STRING(target, SPECIAL_TIME_SYNC_TARGET, SPECIAL_TIME_SET_TARGET) { |
103 | 0 | r = unit_add_dependency_by_name(UNIT(t), UNIT_AFTER, target, true, UNIT_DEPENDENCY_DEFAULT); |
104 | 0 | if (r < 0) |
105 | 0 | return r; |
106 | 0 | } |
107 | | |
108 | 0 | break; |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | 0 | return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, true, UNIT_DEPENDENCY_DEFAULT); |
113 | 0 | } |
114 | | |
115 | 0 | static int timer_add_trigger_dependencies(Timer *t) { |
116 | 0 | Unit *x; |
117 | 0 | int r; |
118 | |
|
119 | 0 | assert(t); |
120 | |
|
121 | 0 | if (UNIT_TRIGGER(UNIT(t))) |
122 | 0 | return 0; |
123 | | |
124 | 0 | r = unit_load_related_unit(UNIT(t), ".service", &x); |
125 | 0 | if (r < 0) |
126 | 0 | return r; |
127 | | |
128 | 0 | return unit_add_two_dependencies(UNIT(t), UNIT_BEFORE, UNIT_TRIGGERS, x, true, UNIT_DEPENDENCY_IMPLICIT); |
129 | 0 | } |
130 | | |
131 | 0 | static int timer_setup_persistent(Timer *t) { |
132 | 0 | _cleanup_free_ char *stamp_path = NULL; |
133 | 0 | int r; |
134 | |
|
135 | 0 | assert(t); |
136 | |
|
137 | 0 | if (!t->persistent) |
138 | 0 | return 0; |
139 | | |
140 | 0 | if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { |
141 | |
|
142 | 0 | r = unit_add_mounts_for(UNIT(t), "/var/lib/systemd/timers", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES); |
143 | 0 | if (r < 0) |
144 | 0 | return r; |
145 | | |
146 | 0 | stamp_path = strjoin("/var/lib/systemd/timers/stamp-", UNIT(t)->id); |
147 | 0 | } else { |
148 | 0 | const char *e; |
149 | |
|
150 | 0 | e = getenv("XDG_DATA_HOME"); |
151 | 0 | if (e) |
152 | 0 | stamp_path = strjoin(e, "/systemd/timers/stamp-", UNIT(t)->id); |
153 | 0 | else { |
154 | |
|
155 | 0 | _cleanup_free_ char *h = NULL; |
156 | |
|
157 | 0 | r = get_home_dir(&h); |
158 | 0 | if (r < 0) |
159 | 0 | return log_unit_error_errno(UNIT(t), r, "Failed to determine home directory: %m"); |
160 | | |
161 | 0 | stamp_path = strjoin(h, "/.local/share/systemd/timers/stamp-", UNIT(t)->id); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | 0 | if (!stamp_path) |
166 | 0 | return log_oom(); |
167 | | |
168 | 0 | return free_and_replace(t->stamp_path, stamp_path); |
169 | 0 | } |
170 | | |
171 | 0 | static uint64_t timer_get_fixed_delay_hash(Timer *t) { |
172 | 0 | static const uint8_t hash_key[] = { |
173 | 0 | 0x51, 0x0a, 0xdb, 0x76, 0x29, 0x51, 0x42, 0xc2, |
174 | 0 | 0x80, 0x35, 0xea, 0xe6, 0x8e, 0x3a, 0x37, 0xbd |
175 | 0 | }; |
176 | |
|
177 | 0 | struct siphash state; |
178 | 0 | sd_id128_t machine_id; |
179 | 0 | uid_t uid; |
180 | 0 | int r; |
181 | |
|
182 | 0 | assert(t); |
183 | |
|
184 | 0 | uid = getuid(); |
185 | 0 | r = sd_id128_get_machine(&machine_id); |
186 | 0 | if (r < 0) { |
187 | 0 | log_unit_debug_errno(UNIT(t), r, |
188 | 0 | "Failed to get machine ID for the fixed delay calculation, proceeding with 0: %m"); |
189 | 0 | machine_id = SD_ID128_NULL; |
190 | 0 | } |
191 | |
|
192 | 0 | siphash24_init(&state, hash_key); |
193 | 0 | siphash24_compress_typesafe(machine_id, &state); |
194 | 0 | siphash24_compress_boolean(MANAGER_IS_SYSTEM(UNIT(t)->manager), &state); |
195 | 0 | siphash24_compress_typesafe(uid, &state); |
196 | 0 | siphash24_compress_string(UNIT(t)->id, &state); |
197 | |
|
198 | 0 | return siphash24_finalize(&state); |
199 | 0 | } |
200 | | |
201 | 1.59k | static int timer_load(Unit *u) { |
202 | 1.59k | Timer *t = ASSERT_PTR(TIMER(u)); |
203 | 1.59k | int r; |
204 | | |
205 | 1.59k | assert(u->load_state == UNIT_STUB); |
206 | | |
207 | 1.59k | r = unit_load_fragment_and_dropin(u, true); |
208 | 1.59k | if (r < 0) |
209 | 1.59k | return r; |
210 | | |
211 | 0 | if (u->load_state != UNIT_LOADED) |
212 | 0 | return 0; |
213 | | |
214 | | /* This is a new unit? Then let's add in some extras */ |
215 | 0 | r = timer_add_trigger_dependencies(t); |
216 | 0 | if (r < 0) |
217 | 0 | return r; |
218 | | |
219 | 0 | r = timer_setup_persistent(t); |
220 | 0 | if (r < 0) |
221 | 0 | return r; |
222 | | |
223 | 0 | r = timer_add_default_dependencies(t); |
224 | 0 | if (r < 0) |
225 | 0 | return r; |
226 | | |
227 | 0 | return timer_verify(t); |
228 | 0 | } |
229 | | |
230 | 0 | static void timer_dump(Unit *u, FILE *f, const char *prefix) { |
231 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
232 | 0 | Unit *trigger; |
233 | |
|
234 | 0 | assert(f); |
235 | 0 | assert(prefix); |
236 | |
|
237 | 0 | trigger = UNIT_TRIGGER(u); |
238 | |
|
239 | 0 | fprintf(f, |
240 | 0 | "%sTimer State: %s\n" |
241 | 0 | "%sResult: %s\n" |
242 | 0 | "%sUnit: %s\n" |
243 | 0 | "%sPersistent: %s\n" |
244 | 0 | "%sWakeSystem: %s\n" |
245 | 0 | "%sAccuracy: %s\n" |
246 | 0 | "%sRemainAfterElapse: %s\n" |
247 | 0 | "%sFixedRandomDelay: %s\n" |
248 | 0 | "%sOnClockChange: %s\n" |
249 | 0 | "%sOnTimeZoneChange: %s\n" |
250 | 0 | "%sDeferReactivation: %s\n", |
251 | 0 | prefix, timer_state_to_string(t->state), |
252 | 0 | prefix, timer_result_to_string(t->result), |
253 | 0 | prefix, trigger ? trigger->id : "n/a", |
254 | 0 | prefix, yes_no(t->persistent), |
255 | 0 | prefix, yes_no(t->wake_system), |
256 | 0 | prefix, FORMAT_TIMESPAN(t->accuracy_usec, 1), |
257 | 0 | prefix, yes_no(t->remain_after_elapse), |
258 | 0 | prefix, yes_no(t->fixed_random_delay), |
259 | 0 | prefix, yes_no(t->on_clock_change), |
260 | 0 | prefix, yes_no(t->on_timezone_change), |
261 | 0 | prefix, yes_no(t->defer_reactivation)); |
262 | |
|
263 | 0 | LIST_FOREACH(value, v, t->values) |
264 | 0 | if (v->base == TIMER_CALENDAR) { |
265 | 0 | _cleanup_free_ char *p = NULL; |
266 | |
|
267 | 0 | (void) calendar_spec_to_string(v->calendar_spec, &p); |
268 | |
|
269 | 0 | fprintf(f, |
270 | 0 | "%s%s: %s\n", |
271 | 0 | prefix, |
272 | 0 | timer_base_to_string(v->base), |
273 | 0 | strna(p)); |
274 | 0 | } else |
275 | 0 | fprintf(f, |
276 | 0 | "%s%s: %s\n", |
277 | 0 | prefix, |
278 | 0 | timer_base_to_string(v->base), |
279 | 0 | FORMAT_TIMESPAN(v->value, 0)); |
280 | 0 | } |
281 | | |
282 | 0 | static void timer_set_state(Timer *t, TimerState state) { |
283 | 0 | TimerState old_state; |
284 | |
|
285 | 0 | assert(t); |
286 | |
|
287 | 0 | if (t->state != state) |
288 | 0 | bus_unit_send_pending_change_signal(UNIT(t), false); |
289 | |
|
290 | 0 | old_state = t->state; |
291 | 0 | t->state = state; |
292 | |
|
293 | 0 | if (state != TIMER_WAITING) { |
294 | 0 | t->monotonic_event_source = sd_event_source_disable_unref(t->monotonic_event_source); |
295 | 0 | t->realtime_event_source = sd_event_source_disable_unref(t->realtime_event_source); |
296 | 0 | t->next_elapse_monotonic_or_boottime = USEC_INFINITY; |
297 | 0 | t->next_elapse_realtime = USEC_INFINITY; |
298 | 0 | } |
299 | |
|
300 | 0 | if (state != old_state) |
301 | 0 | log_unit_debug(UNIT(t), "Changed %s -> %s", timer_state_to_string(old_state), timer_state_to_string(state)); |
302 | |
|
303 | 0 | unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], /* reload_success= */ true); |
304 | 0 | } |
305 | | |
306 | | static void timer_enter_waiting(Timer *t, bool time_change); |
307 | | |
308 | 0 | static int timer_coldplug(Unit *u) { |
309 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
310 | |
|
311 | 0 | assert(t->state == TIMER_DEAD); |
312 | |
|
313 | 0 | if (t->deserialized_state == t->state) |
314 | 0 | return 0; |
315 | | |
316 | 0 | if (t->deserialized_state == TIMER_WAITING) |
317 | 0 | timer_enter_waiting(t, false); |
318 | 0 | else |
319 | 0 | timer_set_state(t, t->deserialized_state); |
320 | |
|
321 | 0 | return 0; |
322 | 0 | } |
323 | | |
324 | 0 | static void timer_enter_dead(Timer *t, TimerResult f) { |
325 | 0 | assert(t); |
326 | |
|
327 | 0 | if (t->result == TIMER_SUCCESS || f == TIMER_FAILURE_START_LIMIT_HIT) |
328 | 0 | t->result = f; |
329 | |
|
330 | 0 | unit_log_result(UNIT(t), t->result == TIMER_SUCCESS, timer_result_to_string(t->result)); |
331 | 0 | timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); |
332 | 0 | } |
333 | | |
334 | 0 | static void timer_enter_elapsed(Timer *t, bool leave_around) { |
335 | 0 | assert(t); |
336 | | |
337 | | /* If a unit is marked with RemainAfterElapse=yes we leave it |
338 | | * around even after it elapsed once, so that starting it |
339 | | * later again does not necessarily mean immediate |
340 | | * retriggering. We unconditionally leave units with |
341 | | * TIMER_UNIT_ACTIVE or TIMER_UNIT_INACTIVE triggers around, |
342 | | * since they might be restarted automatically at any time |
343 | | * later on. */ |
344 | |
|
345 | 0 | if (t->remain_after_elapse || leave_around) |
346 | 0 | timer_set_state(t, TIMER_ELAPSED); |
347 | 0 | else |
348 | 0 | timer_enter_dead(t, TIMER_SUCCESS); |
349 | 0 | } |
350 | | |
351 | 0 | static void add_random_delay(Timer *t, usec_t *v) { |
352 | 0 | usec_t add; |
353 | |
|
354 | 0 | assert(t); |
355 | 0 | assert(v); |
356 | |
|
357 | 0 | if (t->random_delay_usec == 0) |
358 | 0 | return; |
359 | 0 | if (*v == USEC_INFINITY) |
360 | 0 | return; |
361 | | |
362 | 0 | add = (t->fixed_random_delay ? timer_get_fixed_delay_hash(t) : random_u64()) % t->random_delay_usec; |
363 | |
|
364 | 0 | if (*v + add < *v) /* overflow */ |
365 | 0 | *v = (usec_t) -2; /* Highest possible value, that is not USEC_INFINITY */ |
366 | 0 | else |
367 | 0 | *v += add; |
368 | |
|
369 | 0 | log_unit_debug(UNIT(t), "Adding %s random time.", FORMAT_TIMESPAN(add, 0)); |
370 | 0 | } |
371 | | |
372 | 0 | static void timer_enter_waiting(Timer *t, bool time_change) { |
373 | 0 | bool found_monotonic = false, found_realtime = false; |
374 | 0 | bool leave_around = false; |
375 | 0 | triple_timestamp ts; |
376 | 0 | Unit *trigger; |
377 | 0 | int r; |
378 | |
|
379 | 0 | assert(t); |
380 | |
|
381 | 0 | trigger = UNIT_TRIGGER(UNIT(t)); |
382 | 0 | if (!trigger) { |
383 | 0 | log_unit_error(UNIT(t), "Unit to trigger vanished."); |
384 | 0 | goto fail; |
385 | 0 | } |
386 | | |
387 | 0 | triple_timestamp_now(&ts); |
388 | 0 | t->next_elapse_monotonic_or_boottime = t->next_elapse_realtime = 0; |
389 | |
|
390 | 0 | LIST_FOREACH(value, v, t->values) { |
391 | 0 | if (v->disabled) |
392 | 0 | continue; |
393 | | |
394 | 0 | if (v->base == TIMER_CALENDAR) { |
395 | 0 | bool rebase_after_boot_time = false; |
396 | 0 | usec_t b, random_offset = 0; |
397 | 0 | usec_t boot_monotonic = UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic; |
398 | |
|
399 | 0 | if (t->random_offset_usec != 0) |
400 | 0 | random_offset = timer_get_fixed_delay_hash(t) % t->random_offset_usec; |
401 | | |
402 | | /* If DeferReactivation= is enabled, schedule the job based on the last time |
403 | | * the trigger unit entered inactivity. Otherwise, if we know the last time |
404 | | * this was triggered, schedule the job based relative to that. If we don't, |
405 | | * just start from the activation time or realtime. |
406 | | * |
407 | | * Unless we have a real last-trigger time, we subtract the random_offset because |
408 | | * any event that elapsed within the last random_offset has actually been delayed |
409 | | * and thus hasn't truly elapsed yet. */ |
410 | |
|
411 | 0 | if (t->defer_reactivation && |
412 | 0 | dual_timestamp_is_set(&trigger->inactive_enter_timestamp)) { |
413 | 0 | if (dual_timestamp_is_set(&t->last_trigger)) |
414 | 0 | b = MAX(trigger->inactive_enter_timestamp.realtime, |
415 | 0 | t->last_trigger.realtime); |
416 | 0 | else |
417 | 0 | b = trigger->inactive_enter_timestamp.realtime; |
418 | 0 | } else if (dual_timestamp_is_set(&t->last_trigger)) { |
419 | 0 | b = t->last_trigger.realtime; |
420 | | |
421 | | /* Check if the last_trigger timestamp is older than the current machine |
422 | | * boot. If so, this means the timestamp came from a stamp file of a |
423 | | * persistent timer and we need to rebase it to make RandomizedDelaySec= |
424 | | * work (see below). */ |
425 | 0 | if (t->last_trigger.monotonic < boot_monotonic) |
426 | 0 | rebase_after_boot_time = true; |
427 | 0 | } else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp)) |
428 | 0 | b = UNIT(t)->inactive_exit_timestamp.realtime - random_offset; |
429 | 0 | else { |
430 | 0 | b = ts.realtime - random_offset; |
431 | 0 | rebase_after_boot_time = true; |
432 | 0 | } |
433 | |
|
434 | 0 | r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse); |
435 | 0 | if (r < 0) |
436 | 0 | continue; |
437 | | |
438 | 0 | v->next_elapse += random_offset; |
439 | |
|
440 | 0 | if (rebase_after_boot_time) { |
441 | | /* To make the delay due to RandomizedDelaySec= work even at boot, if the scheduled |
442 | | * time has already passed, set the time when systemd first started as the scheduled |
443 | | * time. Note that we base this on the monotonic timestamp of the boot, not the |
444 | | * realtime one, since the wallclock might have been off during boot. */ |
445 | 0 | usec_t rebased = map_clock_usec(boot_monotonic, CLOCK_MONOTONIC, CLOCK_REALTIME); |
446 | 0 | if (v->next_elapse < rebased) |
447 | 0 | v->next_elapse = rebased; |
448 | 0 | } |
449 | |
|
450 | 0 | if (!found_realtime) |
451 | 0 | t->next_elapse_realtime = v->next_elapse; |
452 | 0 | else |
453 | 0 | t->next_elapse_realtime = MIN(t->next_elapse_realtime, v->next_elapse); |
454 | |
|
455 | 0 | found_realtime = true; |
456 | |
|
457 | 0 | } else { |
458 | 0 | usec_t base; |
459 | |
|
460 | 0 | switch (v->base) { |
461 | | |
462 | 0 | case TIMER_ACTIVE: |
463 | 0 | if (state_translation_table[t->state] == UNIT_ACTIVE) |
464 | 0 | base = UNIT(t)->inactive_exit_timestamp.monotonic; |
465 | 0 | else |
466 | 0 | base = ts.monotonic; |
467 | 0 | break; |
468 | | |
469 | 0 | case TIMER_BOOT: |
470 | 0 | if (detect_container() <= 0) { |
471 | | /* CLOCK_MONOTONIC equals the uptime on Linux */ |
472 | 0 | base = 0; |
473 | 0 | break; |
474 | 0 | } |
475 | | /* In a container we don't want to include the time the host |
476 | | * was already up when the container started, so count from |
477 | | * our own startup. */ |
478 | 0 | _fallthrough_; |
479 | 0 | case TIMER_STARTUP: |
480 | 0 | base = UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic; |
481 | 0 | break; |
482 | | |
483 | 0 | case TIMER_UNIT_ACTIVE: |
484 | 0 | leave_around = true; |
485 | 0 | base = MAX(trigger->inactive_exit_timestamp.monotonic, t->last_trigger.monotonic); |
486 | 0 | if (base <= 0) |
487 | 0 | continue; |
488 | 0 | break; |
489 | | |
490 | 0 | case TIMER_UNIT_INACTIVE: |
491 | 0 | leave_around = true; |
492 | 0 | base = MAX(trigger->inactive_enter_timestamp.monotonic, t->last_trigger.monotonic); |
493 | 0 | if (base <= 0) |
494 | 0 | continue; |
495 | 0 | break; |
496 | | |
497 | 0 | default: |
498 | 0 | assert_not_reached(); |
499 | 0 | } |
500 | | |
501 | 0 | if (!time_change) |
502 | 0 | v->next_elapse = usec_add(usec_shift_clock(base, CLOCK_MONOTONIC, TIMER_MONOTONIC_CLOCK(t)), v->value); |
503 | |
|
504 | 0 | if (dual_timestamp_is_set(&t->last_trigger) && |
505 | 0 | !time_change && |
506 | 0 | v->next_elapse < triple_timestamp_by_clock(&ts, TIMER_MONOTONIC_CLOCK(t)) && |
507 | 0 | IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) { |
508 | | /* This is a one time trigger, disable it now */ |
509 | 0 | v->disabled = true; |
510 | 0 | continue; |
511 | 0 | } |
512 | | |
513 | 0 | if (!found_monotonic) |
514 | 0 | t->next_elapse_monotonic_or_boottime = v->next_elapse; |
515 | 0 | else |
516 | 0 | t->next_elapse_monotonic_or_boottime = MIN(t->next_elapse_monotonic_or_boottime, v->next_elapse); |
517 | |
|
518 | 0 | found_monotonic = true; |
519 | 0 | } |
520 | 0 | } |
521 | | |
522 | 0 | if (!found_monotonic && !found_realtime && !t->on_timezone_change && !t->on_clock_change) { |
523 | 0 | log_unit_debug(UNIT(t), "Timer is elapsed."); |
524 | 0 | timer_enter_elapsed(t, leave_around); |
525 | 0 | return; |
526 | 0 | } |
527 | | |
528 | 0 | if (found_monotonic) { |
529 | 0 | usec_t left; |
530 | |
|
531 | 0 | add_random_delay(t, &t->next_elapse_monotonic_or_boottime); |
532 | |
|
533 | 0 | left = usec_sub_unsigned(t->next_elapse_monotonic_or_boottime, triple_timestamp_by_clock(&ts, TIMER_MONOTONIC_CLOCK(t))); |
534 | 0 | log_unit_debug(UNIT(t), "Monotonic timer elapses in %s.", FORMAT_TIMESPAN(left, 0)); |
535 | |
|
536 | 0 | if (t->monotonic_event_source) { |
537 | 0 | r = sd_event_source_set_time(t->monotonic_event_source, t->next_elapse_monotonic_or_boottime); |
538 | 0 | if (r < 0) { |
539 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to reschedule monotonic event source: %m"); |
540 | 0 | goto fail; |
541 | 0 | } |
542 | | |
543 | 0 | r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_ONESHOT); |
544 | 0 | if (r < 0) { |
545 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to enable monotonic event source: %m"); |
546 | 0 | goto fail; |
547 | 0 | } |
548 | 0 | } else { |
549 | 0 | r = sd_event_add_time( |
550 | 0 | UNIT(t)->manager->event, |
551 | 0 | &t->monotonic_event_source, |
552 | 0 | t->wake_system ? CLOCK_BOOTTIME_ALARM : CLOCK_MONOTONIC, |
553 | 0 | t->next_elapse_monotonic_or_boottime, t->accuracy_usec, |
554 | 0 | timer_dispatch, t); |
555 | 0 | if (r < 0) { |
556 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to add monotonic event source: %m"); |
557 | 0 | goto fail; |
558 | 0 | } |
559 | | |
560 | 0 | (void) sd_event_source_set_description(t->monotonic_event_source, "timer-monotonic"); |
561 | 0 | } |
562 | |
|
563 | 0 | } else { |
564 | 0 | r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_OFF); |
565 | 0 | if (r < 0) { |
566 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to disable monotonic event source: %m"); |
567 | 0 | goto fail; |
568 | 0 | } |
569 | 0 | } |
570 | | |
571 | 0 | if (found_realtime) { |
572 | 0 | add_random_delay(t, &t->next_elapse_realtime); |
573 | |
|
574 | 0 | log_unit_debug(UNIT(t), "Realtime timer elapses at %s.", FORMAT_TIMESTAMP(t->next_elapse_realtime)); |
575 | |
|
576 | 0 | if (t->realtime_event_source) { |
577 | 0 | r = sd_event_source_set_time(t->realtime_event_source, t->next_elapse_realtime); |
578 | 0 | if (r < 0) { |
579 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to reschedule realtime event source: %m"); |
580 | 0 | goto fail; |
581 | 0 | } |
582 | | |
583 | 0 | r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_ONESHOT); |
584 | 0 | if (r < 0) { |
585 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to enable realtime event source: %m"); |
586 | 0 | goto fail; |
587 | 0 | } |
588 | 0 | } else { |
589 | 0 | r = sd_event_add_time( |
590 | 0 | UNIT(t)->manager->event, |
591 | 0 | &t->realtime_event_source, |
592 | 0 | t->wake_system ? CLOCK_REALTIME_ALARM : CLOCK_REALTIME, |
593 | 0 | t->next_elapse_realtime, t->accuracy_usec, |
594 | 0 | timer_dispatch, t); |
595 | 0 | if (r < 0) { |
596 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to add realtime event source: %m"); |
597 | 0 | goto fail; |
598 | 0 | } |
599 | | |
600 | 0 | (void) sd_event_source_set_description(t->realtime_event_source, "timer-realtime"); |
601 | 0 | } |
602 | |
|
603 | 0 | } else if (t->realtime_event_source) { |
604 | |
|
605 | 0 | r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_OFF); |
606 | 0 | if (r < 0) { |
607 | 0 | log_unit_warning_errno(UNIT(t), r, "Failed to disable realtime event source: %m"); |
608 | 0 | goto fail; |
609 | 0 | } |
610 | 0 | } |
611 | | |
612 | 0 | timer_set_state(t, TIMER_WAITING); |
613 | 0 | return; |
614 | | |
615 | 0 | fail: |
616 | 0 | timer_enter_dead(t, TIMER_FAILURE_RESOURCES); |
617 | 0 | } |
618 | | |
619 | 0 | static void timer_enter_running(Timer *t) { |
620 | 0 | _cleanup_(activation_details_unrefp) ActivationDetails *details = NULL; |
621 | 0 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
622 | 0 | Unit *trigger; |
623 | 0 | Job *job; |
624 | 0 | int r; |
625 | |
|
626 | 0 | assert(t); |
627 | | |
628 | | /* Don't start job if we are supposed to go down */ |
629 | 0 | if (unit_stop_pending(UNIT(t))) |
630 | 0 | return; |
631 | | |
632 | 0 | trigger = UNIT_TRIGGER(UNIT(t)); |
633 | 0 | if (!trigger) { |
634 | 0 | log_unit_error(UNIT(t), "Unit to trigger vanished."); |
635 | 0 | goto fail; |
636 | 0 | } |
637 | | |
638 | 0 | details = activation_details_new(UNIT(t)); |
639 | 0 | if (!details) { |
640 | 0 | log_oom(); |
641 | 0 | goto fail; |
642 | 0 | } |
643 | | |
644 | 0 | r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, &job); |
645 | 0 | if (r < 0) { |
646 | 0 | log_unit_warning(UNIT(t), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); |
647 | 0 | goto fail; |
648 | 0 | } |
649 | | |
650 | 0 | dual_timestamp_now(&t->last_trigger); |
651 | 0 | ACTIVATION_DETAILS_TIMER(details)->last_trigger = t->last_trigger; |
652 | |
|
653 | 0 | job_set_activation_details(job, details); |
654 | |
|
655 | 0 | if (t->stamp_path) |
656 | 0 | touch_file(t->stamp_path, true, t->last_trigger.realtime, UID_INVALID, GID_INVALID, MODE_INVALID); |
657 | |
|
658 | 0 | timer_set_state(t, TIMER_RUNNING); |
659 | 0 | return; |
660 | | |
661 | 0 | fail: |
662 | 0 | timer_enter_dead(t, TIMER_FAILURE_RESOURCES); |
663 | 0 | } |
664 | | |
665 | 0 | static int timer_start(Unit *u) { |
666 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
667 | 0 | int r; |
668 | |
|
669 | 0 | assert(IN_SET(t->state, TIMER_DEAD, TIMER_FAILED)); |
670 | |
|
671 | 0 | r = unit_acquire_invocation_id(u); |
672 | 0 | if (r < 0) |
673 | 0 | return r; |
674 | | |
675 | | /* Reenable all timers that depend on unit activation time */ |
676 | 0 | LIST_FOREACH(value, v, t->values) |
677 | 0 | if (v->base == TIMER_ACTIVE) |
678 | 0 | v->disabled = false; |
679 | |
|
680 | 0 | if (t->stamp_path) { |
681 | 0 | struct stat st; |
682 | |
|
683 | 0 | if (stat(t->stamp_path, &st) >= 0) { |
684 | 0 | usec_t ft; |
685 | | |
686 | | /* Load the file timestamp, but only if it is actually in the past. If it is in the future, |
687 | | * something is wrong with the system clock. */ |
688 | |
|
689 | 0 | ft = timespec_load(&st.st_mtim); |
690 | 0 | if (ft < now(CLOCK_REALTIME)) |
691 | 0 | t->last_trigger.realtime = ft; |
692 | 0 | else |
693 | 0 | log_unit_warning(u, "Not using persistent file timestamp %s as it is in the future.", |
694 | 0 | FORMAT_TIMESTAMP(ft)); |
695 | |
|
696 | 0 | } else if (errno == ENOENT) |
697 | | /* The timer has never run before, make sure a stamp file exists. */ |
698 | 0 | (void) touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); |
699 | 0 | } |
700 | |
|
701 | 0 | t->result = TIMER_SUCCESS; |
702 | 0 | timer_enter_waiting(t, false); |
703 | 0 | return 1; |
704 | 0 | } |
705 | | |
706 | 0 | static int timer_stop(Unit *u) { |
707 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
708 | |
|
709 | 0 | assert(IN_SET(t->state, TIMER_WAITING, TIMER_RUNNING, TIMER_ELAPSED)); |
710 | |
|
711 | 0 | timer_enter_dead(t, TIMER_SUCCESS); |
712 | 0 | return 1; |
713 | 0 | } |
714 | | |
715 | 1.83k | static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { |
716 | 1.83k | Timer *t = ASSERT_PTR(TIMER(u)); |
717 | | |
718 | 1.83k | assert(f); |
719 | 1.83k | assert(fds); |
720 | | |
721 | 1.83k | (void) serialize_item(f, "state", timer_state_to_string(t->state)); |
722 | 1.83k | (void) serialize_item(f, "result", timer_result_to_string(t->result)); |
723 | | |
724 | 1.83k | if (dual_timestamp_is_set(&t->last_trigger)) |
725 | 426 | (void) serialize_usec(f, "last-trigger-realtime", t->last_trigger.realtime); |
726 | | |
727 | 1.83k | if (t->last_trigger.monotonic > 0) |
728 | 210 | (void) serialize_usec(f, "last-trigger-monotonic", t->last_trigger.monotonic); |
729 | | |
730 | 1.83k | return 0; |
731 | 1.83k | } |
732 | | |
733 | 3.22k | static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { |
734 | 3.22k | Timer *t = ASSERT_PTR(TIMER(u)); |
735 | | |
736 | 3.22k | assert(key); |
737 | 3.22k | assert(value); |
738 | 3.22k | assert(fds); |
739 | | |
740 | 3.22k | if (streq(key, "state")) { |
741 | 415 | TimerState state; |
742 | | |
743 | 415 | state = timer_state_from_string(value); |
744 | 415 | if (state < 0) |
745 | 415 | log_unit_debug(u, "Failed to parse state value: %s", value); |
746 | 214 | else |
747 | 214 | t->deserialized_state = state; |
748 | | |
749 | 2.81k | } else if (streq(key, "result")) { |
750 | 599 | TimerResult f; |
751 | | |
752 | 599 | f = timer_result_from_string(value); |
753 | 599 | if (f < 0) |
754 | 599 | log_unit_debug(u, "Failed to parse result value: %s", value); |
755 | 388 | else if (f != TIMER_SUCCESS) |
756 | 194 | t->result = f; |
757 | | |
758 | 2.21k | } else if (streq(key, "last-trigger-realtime")) |
759 | 310 | (void) deserialize_usec(value, &t->last_trigger.realtime); |
760 | 1.90k | else if (streq(key, "last-trigger-monotonic")) |
761 | 319 | (void) deserialize_usec(value, &t->last_trigger.monotonic); |
762 | 1.58k | else |
763 | 1.58k | log_unit_debug(u, "Unknown serialization key: %s", key); |
764 | | |
765 | 3.22k | return 0; |
766 | 3.22k | } |
767 | | |
768 | 11.0k | static UnitActiveState timer_active_state(Unit *u) { |
769 | 11.0k | Timer *t = ASSERT_PTR(TIMER(u)); |
770 | | |
771 | 11.0k | return state_translation_table[t->state]; |
772 | 11.0k | } |
773 | | |
774 | 0 | static const char *timer_sub_state_to_string(Unit *u) { |
775 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
776 | |
|
777 | 0 | return timer_state_to_string(t->state); |
778 | 0 | } |
779 | | |
780 | 0 | static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { |
781 | 0 | Timer *t = ASSERT_PTR(TIMER(userdata)); |
782 | |
|
783 | 0 | if (t->state != TIMER_WAITING) |
784 | 0 | return 0; |
785 | | |
786 | 0 | log_unit_debug(UNIT(t), "Timer elapsed."); |
787 | 0 | timer_enter_running(t); |
788 | 0 | return 0; |
789 | 0 | } |
790 | | |
791 | 0 | static void timer_trigger_notify(Unit *u, Unit *other) { |
792 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
793 | |
|
794 | 0 | assert(other); |
795 | | |
796 | | /* Filter out invocations with bogus state */ |
797 | 0 | assert(UNIT_IS_LOAD_COMPLETE(other->load_state)); |
798 | | |
799 | | /* Reenable all timers that depend on unit state */ |
800 | 0 | LIST_FOREACH(value, v, t->values) |
801 | 0 | if (IN_SET(v->base, TIMER_UNIT_ACTIVE, TIMER_UNIT_INACTIVE)) |
802 | 0 | v->disabled = false; |
803 | |
|
804 | 0 | switch (t->state) { |
805 | | |
806 | 0 | case TIMER_WAITING: |
807 | 0 | case TIMER_ELAPSED: |
808 | | |
809 | | /* Recalculate sleep time */ |
810 | 0 | timer_enter_waiting(t, false); |
811 | 0 | break; |
812 | | |
813 | 0 | case TIMER_RUNNING: |
814 | |
|
815 | 0 | if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { |
816 | 0 | log_unit_debug(UNIT(t), "Got notified about unit deactivation."); |
817 | 0 | timer_enter_waiting(t, false); |
818 | 0 | } |
819 | 0 | break; |
820 | | |
821 | 0 | case TIMER_DEAD: |
822 | 0 | case TIMER_FAILED: |
823 | 0 | break; |
824 | | |
825 | 0 | default: |
826 | 0 | assert_not_reached(); |
827 | 0 | } |
828 | 0 | } |
829 | | |
830 | 0 | static void timer_reset_failed(Unit *u) { |
831 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
832 | |
|
833 | 0 | if (t->state == TIMER_FAILED) |
834 | 0 | timer_set_state(t, TIMER_DEAD); |
835 | |
|
836 | 0 | t->result = TIMER_SUCCESS; |
837 | 0 | } |
838 | | |
839 | 0 | static void timer_time_change(Unit *u) { |
840 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
841 | 0 | usec_t ts; |
842 | |
|
843 | 0 | if (t->state != TIMER_WAITING) |
844 | 0 | return; |
845 | | |
846 | | /* If we appear to have triggered in the future, the system clock must |
847 | | * have been set backwards. So let's rewind our own clock and allow |
848 | | * the future triggers to happen again :). Exactly the same as when |
849 | | * you start a timer unit with Persistent=yes. */ |
850 | 0 | ts = now(CLOCK_REALTIME); |
851 | 0 | if (t->last_trigger.realtime > ts) |
852 | 0 | t->last_trigger.realtime = ts; |
853 | |
|
854 | 0 | if (t->on_clock_change) { |
855 | 0 | log_unit_debug(u, "Time change, triggering activation."); |
856 | 0 | timer_enter_running(t); |
857 | 0 | } else { |
858 | 0 | log_unit_debug(u, "Time change, recalculating next elapse."); |
859 | 0 | timer_enter_waiting(t, true); |
860 | 0 | } |
861 | 0 | } |
862 | | |
863 | 0 | static void timer_timezone_change(Unit *u) { |
864 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
865 | |
|
866 | 0 | if (t->state != TIMER_WAITING) |
867 | 0 | return; |
868 | | |
869 | 0 | if (t->on_timezone_change) { |
870 | 0 | log_unit_debug(u, "Timezone change, triggering activation."); |
871 | 0 | timer_enter_running(t); |
872 | 0 | } else { |
873 | 0 | log_unit_debug(u, "Timezone change, recalculating next elapse."); |
874 | 0 | timer_enter_waiting(t, false); |
875 | 0 | } |
876 | 0 | } |
877 | | |
878 | 0 | static int timer_clean(Unit *u, ExecCleanMask mask) { |
879 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
880 | 0 | int r; |
881 | |
|
882 | 0 | assert(mask != 0); |
883 | |
|
884 | 0 | if (t->state != TIMER_DEAD) |
885 | 0 | return -EBUSY; |
886 | | |
887 | 0 | if (mask != EXEC_CLEAN_STATE) |
888 | 0 | return -EUNATCH; |
889 | | |
890 | 0 | r = timer_setup_persistent(t); |
891 | 0 | if (r < 0) |
892 | 0 | return r; |
893 | | |
894 | 0 | if (!t->stamp_path) |
895 | 0 | return -EUNATCH; |
896 | | |
897 | 0 | if (unlink(t->stamp_path) && errno != ENOENT) |
898 | 0 | return log_unit_error_errno(u, errno, "Failed to clean stamp file of timer: %m"); |
899 | | |
900 | 0 | return 0; |
901 | 0 | } |
902 | | |
903 | 0 | static int timer_can_clean(Unit *u, ExecCleanMask *ret) { |
904 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
905 | |
|
906 | 0 | assert(ret); |
907 | |
|
908 | 0 | *ret = t->persistent ? EXEC_CLEAN_STATE : 0; |
909 | 0 | return 0; |
910 | 0 | } |
911 | | |
912 | 0 | static int timer_test_startable(Unit *u) { |
913 | 0 | Timer *t = ASSERT_PTR(TIMER(u)); |
914 | 0 | int r; |
915 | |
|
916 | 0 | r = unit_test_trigger_loaded(u); |
917 | 0 | if (r < 0) |
918 | 0 | return r; |
919 | | |
920 | 0 | r = unit_test_start_limit(u); |
921 | 0 | if (r < 0) { |
922 | 0 | timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); |
923 | 0 | return r; |
924 | 0 | } |
925 | | |
926 | 0 | return true; |
927 | 0 | } |
928 | | |
929 | 35 | static void activation_details_timer_serialize(const ActivationDetails *details, FILE *f) { |
930 | 35 | const ActivationDetailsTimer *t = ASSERT_PTR(ACTIVATION_DETAILS_TIMER(details)); |
931 | | |
932 | 35 | assert(f); |
933 | 35 | assert(t); |
934 | | |
935 | 35 | (void) serialize_dual_timestamp(f, "activation-details-timer-last-trigger", &t->last_trigger); |
936 | 35 | } |
937 | | |
938 | 701 | static int activation_details_timer_deserialize(const char *key, const char *value, ActivationDetails **details) { |
939 | 701 | int r; |
940 | | |
941 | 701 | assert(key); |
942 | 701 | assert(value); |
943 | | |
944 | 701 | if (!details || !*details) |
945 | 0 | return -EINVAL; |
946 | | |
947 | 701 | ActivationDetailsTimer *t = ACTIVATION_DETAILS_TIMER(*details); |
948 | 701 | if (!t) |
949 | 0 | return -EINVAL; |
950 | | |
951 | 701 | if (!streq(key, "activation-details-timer-last-trigger")) |
952 | 284 | return -EINVAL; |
953 | | |
954 | 417 | r = deserialize_dual_timestamp(value, &t->last_trigger); |
955 | 417 | if (r < 0) |
956 | 218 | return r; |
957 | | |
958 | 199 | return 0; |
959 | 417 | } |
960 | | |
961 | 0 | static int activation_details_timer_append_env(const ActivationDetails *details, char ***strv) { |
962 | 0 | const ActivationDetailsTimer *t = ASSERT_PTR(ACTIVATION_DETAILS_TIMER(details)); |
963 | 0 | int r; |
964 | |
|
965 | 0 | assert(strv); |
966 | 0 | assert(t); |
967 | |
|
968 | 0 | if (!dual_timestamp_is_set(&t->last_trigger)) |
969 | 0 | return 0; |
970 | | |
971 | 0 | r = strv_extendf(strv, "TRIGGER_TIMER_REALTIME_USEC=" USEC_FMT, t->last_trigger.realtime); |
972 | 0 | if (r < 0) |
973 | 0 | return r; |
974 | | |
975 | 0 | r = strv_extendf(strv, "TRIGGER_TIMER_MONOTONIC_USEC=" USEC_FMT, t->last_trigger.monotonic); |
976 | 0 | if (r < 0) |
977 | 0 | return r; |
978 | | |
979 | 0 | return 2; /* Return the number of variables added to the env block */ |
980 | 0 | } |
981 | | |
982 | 0 | static int activation_details_timer_append_pair(const ActivationDetails *details, char ***strv) { |
983 | 0 | const ActivationDetailsTimer *t = ASSERT_PTR(ACTIVATION_DETAILS_TIMER(details)); |
984 | 0 | int r; |
985 | |
|
986 | 0 | assert(strv); |
987 | 0 | assert(t); |
988 | |
|
989 | 0 | if (!dual_timestamp_is_set(&t->last_trigger)) |
990 | 0 | return 0; |
991 | | |
992 | 0 | r = strv_extend(strv, "trigger_timer_realtime_usec"); |
993 | 0 | if (r < 0) |
994 | 0 | return r; |
995 | | |
996 | 0 | r = strv_extendf(strv, USEC_FMT, t->last_trigger.realtime); |
997 | 0 | if (r < 0) |
998 | 0 | return r; |
999 | | |
1000 | 0 | r = strv_extend(strv, "trigger_timer_monotonic_usec"); |
1001 | 0 | if (r < 0) |
1002 | 0 | return r; |
1003 | | |
1004 | 0 | r = strv_extendf(strv, USEC_FMT, t->last_trigger.monotonic); |
1005 | 0 | if (r < 0) |
1006 | 0 | return r; |
1007 | | |
1008 | 0 | return 2; /* Return the number of pairs added to the env block */ |
1009 | 0 | } |
1010 | | |
1011 | 0 | uint64_t timer_next_elapse_monotonic(const Timer *t) { |
1012 | 0 | assert(t); |
1013 | |
|
1014 | 0 | return (uint64_t) usec_shift_clock(t->next_elapse_monotonic_or_boottime, |
1015 | 0 | TIMER_MONOTONIC_CLOCK(t), CLOCK_MONOTONIC); |
1016 | 0 | } |
1017 | | |
1018 | | static const char* const timer_base_table[_TIMER_BASE_MAX] = { |
1019 | | [TIMER_ACTIVE] = "OnActiveSec", |
1020 | | [TIMER_BOOT] = "OnBootSec", |
1021 | | [TIMER_STARTUP] = "OnStartupSec", |
1022 | | [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", |
1023 | | [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", |
1024 | | [TIMER_CALENDAR] = "OnCalendar", |
1025 | | }; |
1026 | | |
1027 | | DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); |
1028 | | |
1029 | 0 | char* timer_base_to_usec_string(TimerBase i) { |
1030 | 0 | _cleanup_free_ char *buf = NULL; |
1031 | 0 | const char *s; |
1032 | 0 | size_t l; |
1033 | |
|
1034 | 0 | s = timer_base_to_string(i); |
1035 | |
|
1036 | 0 | if (endswith(s, "Sec")) { |
1037 | | /* s/Sec/USec/ */ |
1038 | 0 | l = strlen(s); |
1039 | 0 | buf = new(char, l+2); |
1040 | 0 | if (!buf) |
1041 | 0 | return NULL; |
1042 | | |
1043 | 0 | memcpy(buf, s, l-3); |
1044 | 0 | memcpy(buf+l-3, "USec", 5); |
1045 | 0 | } else { |
1046 | 0 | buf = strdup(s); |
1047 | 0 | if (!buf) |
1048 | 0 | return NULL; |
1049 | 0 | } |
1050 | | |
1051 | 0 | return TAKE_PTR(buf); |
1052 | 0 | } |
1053 | | |
1054 | | static const char* const timer_result_table[_TIMER_RESULT_MAX] = { |
1055 | | [TIMER_SUCCESS] = "success", |
1056 | | [TIMER_FAILURE_RESOURCES] = "resources", |
1057 | | [TIMER_FAILURE_START_LIMIT_HIT] = "start-limit-hit", |
1058 | | }; |
1059 | | |
1060 | | DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); |
1061 | | |
1062 | | const UnitVTable timer_vtable = { |
1063 | | .object_size = sizeof(Timer), |
1064 | | |
1065 | | .sections = |
1066 | | "Unit\0" |
1067 | | "Timer\0" |
1068 | | "Install\0", |
1069 | | .private_section = "Timer", |
1070 | | |
1071 | | .can_transient = true, |
1072 | | .can_fail = true, |
1073 | | .can_trigger = true, |
1074 | | |
1075 | | .init = timer_init, |
1076 | | .done = timer_done, |
1077 | | .load = timer_load, |
1078 | | |
1079 | | .coldplug = timer_coldplug, |
1080 | | |
1081 | | .dump = timer_dump, |
1082 | | |
1083 | | .start = timer_start, |
1084 | | .stop = timer_stop, |
1085 | | |
1086 | | .clean = timer_clean, |
1087 | | .can_clean = timer_can_clean, |
1088 | | |
1089 | | .serialize = timer_serialize, |
1090 | | .deserialize_item = timer_deserialize_item, |
1091 | | |
1092 | | .active_state = timer_active_state, |
1093 | | .sub_state_to_string = timer_sub_state_to_string, |
1094 | | |
1095 | | .trigger_notify = timer_trigger_notify, |
1096 | | |
1097 | | .reset_failed = timer_reset_failed, |
1098 | | .time_change = timer_time_change, |
1099 | | .timezone_change = timer_timezone_change, |
1100 | | |
1101 | | .bus_set_property = bus_timer_set_property, |
1102 | | |
1103 | | .test_startable = timer_test_startable, |
1104 | | }; |
1105 | | |
1106 | | const ActivationDetailsVTable activation_details_timer_vtable = { |
1107 | | .object_size = sizeof(ActivationDetailsTimer), |
1108 | | |
1109 | | .serialize = activation_details_timer_serialize, |
1110 | | .deserialize = activation_details_timer_deserialize, |
1111 | | .append_env = activation_details_timer_append_env, |
1112 | | .append_pair = activation_details_timer_append_pair, |
1113 | | }; |