mirror of
https://github.com/AuxXxilium/eudev.git
synced 2024-11-23 23:10:57 +07:00
timer: fully implement timer units
This commit is contained in:
parent
4288f61921
commit
871d7de47c
@ -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 \
|
||||
|
2
fixme
2
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()
|
||||
|
@ -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 },
|
||||
|
52
src/dbus-timer.c
Normal file
52
src/dbus-timer.c
Normal file
@ -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);
|
||||
}
|
33
src/dbus-timer.h
Normal file
33
src/dbus-timer.h
Normal file
@ -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
|
@ -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 }
|
||||
};
|
||||
|
||||
|
22
src/logger.c
22
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));
|
||||
|
@ -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) {
|
||||
|
430
src/timer.c
430
src/timer.c
@ -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;
|
||||
|
||||
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);
|
||||
|
||||
static const UnitActiveState table[_TIMER_STATE_MAX] = {
|
||||
[TIMER_DEAD] = UNIT_INACTIVE,
|
||||
[TIMER_WAITING] = UNIT_ACTIVE,
|
||||
[TIMER_RUNNING] = UNIT_ACTIVE
|
||||
};
|
||||
|
||||
return table[TIMER(u)->state];
|
||||
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);
|
||||
|
||||
if (t->state != TIMER_WAITING)
|
||||
return;
|
||||
|
||||
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,
|
||||
|
||||
.active_state = timer_active_state
|
||||
.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,
|
||||
|
||||
.bus_message_handler = bus_timer_message_handler
|
||||
};
|
||||
|
45
src/timer.h
45
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
|
||||
|
14
src/unit.c
14
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)
|
||||
|
@ -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;
|
||||
|
58
src/util.c
58
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",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user