Merge pull request #12007 from poettering/clock-change

.timer OnClockChange= and OnTimezoneChange= settings
This commit is contained in:
Lennart Poettering
2019-04-02 10:24:35 +02:00
committed by GitHub
13 changed files with 203 additions and 39 deletions
-5
View File
@@ -144,9 +144,6 @@ Features:
* When reloading configuration PID 1 should reset all its properties to the
original defaults before calling parse_config()
* Add OnTimezoneChange= and OnTimeChange= stanzas to .timer units in order to
schedule events based on time and timezone changes.
* nspawn: greater control over selinux label?
* hibernate/s2h: make this robust and safe to enable in Fedora by default.
@@ -828,9 +825,7 @@ Features:
* timer units:
- timer units should get the ability to trigger when:
o CLOCK_REALTIME makes jumps (TFD_TIMER_CANCEL_ON_SET)
o DST changes
o timezone changes
- Modulate timer frequency based on battery state
* add libsystemd-password or so to query passwords during boot using the password agent logic
+3 -1
View File
@@ -335,10 +335,12 @@ All automount unit setting is available to transient units:
Most timer unit settings are available to transient units.
```
✓ OnCalendar=
✓ OnActiveSec=
✓ OnBootSec=
✓ OnCalendar=
✓ OnClockChange=
✓ OnStartupSec=
✓ OnTimezoneChange
✓ OnUnitActiveSec=
✓ OnUnitInactiveSec=
✓ Persistent=
+12
View File
@@ -316,6 +316,18 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--on-clock-change</option></term>
<term><option>--on-timezone-change</option></term>
<listitem><para>Defines a trigger based on system clock jumps or timezone changes for starting the
specified command. See <varname>OnClockChange=</varname> and <varname>OnTimezoneChange=</varname> in
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. These
options are shortcuts for <command>--timer-property=OnClockChange=yes</command> and
<command>--timer-property=OnTimezoneChange=yes</command>. These options may not be combined with
<option>--scope</option> or <option>--pty</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--path-property=</option></term>
<term><option>--socket-property=</option></term>
+11
View File
@@ -259,6 +259,17 @@
<varname>AccuracySec=1us</varname>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>OnClockChange=</varname></term>
<term><varname>OnTimezoneChange=</varname></term>
<listitem><para>These options take boolean arguments. When true, the service unit will be triggered
when the system clock (<constant>CLOCK_REALTIME</constant>) jumps relative to the monotonic clock
(<constant>CLOCK_MONOTONIC</constant>), or when the local system timezone is modified. These options
can be used alone or in combination with other timer expressions (see above) within the same timer
unit. These options default to false.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>Unit=</varname></term>
+28 -12
View File
@@ -123,6 +123,8 @@ const sd_bus_vtable bus_timer_vtable[] = {
SD_BUS_PROPERTY("Unit", "s", bus_property_get_triggered_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimersMonotonic", "a(stt)", property_get_monotonic_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
SD_BUS_PROPERTY("TimersCalendar", "a(sst)", property_get_calendar_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
SD_BUS_PROPERTY("OnClockChange", "b", bus_property_get_bool, offsetof(Timer, on_clock_change), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnTimezoneChange", "b", bus_property_get_bool, offsetof(Timer, on_timezone_change), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NextElapseUSecRealtime", "t", bus_property_get_usec, offsetof(Timer, next_elapse_realtime), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NextElapseUSecMonotonic", "t", property_get_next_elapse_monotonic, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
BUS_PROPERTY_DUAL_TIMESTAMP("LastTriggerUSec", offsetof(Timer, last_trigger), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -171,6 +173,12 @@ static int bus_timer_set_transient_property(
if (streq(name, "RemainAfterElapse"))
return bus_set_transient_bool(u, name, &t->remain_after_elapse, message, flags, error);
if (streq(name, "OnTimezoneChange"))
return bus_set_transient_bool(u, name, &t->on_timezone_change, message, flags, error);
if (streq(name, "OnClockChange"))
return bus_set_transient_bool(u, name, &t->on_clock_change, message, flags, error);
if (streq(name, "TimersMonotonic")) {
const char *base_name;
usec_t usec = 0;
@@ -194,12 +202,14 @@ static int bus_timer_set_transient_property(
unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", base_name,
format_timespan(ts, sizeof(ts), usec, USEC_PER_MSEC));
v = new0(TimerValue, 1);
v = new(TimerValue, 1);
if (!v)
return -ENOMEM;
v->base = b;
v->value = usec;
*v = (TimerValue) {
.base = b,
.value = usec,
};
LIST_PREPEND(value, t->values, v);
}
@@ -247,12 +257,14 @@ static int bus_timer_set_transient_property(
unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", base_name, str);
v = new0(TimerValue, 1);
v = new(TimerValue, 1);
if (!v)
return -ENOMEM;
v->base = b;
v->calendar_spec = TAKE_PTR(c);
*v = (TimerValue) {
.base = b,
.calendar_spec = TAKE_PTR(c),
};
LIST_PREPEND(value, t->values, v);
}
@@ -300,12 +312,14 @@ static int bus_timer_set_transient_property(
unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", name,
format_timespan(time, sizeof(time), usec, USEC_PER_MSEC));
v = new0(TimerValue, 1);
v = new(TimerValue, 1);
if (!v)
return -ENOMEM;
v->base = b;
v->value = usec;
*v = (TimerValue) {
.base = b,
.value = usec,
};
LIST_PREPEND(value, t->values, v);
}
@@ -333,12 +347,14 @@ static int bus_timer_set_transient_property(
unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", name, str);
v = new0(TimerValue, 1);
v = new(TimerValue, 1);
if (!v)
return -ENOMEM;
v->base = TIMER_CALENDAR;
v->calendar_spec = TAKE_PTR(c);
*v = (TimerValue) {
.base = TIMER_CALENDAR,
.calendar_spec = TAKE_PTR(c),
};
LIST_PREPEND(value, t->values, v);
}
+2
View File
@@ -433,6 +433,8 @@ Timer.OnBootSec, config_parse_timer, TIMER_BOOT,
Timer.OnStartupSec, config_parse_timer, TIMER_STARTUP, 0
Timer.OnUnitActiveSec, config_parse_timer, TIMER_UNIT_ACTIVE, 0
Timer.OnUnitInactiveSec, config_parse_timer, TIMER_UNIT_INACTIVE, 0
Timer.OnClockChange, config_parse_bool, 0, offsetof(Timer, on_clock_change)
Timer.OnTimezoneChange, config_parse_bool, 0, offsetof(Timer, on_timezone_change)
Timer.Persistent, config_parse_bool, 0, offsetof(Timer, persistent)
Timer.WakeSystem, config_parse_bool, 0, offsetof(Timer, wake_system)
Timer.RemainAfterElapse, config_parse_bool, 0, offsetof(Timer, remain_after_elapse)
+22 -8
View File
@@ -77,7 +77,7 @@ static int timer_verify(Timer *t) {
if (UNIT(t)->load_state != UNIT_LOADED)
return 0;
if (!t->values) {
if (!t->values && !t->on_clock_change && !t->on_timezone_change) {
log_unit_error(UNIT(t), "Timer unit lacks value setting. Refusing.");
return -ENOEXEC;
}
@@ -215,14 +215,18 @@ static void timer_dump(Unit *u, FILE *f, const char *prefix) {
"%sPersistent: %s\n"
"%sWakeSystem: %s\n"
"%sAccuracy: %s\n"
"%sRemainAfterElapse: %s\n",
"%sRemainAfterElapse: %s\n"
"%sOnClockChange: %s\n"
"%sOnTimeZoneChange %s\n",
prefix, timer_state_to_string(t->state),
prefix, timer_result_to_string(t->result),
prefix, trigger ? trigger->id : "n/a",
prefix, yes_no(t->persistent),
prefix, yes_no(t->wake_system),
prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1),
prefix, yes_no(t->remain_after_elapse));
prefix, yes_no(t->remain_after_elapse),
prefix, yes_no(t->on_clock_change),
prefix, yes_no(t->on_timezone_change));
LIST_FOREACH(value, v, t->values) {
@@ -474,7 +478,7 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
}
}
if (!found_monotonic && !found_realtime) {
if (!found_monotonic && !found_realtime && !t->on_timezone_change && !t->on_clock_change) {
log_unit_debug(UNIT(t), "Timer is elapsed.");
timer_enter_elapsed(t, leave_around);
return;
@@ -815,8 +819,13 @@ static void timer_time_change(Unit *u) {
if (t->last_trigger.realtime > ts)
t->last_trigger.realtime = ts;
log_unit_debug(u, "Time change, recalculating next elapse.");
timer_enter_waiting(t, true);
if (t->on_clock_change) {
log_unit_debug(u, "Time change, triggering activation.");
timer_enter_running(t);
} else {
log_unit_debug(u, "Time change, recalculating next elapse.");
timer_enter_waiting(t, true);
}
}
static void timer_timezone_change(Unit *u) {
@@ -827,8 +836,13 @@ static void timer_timezone_change(Unit *u) {
if (t->state != TIMER_WAITING)
return;
log_unit_debug(u, "Timezone change, recalculating next elapse.");
timer_enter_waiting(t, false);
if (t->on_timezone_change) {
log_unit_debug(u, "Timezone change, triggering activation.");
timer_enter_running(t);
} else {
log_unit_debug(u, "Timezone change, recalculating next elapse.");
timer_enter_waiting(t, false);
}
}
static const char* const timer_base_table[_TIMER_BASE_MAX] = {
+2
View File
@@ -57,6 +57,8 @@ struct Timer {
bool persistent;
bool wake_system;
bool remain_after_elapse;
bool on_clock_change;
bool on_timezone_change;
char *stamp_path;
};
+34 -12
View File
@@ -60,7 +60,7 @@ static enum {
static char **arg_path_property = NULL;
static char **arg_socket_property = NULL;
static char **arg_timer_property = NULL;
static bool with_timer = false;
static bool arg_with_timer = false;
static bool arg_quiet = false;
static bool arg_aggressive_gc = false;
static char *arg_working_directory = NULL;
@@ -124,6 +124,8 @@ static int help(void) {
" --on-unit-active=SECONDS Run SECONDS after the last activation\n"
" --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n"
" --on-calendar=SPEC Realtime timer\n"
" --on-timezone-change Run when the timezone changes\n"
" --on-clock-change Run when the realtime clock jumps\n"
" --timer-property=NAME=VALUE Set timer unit property\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
@@ -170,6 +172,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ON_UNIT_ACTIVE,
ARG_ON_UNIT_INACTIVE,
ARG_ON_CALENDAR,
ARG_ON_TIMEZONE_CHANGE,
ARG_ON_CLOCK_CHANGE,
ARG_TIMER_PROPERTY,
ARG_PATH_PROPERTY,
ARG_SOCKET_PROPERTY,
@@ -210,6 +214,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE },
{ "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE },
{ "on-calendar", required_argument, NULL, ARG_ON_CALENDAR },
{ "on-timezone-change",no_argument, NULL, ARG_ON_TIMEZONE_CHANGE},
{ "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE },
{ "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY },
{ "path-property", required_argument, NULL, ARG_PATH_PROPERTY },
{ "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY },
@@ -339,7 +345,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
with_timer = true;
arg_with_timer = true;
break;
case ARG_ON_BOOT:
@@ -347,7 +353,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
with_timer = true;
arg_with_timer = true;
break;
case ARG_ON_STARTUP:
@@ -355,7 +361,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
with_timer = true;
arg_with_timer = true;
break;
case ARG_ON_UNIT_ACTIVE:
@@ -363,7 +369,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
with_timer = true;
arg_with_timer = true;
break;
case ARG_ON_UNIT_INACTIVE:
@@ -371,7 +377,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
with_timer = true;
arg_with_timer = true;
break;
case ARG_ON_CALENDAR:
@@ -379,7 +385,23 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
with_timer = true;
arg_with_timer = true;
break;
case ARG_ON_TIMEZONE_CHANGE:
r = add_timer_property("OnTimezoneChange", "yes");
if (r < 0)
return r;
arg_with_timer = true;
break;
case ARG_ON_CLOCK_CHANGE:
r = add_timer_property("OnClockChange", "yes");
if (r < 0)
return r;
arg_with_timer = true;
break;
case ARG_TIMER_PROPERTY:
@@ -387,7 +409,7 @@ static int parse_argv(int argc, char *argv[]) {
if (strv_extend(&arg_timer_property, optarg) < 0)
return log_oom();
with_timer = with_timer ||
arg_with_timer = arg_with_timer ||
STARTSWITH_SET(optarg,
"OnActiveSec=",
"OnBootSec=",
@@ -455,10 +477,10 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached("Unhandled option");
}
with_trigger = !!arg_path_property || !!arg_socket_property || with_timer;
with_trigger = !!arg_path_property || !!arg_socket_property || arg_with_timer;
/* currently, only single trigger (path, socket, timer) unit can be created simultaneously */
if ((int) !!arg_path_property + (int) !!arg_socket_property + (int) with_timer > 1)
if ((int) !!arg_path_property + (int) !!arg_socket_property + (int) arg_with_timer > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Only single trigger (path, socket, timer) unit can be created.");
@@ -554,7 +576,7 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Path, socket or timer options are not supported in --scope mode.");
if (arg_timer_property && !with_timer)
if (arg_timer_property && !arg_with_timer)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--timer-property= has no effect without any other timer options.");
@@ -1646,7 +1668,7 @@ static int run(int argc, char* argv[]) {
r = start_transient_trigger(bus, ".path");
else if (arg_socket_property)
r = start_transient_trigger(bus, ".socket");
else if (with_timer)
else if (arg_with_timer)
r = start_transient_trigger(bus, ".timer");
else
r = start_transient_service(bus, &retval);
+2 -1
View File
@@ -1499,7 +1499,8 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
static int bus_append_timer_property(sd_bus_message *m, const char *field, const char *eq) {
int r;
if (STR_IN_SET(field, "WakeSystem", "RemainAfterElapse", "Persistent"))
if (STR_IN_SET(field, "WakeSystem", "RemainAfterElapse", "Persistent",
"OnTimezoneChange", "OnClockChange"))
return bus_append_parse_boolean(m, field, eq);
+1
View File
@@ -0,0 +1 @@
../TEST-01-BASIC/Makefile
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
set -e
TEST_DESCRIPTION="test OnClockChange= + OnTimezoneChange="
TEST_NO_NSPAWN=1
. $TEST_BASE_DIR/test-functions
test_setup() {
create_empty_image
mkdir -p $TESTDIR/root
mount ${LOOPDEV}p1 $TESTDIR/root
(
LOG_LEVEL=5
eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
inst_any /usr/share/zoneinfo/Europe/Kiev
inst_any /usr/share/zoneinfo/Europe/Berlin
setup_basic_environment
# mask some services that we do not want to run in these tests
ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket
ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service
ln -fs /dev/null $initdir/etc/systemd/system/systemd-machined.service
# setup the testsuite service
cat >$initdir/etc/systemd/system/testsuite.service <<EOF
[Unit]
Description=Testsuite service
[Service]
ExecStart=/testsuite.sh
Type=oneshot
StandardOutput=tty
StandardError=tty
NotifyAccess=all
EOF
cp testsuite.sh $initdir/
setup_testsuite
) || return 1
ddebug "umount $TESTDIR/root"
umount $TESTDIR/root
}
do_test "$@"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
set -ex
set -o pipefail
systemd-analyze set-log-level debug
systemd-analyze set-log-target console
systemctl disable --now systemd-timesyncd.service
timedatectl set-timezone Europe/Berlin
timedatectl set-time 1980-10-15
systemd-run --on-timezone-change touch /tmp/timezone-changed
systemd-run --on-clock-change touch /tmp/clock-changed
! test -f /tmp/timezone-changed
! test -f /tmp/clock-changed
timedatectl set-timezone Europe/Kiev
while ! test -f /tmp/timezone-changed ; do sleep .5 ; done
timedatectl set-time 2018-1-1
while ! test -f /tmp/clock-changed ; do sleep .5 ; done
systemd-analyze set-log-level info
echo OK > /testok
exit 0