From 871d7de47c13ee6cd78b8eefdf9128be3c740ac0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 24 May 2010 01:45:54 +0200 Subject: [PATCH] timer: fully implement timer units --- Makefile.am | 2 + fixme | 2 + src/dbus-manager.c | 2 +- src/dbus-timer.c | 52 ++++++ src/dbus-timer.h | 33 ++++ src/dbus.c | 2 + src/load-fragment.c | 75 ++++++++ src/logger.c | 22 +-- src/manager.c | 4 +- src/manager.h | 2 +- src/ratelimit.c | 8 +- src/socket.c | 3 + src/timer.c | 428 +++++++++++++++++++++++++++++++++++++++++++- src/timer.h | 45 ++++- src/unit.c | 14 +- src/unit.h | 8 +- src/util.c | 58 ++++++ src/util.h | 9 + src/utmp-wtmp.c | 22 +-- 19 files changed, 739 insertions(+), 52 deletions(-) create mode 100644 src/dbus-timer.c create mode 100644 src/dbus-timer.h diff --git a/Makefile.am b/Makefile.am index b3e9dfde..b404bbed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 6b6f7f7d..7abf795c 100644 --- 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() diff --git a/src/dbus-manager.c b/src/dbus-manager.c index 6b658d19..6a323019 100644 --- a/src/dbus-manager.c +++ b/src/dbus-manager.c @@ -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 index 00000000..d5729078 --- /dev/null +++ b/src/dbus-timer.c @@ -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 . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-timer.h" +#include "dbus-execute.h" + +#define BUS_TIMER_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_TIMER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\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 index 00000000..250e8186 --- /dev/null +++ b/src/dbus-timer.h @@ -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 . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_timer_message_handler(Unit *u, DBusMessage *message); + +extern const char bus_timer_interface[]; + +#endif diff --git a/src/dbus.c b/src/dbus.c index 5caf1eb1..6dd495d4 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -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 }; diff --git a/src/load-fragment.c b/src/load-fragment.c index 70b69233..889e6211 100644 --- a/src/load-fragment.c +++ b/src/load-fragment.c @@ -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 } }; diff --git a/src/logger.c b/src/logger.c index 2e036dd7..5c7e4ee4 100644 --- a/src/logger.c +++ b/src/logger.c @@ -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)); diff --git a/src/manager.c b/src/manager.c index 5d888759..2a773c6d 100644 --- a/src/manager.c +++ b/src/manager.c @@ -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)); diff --git a/src/manager.h b/src/manager.h index 78923003..9548b0f6 100644 --- a/src/manager.h +++ b/src/manager.h @@ -179,7 +179,7 @@ struct Manager { char **environment; - usec_t boot_timestamp; + timestamp startup_timestamp; /* Data specific to the device subsystem */ struct udev* udev; diff --git a/src/ratelimit.c b/src/ratelimit.c index 1e5ed03c..fc0f71de 100644 --- a/src/ratelimit.c +++ b/src/ratelimit.c @@ -28,21 +28,21 @@ * , 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; diff --git a/src/socket.c b/src/socket.c index 4647d317..ef7674f4 100644 --- a/src/socket.c +++ b/src/socket.c @@ -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) { diff --git a/src/timer.c b/src/timer.c index 41aeb7f3..e95b4d66 100644 --- a/src/timer.c +++ b/src/timer.c @@ -22,30 +22,442 @@ #include #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 }; diff --git a/src/timer.h b/src/timer.h index 0ec3e0d9..69c5609a 100644 --- a/src/timer.h +++ b/src/timer.h @@ -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 diff --git a/src/unit.c b/src/unit.c index b38be317..2af2685e 100644 --- a/src/unit.c +++ b/src/unit.c @@ -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) diff --git a/src/unit.h b/src/unit.h index d8be185c..fd0defe2 100644 --- a/src/unit.h +++ b/src/unit.h @@ -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; diff --git a/src/util.c b/src/util.c index 85a8e37d..a8ea4a97 100644 --- a/src/util.c +++ b/src/util.c @@ -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", diff --git a/src/util.h b/src/util.h index ccb97692..b411df0c 100644 --- a/src/util.h +++ b/src/util.h @@ -32,6 +32,11 @@ 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); diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c index cb3f2013..ba0273f7 100644 --- a/src/utmp-wtmp.c +++ b/src/utmp-wtmp.c @@ -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); -- 2.39.5