chiark / gitweb /
timer: fully implement timer units
authorLennart Poettering <lennart@poettering.net>
Sun, 23 May 2010 23:45:54 +0000 (01:45 +0200)
committerLennart Poettering <lennart@poettering.net>
Sun, 23 May 2010 23:45:54 +0000 (01:45 +0200)
19 files changed:
Makefile.am
fixme
src/dbus-manager.c
src/dbus-timer.c [new file with mode: 0644]
src/dbus-timer.h [new file with mode: 0644]
src/dbus.c
src/load-fragment.c
src/logger.c
src/manager.c
src/manager.h
src/ratelimit.c
src/socket.c
src/timer.c
src/timer.h
src/unit.c
src/unit.h
src/util.c
src/util.h
src/utmp-wtmp.c

index b3e9dfde811e9990572cc944ae961505f28025a6..b404bbedff464cb876cbe199f15aa78c9349471e 100644 (file)
@@ -71,6 +71,7 @@ interface_DATA = \
        org.freedesktop.systemd1.Unit.xml \
        org.freedesktop.systemd1.Service.xml \
        org.freedesktop.systemd1.Socket.xml \
+       org.freedesktop.systemd1.Timer.xml \
        org.freedesktop.systemd1.Target.xml \
        org.freedesktop.systemd1.Device.xml \
        org.freedesktop.systemd1.Mount.xml \
@@ -196,6 +197,7 @@ COMMON_SOURCES = \
         src/dbus-job.c \
        src/dbus-service.c \
        src/dbus-socket.c \
+       src/dbus-timer.c \
        src/dbus-target.c \
        src/dbus-mount.c \
        src/dbus-automount.c \
diff --git a/fixme b/fixme
index 6b6f7f7d0a868efddb485c98282e929477c60306..7abf795cf61ccc932bf0f63991357940c15b2caf 100644 (file)
--- a/fixme
+++ b/fixme
@@ -66,6 +66,8 @@
 
 * introduce exit.target for session instances
 
+* use _PATH_XXX
+
 Regularly:
 
 * look for close() vs. close_nointr() vs. close_nointr_nofail()
index 6b658d1931e7d46819f626715d4f54e7b4396684..6a323019fe8ccf6fbebfbf1ad391b2117e0adb16 100644 (file)
@@ -171,7 +171,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection  *connection
         const BusProperty properties[] = {
                 { "org.freedesktop.systemd1.Manager", "Version",       bus_property_append_string,    "s", PACKAGE_STRING     },
                 { "org.freedesktop.systemd1.Manager", "RunningAs",     bus_manager_append_running_as, "s", &m->running_as     },
-                { "org.freedesktop.systemd1.Manager", "BootTimestamp", bus_property_append_uint64,    "t", &m->boot_timestamp },
+                { "org.freedesktop.systemd1.Manager", "BootTimestamp", bus_property_append_uint64,    "t", &m->startup_timestamp.realtime },
                 { "org.freedesktop.systemd1.Manager", "LogLevel",      bus_manager_append_log_level,  "s", NULL               },
                 { "org.freedesktop.systemd1.Manager", "LogTarget",     bus_manager_append_log_target, "s", NULL               },
                 { "org.freedesktop.systemd1.Manager", "NNames",        bus_manager_append_n_names,    "u", NULL               },
diff --git a/src/dbus-timer.c b/src/dbus-timer.c
new file mode 100644 (file)
index 0000000..d572907
--- /dev/null
@@ -0,0 +1,52 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-timer.h"
+#include "dbus-execute.h"
+
+#define BUS_TIMER_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Timer\">\n"        \
+        "  <property name=\"Unit\" type=\"s\"  access=\"read\"/>\n"     \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_TIMER_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_timer_interface[] = BUS_TIMER_INTERFACE;
+
+DBusHandlerResult bus_timer_message_handler(Unit *u, DBusMessage *message) {
+        const BusProperty properties[] = {
+                BUS_UNIT_PROPERTIES,
+                { "org.freedesktop.systemd1.Timer", "Unit", bus_property_append_string, "s", &u->timer.unit->meta.id },
+                { NULL, NULL, NULL, NULL, NULL }
+        };
+
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
+}
diff --git a/src/dbus-timer.h b/src/dbus-timer.h
new file mode 100644 (file)
index 0000000..250e818
--- /dev/null
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbustimerhfoo
+#define foodbustimerhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_timer_message_handler(Unit *u, DBusMessage *message);
+
+extern const char bus_timer_interface[];
+
+#endif
index 5caf1eb19176dfafd579986ec1cc085f56561b56..6dd495d4df6be3ea542f87bae2f2006bafa57f32 100644 (file)
@@ -40,6 +40,7 @@
 #include "dbus-automount.h"
 #include "dbus-snapshot.h"
 #include "dbus-swap.h"
+#include "dbus-timer.h"
 
 static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE;
 static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE;
@@ -58,6 +59,7 @@ const char *const bus_interface_table[] = {
         "org.freedesktop.systemd1.Automount",  bus_automount_interface,
         "org.freedesktop.systemd1.Snapshot",   bus_snapshot_interface,
         "org.freedesktop.systemd1.Swap",       bus_swap_interface,
+        "org.freedesktop.systemd1.Timer",      bus_timer_interface,
         NULL
 };
 
index 70b69233c7f5bb5ec19081c81e6c2d080fdab895..889e6211008d1b49e84acfa2c5b2d4eba794efb6 100644 (file)
@@ -1002,6 +1002,72 @@ static int config_parse_mount_flags(
         return 0;
 }
 
+static int config_parse_timer(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Timer *t = data;
+        usec_t u;
+        int r;
+        TimerValue *v;
+        TimerBase b;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((b = timer_base_from_string(lvalue)) < 0) {
+                log_error("[%s:%u] Failed to parse timer base: %s", filename, line, lvalue);
+                return -EINVAL;
+        }
+
+        if ((r = parse_usec(rvalue, &u)) < 0) {
+                log_error("[%s:%u] Failed to parse timer value: %s", filename, line, rvalue);
+                return r;
+        }
+
+        if (!(v = new0(TimerValue, 1)))
+                return -ENOMEM;
+
+        v->base = b;
+        v->value = u;
+
+        LIST_PREPEND(TimerValue, value, t->values, v);
+
+        return 0;
+}
+
+static int config_parse_timer_unit(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Timer *t = data;
+        int r;
+
+        if (endswith(rvalue, ".timer")) {
+                log_error("[%s:%u] Unit cannot be of type timer: %s", filename, line, rvalue);
+                return -EINVAL;
+        }
+
+        if ((r = manager_load_unit(t->meta.manager, rvalue, NULL, &t->unit)) < 0) {
+                log_error("[%s:%u] Failed to load unit: %s", filename, line, rvalue);
+                return r;
+        }
+
+        return 0;
+}
+
 #define FOLLOW_MAX 8
 
 static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
@@ -1161,6 +1227,8 @@ static void dump_items(FILE *f, const ConfigItem *items) {
                 { config_parse_path_strv,        "PATH [...]" },
                 { config_parse_mount_flags,      "MOUNTFLAG [...]" },
                 { config_parse_description,      "DESCRIPTION" },
+                { config_parse_timer,            "TIMER" },
+                { config_parse_timer_unit,       "NAME" },
         };
 
         assert(f);
@@ -1321,6 +1389,13 @@ static int load_from_path(Unit *u, const char *path) {
                 { "What",                   config_parse_path,            &u->swap.parameters_fragment.what,               "Swap" },
                 { "Priority",               config_parse_int,             &u->swap.parameters_fragment.priority,           "Swap" },
 
+                { "OnActive",               config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnBoot",                 config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnStartup",              config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnUnitActive",           config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnUnitInactive",         config_parse_timer,           &u->timer,                                       "Timer" },
+                { "Unit",                   config_parse_timer_unit,      &u->timer,                                       "Timer" },
+
                 { NULL, NULL, NULL, NULL }
         };
 
index 2e036dd77bc14153d9c3e90a5d6cbcd2b28d569e..5c7e4ee42b9cd4554450a210ade8f5e55d8d584b 100644 (file)
@@ -89,7 +89,7 @@ struct Stream {
         LIST_FIELDS(Stream, stream);
 };
 
-static int stream_log(Stream *s, char *p, usec_t timestamp) {
+static int stream_log(Stream *s, char *p, usec_t ts) {
 
         char header_priority[16], header_time[64], header_pid[16];
         struct iovec iovec[5];
@@ -134,7 +134,7 @@ static int stream_log(Stream *s, char *p, usec_t timestamp) {
                 time_t t;
                 struct tm *tm;
 
-                t = (time_t) (timestamp / USEC_PER_SEC);
+                t = (time_t) (ts / USEC_PER_SEC);
                 if (!(tm = localtime(&t)))
                         return -EINVAL;
 
@@ -177,7 +177,7 @@ static int stream_log(Stream *s, char *p, usec_t timestamp) {
         return 0;
 }
 
-static int stream_line(Stream *s, char *p, usec_t timestamp) {
+static int stream_line(Stream *s, char *p, usec_t ts) {
         int r;
 
         assert(s);
@@ -236,13 +236,13 @@ static int stream_line(Stream *s, char *p, usec_t timestamp) {
                 return 0;
 
         case STREAM_RUNNING:
-                return stream_log(s, p, timestamp);
+                return stream_log(s, p, ts);
         }
 
         assert_not_reached("Unknown stream state");
 }
 
-static int stream_scan(Stream *s, usec_t timestamp) {
+static int stream_scan(Stream *s, usec_t ts) {
         char *p;
         size_t remaining;
         int r = 0;
@@ -259,7 +259,7 @@ static int stream_scan(Stream *s, usec_t timestamp) {
 
                 *newline = 0;
 
-                if ((r = stream_line(s, p, timestamp)) >= 0) {
+                if ((r = stream_line(s, p, ts)) >= 0) {
                         remaining -= newline-p+1;
                         p = newline+1;
                 }
@@ -273,7 +273,7 @@ static int stream_scan(Stream *s, usec_t timestamp) {
         return r;
 }
 
-static int stream_process(Stream *s, usec_t timestamp) {
+static int stream_process(Stream *s, usec_t ts) {
         ssize_t l;
         int r;
         assert(s);
@@ -292,7 +292,7 @@ static int stream_process(Stream *s, usec_t timestamp) {
                 return 0;
 
         s->length += l;
-        r = stream_scan(s, timestamp);
+        r = stream_scan(s, ts);
 
         if (r < 0)
                 return r;
@@ -501,10 +501,10 @@ static int process_event(Server *s, struct epoll_event *ev) {
                 }
 
         } else {
-                usec_t timestamp;
+                usec_t ts;
                 Stream *stream = ev->data.ptr;
 
-                timestamp = now(CLOCK_REALTIME);
+                ts = now(CLOCK_REALTIME);
 
                 if (!(ev->events & EPOLLIN)) {
                         log_info("Got invalid event from epoll. (2)");
@@ -512,7 +512,7 @@ static int process_event(Server *s, struct epoll_event *ev) {
                         return 0;
                 }
 
-                if ((r = stream_process(stream, timestamp)) <= 0) {
+                if ((r = stream_process(stream, ts)) <= 0) {
 
                         if (r < 0)
                                 log_info("Got error on stream: %s", strerror(-r));
index 5d88875984179949c3f9f80a79c3d1b8fe05db97..2a773c6dbbd98bc3d10dab2ea737094b92bc57bf 100644 (file)
@@ -361,7 +361,7 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
         if (!(m = new0(Manager, 1)))
                 return -ENOMEM;
 
-        m->boot_timestamp = now(CLOCK_REALTIME);
+        timestamp_get(&m->startup_timestamp);
 
         m->running_as = running_as;
         m->confirm_spawn = confirm_spawn;
@@ -2101,7 +2101,7 @@ void manager_write_utmp_reboot(Manager *m) {
         if (!manager_utmp_good(m))
                 return;
 
-        if ((r = utmp_put_reboot(m->boot_timestamp)) < 0) {
+        if ((r = utmp_put_reboot(m->startup_timestamp.realtime)) < 0) {
 
                 if (r != -ENOENT && r != -EROFS)
                         log_warning("Failed to write utmp/wtmp: %s", strerror(-r));
index 78923003e25877999cd3d3c8a855cc032f8fba79..9548b0f6138764c85dbb24b062bb3f9451c77a96 100644 (file)
@@ -179,7 +179,7 @@ struct Manager {
 
         char **environment;
 
-        usec_t boot_timestamp;
+        timestamp startup_timestamp;
 
         /* Data specific to the device subsystem */
         struct udev* udev;
index 1e5ed03c55dbde295d096ba679cdacaae588db07..fc0f71de8bf2ebb4e562b496105a77629321ad7e 100644 (file)
  * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */
 
 bool ratelimit_test(RateLimit *r) {
-        usec_t timestamp;
+        usec_t ts;
 
-        timestamp = now(CLOCK_MONOTONIC);
+        ts = now(CLOCK_MONOTONIC);
 
         assert(r);
         assert(r->interval > 0);
         assert(r->burst > 0);
 
         if (r->begin <= 0 ||
-            r->begin + r->interval < timestamp) {
+            r->begin + r->interval < ts) {
 
                 if (r->n_missed > 0)
                         log_warning("%u events suppressed", r->n_missed);
 
-                r->begin = timestamp;
+                r->begin = ts;
 
                 /* Reset counters */
                 r->n_printed = 0;
index 4647d3174e47a0900e57f30e21470ccc63064a22..ef7674f46386fae40a3093316ae51a105ec1f382 100644 (file)
@@ -1189,6 +1189,9 @@ static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
         assert(s);
         assert(fd >= 0);
 
+        if (s->state != SOCKET_LISTENING)
+                return;
+
         log_debug("Incoming traffic on %s", u->meta.id);
 
         if (events != EPOLLIN) {
index 41aeb7f3a5dbebb8c2f4cf1d3326a9463d7402da..e95b4d66e10e3b1262cf4da292b817ef882ee163 100644 (file)
 #include <errno.h>
 
 #include "unit.h"
+#include "unit-name.h"
 #include "timer.h"
+#include "dbus-timer.h"
+
+static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = {
+        [TIMER_DEAD] = UNIT_INACTIVE,
+        [TIMER_WAITING] = UNIT_ACTIVE,
+        [TIMER_RUNNING] = UNIT_ACTIVE,
+        [TIMER_ELAPSED] = UNIT_ACTIVE,
+        [TIMER_MAINTAINANCE] = UNIT_INACTIVE
+};
+
+static void timer_init(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+        assert(u->meta.load_state == UNIT_STUB);
+
+        t->next_elapse = (usec_t) -1;
+        t->timer_watch.type = WATCH_INVALID;
+}
 
 static void timer_done(Unit *u) {
         Timer *t = TIMER(u);
+        TimerValue *v;
+
+        assert(t);
+
+        while ((v = t->values)) {
+                LIST_REMOVE(TimerValue, value, t->values, v);
+                free(v);
+        }
+
+        unit_unwatch_timer(u, &t->timer_watch);
+}
+
+static int timer_verify(Timer *t) {
+        assert(t);
+
+        if (UNIT(t)->meta.load_state != UNIT_LOADED)
+                return 0;
+
+        if (!t->values) {
+                log_error("%s lacks value setting. Refusing.", t->meta.id);
+                return -EINVAL;
+        }
+
+        return 0;
+}
+
+static int timer_load(Unit *u) {
+        Timer *t = TIMER(u);
+        int r;
+
+        assert(u);
+        assert(u->meta.load_state == UNIT_STUB);
+
+        if ((r = unit_load_fragment_and_dropin(u)) < 0)
+                return r;
+
+        if (u->meta.load_state == UNIT_LOADED) {
+
+                if (!t->unit)
+                        if ((r = unit_load_related_unit(u, ".service", &t->unit)))
+                                return r;
+
+                if ((r = unit_add_dependency(u, UNIT_BEFORE, t->unit, true)) < 0)
+                        return r;
+        }
+
+        return timer_verify(t);
+}
+
+static void timer_dump(Unit *u, FILE *f, const char *prefix) {
+        Timer *t = TIMER(u);
+        const char *prefix2;
+        char *p2;
+        TimerValue *v;
+        char
+                timespan1[FORMAT_TIMESPAN_MAX];
 
+        p2 = strappend(prefix, "\t");
+        prefix2 = p2 ? p2 : prefix;
+
+        fprintf(f,
+                "%sTimer State: %s\n"
+                "%sUnit: %s\n",
+                prefix, timer_state_to_string(t->state),
+                prefix, t->unit->meta.id);
+
+        LIST_FOREACH(value, v, t->values)
+                fprintf(f,
+                        "%s%s: %s\n",
+                        prefix,
+                        timer_base_to_string(v->base),
+                        strna(format_timespan(timespan1, sizeof(timespan1), v->value)));
+
+        free(p2);
+}
+
+static void timer_set_state(Timer *t, TimerState state) {
+        TimerState old_state;
         assert(t);
+
+        old_state = t->state;
+        t->state = state;
+
+        if (state != TIMER_WAITING)
+                unit_unwatch_timer(UNIT(t), &t->timer_watch);
+
+        if (state != old_state)
+                log_debug("%s changed %s -> %s",
+                          t->meta.id,
+                          timer_state_to_string(old_state),
+                          timer_state_to_string(state));
+
+        unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state]);
+}
+
+static void timer_enter_waiting(Timer *t, bool initial);
+
+static int timer_coldplug(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(t->state == TIMER_DEAD);
+
+        if (t->deserialized_state != t->state) {
+
+                if (t->deserialized_state == TIMER_WAITING ||
+                    t->deserialized_state == TIMER_RUNNING ||
+                    t->deserialized_state == TIMER_ELAPSED)
+                        timer_enter_waiting(t, false);
+                else
+                        timer_set_state(t, t->deserialized_state);
+        }
+
+        return 0;
+}
+
+static void timer_enter_dead(Timer *t, bool success) {
+        assert(t);
+
+        if (!success)
+                t->failure = true;
+
+        timer_set_state(t, t->failure ? TIMER_MAINTAINANCE : TIMER_DEAD);
+}
+
+static void timer_enter_waiting(Timer *t, bool initial) {
+        TimerValue *v;
+        usec_t base = 0, delay, n;
+        bool found = false;
+        int r;
+
+        n = now(CLOCK_MONOTONIC);
+
+        LIST_FOREACH(value, v, t->values) {
+
+                if (v->disabled)
+                        continue;
+
+                switch (v->base) {
+
+                case TIMER_ACTIVE:
+                        if (state_translation_table[t->state] == UNIT_ACTIVE) {
+                                base = t->meta.inactive_exit_timestamp.monotonic;
+                        } else
+                                base = n;
+                        break;
+
+                case TIMER_BOOT:
+                        /* CLOCK_MONOTONIC equals the uptime on Linux */
+                        base = 0;
+                        break;
+
+                case TIMER_STARTUP:
+                        base = t->meta.manager->startup_timestamp.monotonic;
+                        break;
+
+                case TIMER_UNIT_ACTIVE:
+
+                        if (t->unit->meta.inactive_exit_timestamp.monotonic <= 0)
+                                continue;
+
+                        base = t->unit->meta.inactive_exit_timestamp.monotonic;
+                        break;
+
+                case TIMER_UNIT_INACTIVE:
+
+                        if (t->unit->meta.inactive_enter_timestamp.monotonic <= 0)
+                                continue;
+
+                        base = t->unit->meta.inactive_enter_timestamp.monotonic;
+                        break;
+
+                default:
+                        assert_not_reached("Unknown timer base");
+                }
+
+                v->next_elapse = base + v->value;
+
+                if (!initial && v->next_elapse < n) {
+                        v->disabled = true;
+                        continue;
+                }
+
+                if (!found)
+                        t->next_elapse = v->next_elapse;
+                else
+                        t->next_elapse = MIN(t->next_elapse, v->next_elapse);
+
+                found = true;
+        }
+
+        if (!found) {
+                timer_set_state(t, TIMER_ELAPSED);
+                return;
+        }
+
+        delay = n < t->next_elapse ? t->next_elapse - n : 0;
+
+        if ((r = unit_watch_timer(UNIT(t), delay, &t->timer_watch)) < 0)
+                goto fail;
+
+        timer_set_state(t, TIMER_WAITING);
+        return;
+
+fail:
+        log_warning("%s failed to enter waiting state: %s", t->meta.id, strerror(-r));
+        timer_enter_dead(t, false);
+}
+
+static void timer_enter_running(Timer *t) {
+        int r;
+        assert(t);
+
+        if ((r = manager_add_job(UNIT(t)->meta.manager, JOB_START, t->unit, JOB_REPLACE, true, NULL)) < 0)
+                goto fail;
+
+        timer_set_state(t, TIMER_RUNNING);
+        return;
+
+fail:
+        log_warning("%s failed to queue unit startup job: %s", t->meta.id, strerror(-r));
+        timer_enter_dead(t, false);
+}
+
+static int timer_start(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(t->state == TIMER_DEAD);
+
+        timer_enter_waiting(t, true);
+        return 0;
+}
+
+static int timer_stop(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED);
+
+        timer_enter_dead(t, true);
+        return 0;
+}
+
+static int timer_serialize(Unit *u, FILE *f, FDSet *fds) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+        assert(f);
+        assert(fds);
+
+        unit_serialize_item(u, f, "state", timer_state_to_string(t->state));
+
+        return 0;
+}
+
+static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+        assert(key);
+        assert(value);
+        assert(fds);
+
+        if (streq(key, "state")) {
+                TimerState state;
+
+                if ((state = timer_state_from_string(value)) < 0)
+                        log_debug("Failed to parse state value %s", value);
+                else
+                        t->deserialized_state = state;
+        } else
+                log_debug("Unknown serialization key '%s'", key);
+
+        return 0;
 }
 
 static UnitActiveState timer_active_state(Unit *u) {
+        assert(u);
+
+        return state_translation_table[TIMER(u)->state];
+}
+
+static const char *timer_sub_state_to_string(Unit *u) {
+        assert(u);
+
+        return timer_state_to_string(TIMER(u)->state);
+}
+
+static void timer_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(elapsed == 1);
 
-        static const UnitActiveState table[_TIMER_STATE_MAX] = {
-                [TIMER_DEAD] = UNIT_INACTIVE,
-                [TIMER_WAITING] = UNIT_ACTIVE,
-                [TIMER_RUNNING] = UNIT_ACTIVE
-        };
+        if (t->state != TIMER_WAITING)
+                return;
 
-        return table[TIMER(u)->state];
+        log_debug("Timer elapsed on %s", u->meta.id);
+        timer_enter_running(t);
 }
 
+void timer_unit_notify(Unit *u, UnitActiveState new_state) {
+        char *n;
+        int r;
+        Iterator i;
+
+        if (u->meta.type == UNIT_TIMER)
+                return;
+
+        SET_FOREACH(n, u->meta.names, i) {
+                char *k;
+                Unit *p;
+                Timer *t;
+                TimerValue *v;
+
+                if (!(k = unit_name_change_suffix(n, ".timer"))) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                p = manager_get_unit(u->meta.manager, k);
+                free(k);
+
+                if (!p)
+                        continue;
+
+                t = TIMER(p);
+
+                if (t->meta.load_state != UNIT_LOADED)
+                        continue;
+
+                /* Reenable all timers that depend on unit state */
+                LIST_FOREACH(value, v, t->values)
+                        if (v->base == TIMER_UNIT_ACTIVE ||
+                            v->base == TIMER_UNIT_INACTIVE)
+                                v->disabled = false;
+
+                switch (t->state) {
+
+                case TIMER_WAITING:
+                case TIMER_ELAPSED:
+
+                        /* Recalculate sleep time */
+                        timer_enter_waiting(t, false);
+                        break;
+
+                case TIMER_RUNNING:
+
+                        if (new_state == UNIT_INACTIVE) {
+                                log_debug("%s got notified about unit deactivation.", t->meta.id);
+                                timer_enter_waiting(t, false);
+                        }
+
+                        break;
+
+                case TIMER_DEAD:
+                case TIMER_MAINTAINANCE:
+                        ;
+
+                default:
+                        assert_not_reached("Unknown timer state");
+                }
+        }
+
+        return;
+
+fail:
+        log_error("Failed find timer unit: %s", strerror(-r));
+}
+
+static const char* const timer_state_table[_TIMER_STATE_MAX] = {
+        [TIMER_DEAD] = "dead",
+        [TIMER_WAITING] = "waiting",
+        [TIMER_RUNNING] = "running",
+        [TIMER_ELAPSED] = "elapsed",
+        [TIMER_MAINTAINANCE] = "maintainance"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState);
+
+static const char* const timer_base_table[_TIMER_BASE_MAX] = {
+        [TIMER_ACTIVE] = "OnActive",
+        [TIMER_BOOT] = "OnBoot",
+        [TIMER_STARTUP] = "OnStartup",
+        [TIMER_UNIT_ACTIVE] = "OnUnitActive",
+        [TIMER_UNIT_INACTIVE] = "OnUnitInactive"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase);
+
 const UnitVTable timer_vtable = {
         .suffix = ".timer",
 
-        .load = unit_load_fragment_and_dropin,
+        .init = timer_init,
         .done = timer_done,
+        .load = timer_load,
+
+        .coldplug = timer_coldplug,
+
+        .dump = timer_dump,
+
+        .start = timer_start,
+        .stop = timer_stop,
+
+        .serialize = timer_serialize,
+        .deserialize_item = timer_deserialize_item,
+
+        .active_state = timer_active_state,
+        .sub_state_to_string = timer_sub_state_to_string,
+
+        .timer_event = timer_timer_event,
 
-        .active_state = timer_active_state
+        .bus_message_handler = bus_timer_message_handler
 };
index 0ec3e0d9bbd071663f00e31f274aeac0934b6c33..69c5609a9c5631fc05e987c5ea0c8ebdc6acc724 100644 (file)
@@ -30,20 +30,57 @@ typedef enum TimerState {
         TIMER_DEAD,
         TIMER_WAITING,
         TIMER_RUNNING,
-        _TIMER_STATE_MAX
+        TIMER_ELAPSED,
+        TIMER_MAINTAINANCE,
+        _TIMER_STATE_MAX,
+        _TIMER_STATE_INVALID = -1
 } TimerState;
 
+typedef enum TimerBase {
+        TIMER_ACTIVE,
+        TIMER_BOOT,
+        TIMER_STARTUP,
+        TIMER_UNIT_ACTIVE,
+        TIMER_UNIT_INACTIVE,
+        _TIMER_BASE_MAX,
+        _TIMER_BASE_INVALID = -1
+} TimerBase;
+
+typedef struct TimerValue {
+        TimerBase base;
+        usec_t value;
+
+        usec_t next_elapse;
+
+        bool disabled;
+
+        LIST_FIELDS(struct TimerValue, value);
+} TimerValue;
+
 struct Timer {
         Meta meta;
 
-        TimerState state;
+        LIST_HEAD(TimerValue, values);
+
+        TimerState state, deserialized_state;
 
-        clockid_t clock_id;
         usec_t next_elapse;
 
-        Service *service;
+        Unit *unit;
+
+        Watch timer_watch;
+
+        bool failure;
 };
 
+void timer_unit_notify(Unit *u, UnitActiveState new_state);
+
 extern const UnitVTable timer_vtable;
 
+const char *timer_state_to_string(TimerState i);
+TimerState timer_state_from_string(const char *s);
+
+const char *timer_base_to_string(TimerBase i);
+TimerBase timer_base_from_string(const char *s);
+
 #endif
index b38be317eef95468feb3023cf1059f6cfaac835b..2af2685e0d8be8eeac67737bd37928d1b9b3241a 100644 (file)
@@ -600,10 +600,10 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, strna(u->meta.instance),
                 prefix, unit_load_state_to_string(u->meta.load_state),
                 prefix, unit_active_state_to_string(unit_active_state(u)),
-                prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.inactive_exit_timestamp)),
-                prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_enter_timestamp)),
-                prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->meta.active_exit_timestamp)),
-                prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->meta.inactive_enter_timestamp)),
+                prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.inactive_exit_timestamp.realtime)),
+                prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_enter_timestamp.realtime)),
+                prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->meta.active_exit_timestamp.realtime)),
+                prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->meta.inactive_enter_timestamp.realtime)),
                 prefix, yes_no(unit_check_gc(u)),
                 prefix, yes_no(u->meta.only_by_dependency));
 
@@ -930,7 +930,7 @@ static void retroactively_stop_dependencies(Unit *u) {
 
 void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
         bool unexpected = false;
-        usec_t ts;
+        timestamp ts;
 
         assert(u);
         assert(os < _UNIT_ACTIVE_STATE_MAX);
@@ -943,7 +943,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
          * this function will be called too and the utmp code below
          * relies on that! */
 
-        ts = now(CLOCK_REALTIME);
+        timestamp_get(&ts);
 
         if (os == UNIT_INACTIVE && ns != UNIT_INACTIVE)
                 u->meta.inactive_exit_timestamp = ts;
@@ -955,6 +955,8 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
         else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns))
                 u->meta.active_exit_timestamp = ts;
 
+        timer_unit_notify(u, ns);
+
         if (u->meta.job) {
 
                 if (u->meta.job->state == JOB_WAITING)
index d8be185c5c6049ddc578035f19747455aca09db2..fd0defe2d1666f49165a3c65d6569ed42764cb11 100644 (file)
@@ -152,10 +152,10 @@ struct Meta {
          * the job for it */
         Job *job;
 
-        usec_t inactive_exit_timestamp;
-        usec_t active_enter_timestamp;
-        usec_t active_exit_timestamp;
-        usec_t inactive_enter_timestamp;
+        timestamp inactive_exit_timestamp;
+        timestamp active_enter_timestamp;
+        timestamp active_exit_timestamp;
+        timestamp inactive_enter_timestamp;
 
         /* Counterparts in the cgroup filesystem */
         CGroupBonding *cgroup_bondings;
index 85a8e37d4b559aca10af667fbd6f101c577e99b4..a8ea4a97ab745dbc3ea4400176e10d9bae29f4e9 100644 (file)
@@ -72,6 +72,15 @@ usec_t now(clockid_t clock_id) {
         return timespec_load(&ts);
 }
 
+timestamp* timestamp_get(timestamp *ts) {
+        assert(ts);
+
+        ts->realtime = now(CLOCK_REALTIME);
+        ts->monotonic = now(CLOCK_MONOTONIC);
+
+        return ts;
+}
+
 usec_t timespec_load(const struct timespec *ts) {
         assert(ts);
 
@@ -1398,6 +1407,55 @@ char *format_timestamp(char *buf, size_t l, usec_t t) {
         return buf;
 }
 
+char *format_timespan(char *buf, size_t l, usec_t t) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "w", USEC_PER_WEEK },
+                { "d", USEC_PER_DAY },
+                { "h", USEC_PER_HOUR },
+                { "min", USEC_PER_MINUTE },
+                { "s", USEC_PER_SEC },
+                { "ms", USEC_PER_MSEC },
+                { "us", 1 },
+        };
+
+        unsigned i;
+        char *p = buf;
+
+        assert(buf);
+        assert(l > 0);
+
+        if (t == (usec_t) -1)
+                return NULL;
+
+        /* The result of this function can be parsed with parse_usec */
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                int k;
+                size_t n;
+
+                if (t < table[i].usec)
+                        continue;
+
+                if (l <= 1)
+                        break;
+
+                k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix);
+                n = MIN((size_t) k, l);
+
+                l -= n;
+                p += n;
+
+                t %= table[i].usec;
+        }
+
+        *p = 0;
+
+        return buf;
+}
+
 bool fstype_is_network(const char *fstype) {
         static const char * const table[] = {
                 "cifs",
index ccb976925638bae4a0a6529eb567144b0b43dd23..b411df0cf2fd460bdc55b99b0a6bd01f4703fe79 100644 (file)
 
 typedef uint64_t usec_t;
 
+typedef struct timestamp {
+        usec_t realtime;
+        usec_t monotonic;
+} timestamp;
+
 #define MSEC_PER_SEC  1000ULL
 #define USEC_PER_SEC  1000000ULL
 #define USEC_PER_MSEC 1000ULL
@@ -49,9 +54,12 @@ typedef uint64_t usec_t;
 #define NEWLINE "\n\r"
 
 #define FORMAT_TIMESTAMP_MAX 64
+#define FORMAT_TIMESPAN_MAX 64
 
 usec_t now(clockid_t clock);
 
+timestamp* timestamp_get(timestamp *ts);
+
 usec_t timespec_load(const struct timespec *ts);
 struct timespec *timespec_store(struct timespec *ts, usec_t u);
 
@@ -181,6 +189,7 @@ bool ignore_file(const char *filename);
 bool chars_intersect(const char *a, const char *b);
 
 char *format_timestamp(char *buf, size_t l, usec_t t);
+char *format_timespan(char *buf, size_t l, usec_t t);
 
 int make_stdio(int fd);
 
index cb3f201322edbef648ca68b7bf5cb8933d231711..ba0273f7763aeac786eeec7b31bd3de29158de7d 100644 (file)
@@ -89,7 +89,7 @@ int utmp_get_runlevel(int *runlevel, int *previous) {
         return r;
 }
 
-static void init_entry(struct utmpx *store, usec_t timestamp) {
+static void init_entry(struct utmpx *store, usec_t t) {
         struct utsname uts;
 
         assert(store);
@@ -97,11 +97,11 @@ static void init_entry(struct utmpx *store, usec_t timestamp) {
         zero(*store);
         zero(uts);
 
-        if (timestamp <= 0)
-                timestamp = now(CLOCK_REALTIME);
+        if (t <= 0)
+                t = now(CLOCK_REALTIME);
 
-        store->ut_tv.tv_sec = timestamp / USEC_PER_SEC;
-        store->ut_tv.tv_usec = timestamp % USEC_PER_SEC;
+        store->ut_tv.tv_sec = t / USEC_PER_SEC;
+        store->ut_tv.tv_usec = t % USEC_PER_SEC;
 
         if (uname(&uts) >= 0)
                 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
@@ -162,10 +162,10 @@ static int write_entry_both(const struct utmpx *store) {
         return r;
 }
 
-int utmp_put_shutdown(usec_t timestamp) {
+int utmp_put_shutdown(usec_t t) {
         struct utmpx store;
 
-        init_entry(&store, timestamp);
+        init_entry(&store, t);
 
         store.ut_type = RUN_LVL;
         strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
@@ -173,10 +173,10 @@ int utmp_put_shutdown(usec_t timestamp) {
         return write_entry_both(&store);
 }
 
-int utmp_put_reboot(usec_t timestamp) {
+int utmp_put_reboot(usec_t t) {
         struct utmpx store;
 
-        init_entry(&store, timestamp);
+        init_entry(&store, t);
 
         store.ut_type = BOOT_TIME;
         strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
@@ -184,7 +184,7 @@ int utmp_put_reboot(usec_t timestamp) {
         return write_entry_both(&store);
 }
 
-int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous) {
+int utmp_put_runlevel(usec_t t, int runlevel, int previous) {
         struct utmpx store;
         int r;
 
@@ -204,7 +204,7 @@ int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous) {
                         return 0;
         }
 
-        init_entry(&store, timestamp);
+        init_entry(&store, t);
 
         store.ut_type = RUN_LVL;
         store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);