]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/timer.c
core: clearly refuse OnFailure= deps on units that can't fail
[thirdparty/systemd.git] / src / core / timer.c
index 4e7fade999cd4b4e02f4d8ab02f365fdea3c5393..57d979d52da1e58d3e7b79319cae29418ee9cf66 100644 (file)
@@ -1,11 +1,16 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <errno.h>
 
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-util.h"
 #include "dbus-timer.h"
+#include "dbus-unit.h"
 #include "fs-util.h"
 #include "parse-util.h"
 #include "random-util.h"
@@ -68,11 +73,9 @@ static void timer_done(Unit *u) {
 
 static int timer_verify(Timer *t) {
         assert(t);
+        assert(UNIT(t)->load_state == UNIT_LOADED);
 
-        if (UNIT(t)->load_state != UNIT_LOADED)
-                return 0;
-
-        if (!t->values) {
+        if (!t->values && !t->on_clock_change && !t->on_timezone_change) {
                 log_unit_error(UNIT(t), "Timer unit lacks value setting. Refusing.");
                 return -ENOEXEC;
         }
@@ -141,7 +144,7 @@ static int timer_setup_persistent(Timer *t) {
                 if (r < 0)
                         return r;
 
-                t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id);
+                t->stamp_path = strjoin("/var/lib/systemd/timers/stamp-", UNIT(t)->id);
         } else {
                 const char *e;
 
@@ -173,24 +176,25 @@ static int timer_load(Unit *u) {
         assert(u);
         assert(u->load_state == UNIT_STUB);
 
-        r = unit_load_fragment_and_dropin(u);
+        r = unit_load_fragment_and_dropin(u, true);
         if (r < 0)
                 return r;
 
-        if (u->load_state == UNIT_LOADED) {
+        if (u->load_state != UNIT_LOADED)
+                return 0;
 
-                r = timer_add_trigger_dependencies(t);
-                if (r < 0)
-                        return r;
+        /* This is a new unit? Then let's add in some extras */
+        r = timer_add_trigger_dependencies(t);
+        if (r < 0)
+                return r;
 
-                r = timer_setup_persistent(t);
-                if (r < 0)
-                        return r;
+        r = timer_setup_persistent(t);
+        if (r < 0)
+                return r;
 
-                r = timer_add_default_dependencies(t);
-                if (r < 0)
-                        return r;
-        }
+        r = timer_add_default_dependencies(t);
+        if (r < 0)
+                return r;
 
         return timer_verify(t);
 }
@@ -210,14 +214,18 @@ static void timer_dump(Unit *u, FILE *f, const char *prefix) {
                 "%sPersistent: %s\n"
                 "%sWakeSystem: %s\n"
                 "%sAccuracy: %s\n"
-                "%sRemainAfterElapse: %s\n",
+                "%sRemainAfterElapse: %s\n"
+                "%sOnClockChange: %s\n"
+                "%sOnTimeZoneChange: %s\n",
                 prefix, timer_state_to_string(t->state),
                 prefix, timer_result_to_string(t->result),
                 prefix, trigger ? trigger->id : "n/a",
                 prefix, yes_no(t->persistent),
                 prefix, yes_no(t->wake_system),
                 prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1),
-                prefix, yes_no(t->remain_after_elapse));
+                prefix, yes_no(t->remain_after_elapse),
+                prefix, yes_no(t->on_clock_change),
+                prefix, yes_no(t->on_timezone_change));
 
         LIST_FOREACH(value, v, t->values) {
 
@@ -247,6 +255,9 @@ static void timer_set_state(Timer *t, TimerState state) {
         TimerState old_state;
         assert(t);
 
+        if (t->state != state)
+                bus_unit_send_pending_change_signal(UNIT(t), false);
+
         old_state = t->state;
         t->state = state;
 
@@ -263,7 +274,7 @@ static void timer_set_state(Timer *t, TimerState state) {
         unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], 0);
 }
 
-static void timer_enter_waiting(Timer *t, bool initial, bool time_change);
+static void timer_enter_waiting(Timer *t, bool time_change);
 
 static int timer_coldplug(Unit *u) {
         Timer *t = TIMER(u);
@@ -275,7 +286,7 @@ static int timer_coldplug(Unit *u) {
                 return 0;
 
         if (t->deserialized_state == TIMER_WAITING)
-                timer_enter_waiting(t, false, false);
+                timer_enter_waiting(t, false);
         else
                 timer_set_state(t, t->deserialized_state);
 
@@ -331,7 +342,7 @@ static void add_random(Timer *t, usec_t *v) {
         log_unit_debug(UNIT(t), "Adding %s random time.", format_timespan(s, sizeof(s), add, 0));
 }
 
-static void timer_enter_waiting(Timer *t, bool initial, bool time_change) {
+static void timer_enter_waiting(Timer *t, bool time_change) {
         bool found_monotonic = false, found_realtime = false;
         bool leave_around = false;
         triple_timestamp ts;
@@ -376,6 +387,13 @@ static void timer_enter_waiting(Timer *t, bool initial, bool time_change) {
                         if (r < 0)
                                 continue;
 
+                        /* To make the delay due to RandomizedDelaySec= work even at boot,
+                         * if the scheduled time has already passed, set the time when systemd
+                         * first started as the scheduled time.
+                         * Also, we don't have to check t->persistent since the logic implicitly express true. */
+                        if (v->next_elapse < UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].realtime)
+                                v->next_elapse = UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].realtime;
+
                         if (!found_realtime)
                                 t->next_elapse_realtime = v->next_elapse;
                         else
@@ -411,28 +429,16 @@ static void timer_enter_waiting(Timer *t, bool initial, bool time_change) {
 
                         case TIMER_UNIT_ACTIVE:
                                 leave_around = true;
-                                base = trigger->inactive_exit_timestamp.monotonic;
-
-                                if (base <= 0)
-                                        base = t->last_trigger.monotonic;
-
+                                base = MAX(trigger->inactive_exit_timestamp.monotonic, t->last_trigger.monotonic);
                                 if (base <= 0)
                                         continue;
-                                base = MAX(base, t->last_trigger.monotonic);
-
                                 break;
 
                         case TIMER_UNIT_INACTIVE:
                                 leave_around = true;
-                                base = trigger->inactive_enter_timestamp.monotonic;
-
-                                if (base <= 0)
-                                        base = t->last_trigger.monotonic;
-
+                                base = MAX(trigger->inactive_enter_timestamp.monotonic, t->last_trigger.monotonic);
                                 if (base <= 0)
                                         continue;
-                                base = MAX(base, t->last_trigger.monotonic);
-
                                 break;
 
                         default:
@@ -441,7 +447,8 @@ static void timer_enter_waiting(Timer *t, bool initial, bool time_change) {
 
                         v->next_elapse = usec_add(usec_shift_clock(base, CLOCK_MONOTONIC, TIMER_MONOTONIC_CLOCK(t)), v->value);
 
-                        if (!initial && !time_change &&
+                        if (dual_timestamp_is_set(&t->last_trigger) &&
+                            !time_change &&
                             v->next_elapse < triple_timestamp_by_clock(&ts, TIMER_MONOTONIC_CLOCK(t)) &&
                             IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) {
                                 /* This is a one time trigger, disable it now */
@@ -458,7 +465,7 @@ static void timer_enter_waiting(Timer *t, bool initial, bool time_change) {
                 }
         }
 
-        if (!found_monotonic && !found_realtime) {
+        if (!found_monotonic && !found_realtime && !t->on_timezone_change && !t->on_clock_change) {
                 log_unit_debug(UNIT(t), "Timer is elapsed.");
                 timer_enter_elapsed(t, leave_around);
                 return;
@@ -563,7 +570,7 @@ static void timer_enter_running(Timer *t) {
                 return;
         }
 
-        r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL);
+        r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, NULL);
         if (r < 0)
                 goto fail;
 
@@ -583,19 +590,16 @@ fail:
 static int timer_start(Unit *u) {
         Timer *t = TIMER(u);
         TimerValue *v;
-        Unit *trigger;
         int r;
 
         assert(t);
         assert(IN_SET(t->state, TIMER_DEAD, TIMER_FAILED));
 
-        trigger = UNIT_TRIGGER(u);
-        if (!trigger || trigger->load_state != UNIT_LOADED) {
-                log_unit_error(u, "Refusing to start, unit to trigger not loaded.");
-                return -ENOENT;
-        }
+        r = unit_test_trigger_loaded(u);
+        if (r < 0)
+                return r;
 
-        r = unit_start_limit_test(u);
+        r = unit_test_start_limit(u);
         if (r < 0) {
                 timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT);
                 return r;
@@ -639,7 +643,7 @@ static int timer_start(Unit *u) {
         }
 
         t->result = TIMER_SUCCESS;
-        timer_enter_waiting(t, true, false);
+        timer_enter_waiting(t, false);
         return 1;
 }
 
@@ -754,14 +758,14 @@ static void timer_trigger_notify(Unit *u, Unit *other) {
         case TIMER_ELAPSED:
 
                 /* Recalculate sleep time */
-                timer_enter_waiting(t, false, false);
+                timer_enter_waiting(t, false);
                 break;
 
         case TIMER_RUNNING:
 
                 if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
                         log_unit_debug(UNIT(t), "Got notified about unit deactivation.");
-                        timer_enter_waiting(t, false, false);
+                        timer_enter_waiting(t, false);
                 }
                 break;
 
@@ -802,8 +806,13 @@ static void timer_time_change(Unit *u) {
         if (t->last_trigger.realtime > ts)
                 t->last_trigger.realtime = ts;
 
-        log_unit_debug(u, "Time change, recalculating next elapse.");
-        timer_enter_waiting(t, false, true);
+        if (t->on_clock_change) {
+                log_unit_debug(u, "Time change, triggering activation.");
+                timer_enter_running(t);
+        } else {
+                log_unit_debug(u, "Time change, recalculating next elapse.");
+                timer_enter_waiting(t, true);
+        }
 }
 
 static void timer_timezone_change(Unit *u) {
@@ -814,8 +823,48 @@ static void timer_timezone_change(Unit *u) {
         if (t->state != TIMER_WAITING)
                 return;
 
-        log_unit_debug(u, "Timezone change, recalculating next elapse.");
-        timer_enter_waiting(t, false, false);
+        if (t->on_timezone_change) {
+                log_unit_debug(u, "Timezone change, triggering activation.");
+                timer_enter_running(t);
+        } else {
+                log_unit_debug(u, "Timezone change, recalculating next elapse.");
+                timer_enter_waiting(t, false);
+        }
+}
+
+static int timer_clean(Unit *u, ExecCleanMask mask) {
+        Timer *t = TIMER(u);
+        int r;
+
+        assert(t);
+        assert(mask != 0);
+
+        if (t->state != TIMER_DEAD)
+                return -EBUSY;
+
+        if (!IN_SET(mask, EXEC_CLEAN_STATE))
+                return -EUNATCH;
+
+        r = timer_setup_persistent(t);
+        if (r < 0)
+                return r;
+
+        if (!t->stamp_path)
+                return -EUNATCH;
+
+        if (unlink(t->stamp_path) && errno != ENOENT)
+                return log_unit_error_errno(u, errno, "Failed to clean stamp file of timer: %m");
+
+        return 0;
+}
+
+static int timer_can_clean(Unit *u, ExecCleanMask *ret) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+
+        *ret = t->persistent ? EXEC_CLEAN_STATE : 0;
+        return 0;
 }
 
 static const char* const timer_base_table[_TIMER_BASE_MAX] = {
@@ -846,6 +895,10 @@ const UnitVTable timer_vtable = {
                 "Install\0",
         .private_section = "Timer",
 
+        .can_transient = true,
+        .can_fail = true,
+        .can_trigger = true,
+
         .init = timer_init,
         .done = timer_done,
         .load = timer_load,
@@ -857,6 +910,9 @@ const UnitVTable timer_vtable = {
         .start = timer_start,
         .stop = timer_stop,
 
+        .clean = timer_clean,
+        .can_clean = timer_can_clean,
+
         .serialize = timer_serialize,
         .deserialize_item = timer_deserialize_item,
 
@@ -871,6 +927,4 @@ const UnitVTable timer_vtable = {
 
         .bus_vtable = bus_timer_vtable,
         .bus_set_property = bus_timer_set_property,
-
-        .can_transient = true,
 };