Commit 871d7de4 authored by Lennart Poettering's avatar Lennart Poettering
Browse files

timer: fully implement timer units

parent 4288f619
......@@ -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 \
......
......@@ -66,6 +66,8 @@
* introduce exit.target for session instances
* use _PATH_XXX
Regularly:
* look for close() vs. close_nointr() vs. close_nointr_nofail()
......
......@@ -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 },
......
/*-*- 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);
}
/*-*- 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
......@@ -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
};
......
......@@ -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 }
};
......
......@@ -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));
......
......@@ -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));
......
......@@ -179,7 +179,7 @@ struct Manager {
char **environment;
usec_t boot_timestamp;
timestamp startup_timestamp;
/* Data specific to the device subsystem */
struct udev* udev;
......
......@@ -28,21 +28,21 @@
* <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;
......
......@@ -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) {
......
......@@ -22,30 +22,442 @@
#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;