summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2019-04-02 10:24:35 +0200
committerGitHub <noreply@github.com>2019-04-02 10:24:35 +0200
commitd9550542a8219d04b9fff279e084e0bd3ec9c337 (patch)
treedeee1091f986e22948407277059934cb536582d5
parent509276f2b7d44d472b66e79cbfa531c1de4c3801 (diff)
parentee6bb34d4a30d48c8aabfb07e65af68a86379623 (diff)
downloadsystemd-d9550542a8219d04b9fff279e084e0bd3ec9c337.tar.gz
Merge pull request #12007 from poettering/clock-change
.timer OnClockChange= and OnTimezoneChange= settings
-rw-r--r--TODO5
-rw-r--r--docs/TRANSIENT-SETTINGS.md4
-rw-r--r--man/systemd-run.xml12
-rw-r--r--man/systemd.timer.xml11
-rw-r--r--src/core/dbus-timer.c40
-rw-r--r--src/core/load-fragment-gperf.gperf.m42
-rw-r--r--src/core/timer.c30
-rw-r--r--src/core/timer.h2
-rw-r--r--src/run/run.c46
-rw-r--r--src/shared/bus-unit-util.c3
l---------test/TEST-30-ONCLOCKCHANGE/Makefile1
-rwxr-xr-xtest/TEST-30-ONCLOCKCHANGE/test.sh53
-rwxr-xr-xtest/TEST-30-ONCLOCKCHANGE/testsuite.sh33
13 files changed, 203 insertions, 39 deletions
diff --git a/TODO b/TODO
index ba6b9c034e..010c2ad41d 100644
--- a/TODO
+++ b/TODO
@@ -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
diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md
index ac89fb174f..343df66754 100644
--- a/docs/TRANSIENT-SETTINGS.md
+++ b/docs/TRANSIENT-SETTINGS.md
@@ -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=
diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index 4dd81395cd..8f7a6229cc 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -317,6 +317,18 @@
</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>
<term><option>--timer-property=</option></term>
diff --git a/man/systemd.timer.xml b/man/systemd.timer.xml
index f1afcf6b7e..6a13e52ccf 100644
--- a/man/systemd.timer.xml
+++ b/man/systemd.timer.xml
@@ -260,6 +260,17 @@
</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>
<listitem><para>The unit to activate when this timer elapses.
diff --git a/src/core/dbus-timer.c b/src/core/dbus-timer.c
index b9d2f3d07e..807ca8022a 100644
--- a/src/core/dbus-timer.c
+++ b/src/core/dbus-timer.c
@@ -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);
}
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index ca0cf5894d..a2ed7f2abe 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -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)
diff --git a/src/core/timer.c b/src/core/timer.c
index 0e5214afe8..8440bb27ba 100644
--- a/src/core/timer.c
+++ b/src/core/timer.c
@@ -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] = {
diff --git a/src/core/timer.h b/src/core/timer.h
index 833aadb0b8..ab66a201ad 100644
--- a/src/core/timer.h
+++ b/src/core/timer.h
@@ -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;
};
diff --git a/src/run/run.c b/src/run/run.c
index 0d05fab345..56aa9aaee6 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -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);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 968f91b28c..b27227aeb6 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -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);
diff --git a/test/TEST-30-ONCLOCKCHANGE/Makefile b/test/TEST-30-ONCLOCKCHANGE/Makefile
new file mode 120000
index 0000000000..e9f93b1104
--- /dev/null
+++ b/test/TEST-30-ONCLOCKCHANGE/Makefile
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile \ No newline at end of file
diff --git a/test/TEST-30-ONCLOCKCHANGE/test.sh b/test/TEST-30-ONCLOCKCHANGE/test.sh
new file mode 100755
index 0000000000..25709410b9
--- /dev/null
+++ b/test/TEST-30-ONCLOCKCHANGE/test.sh
@@ -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 "$@"
diff --git a/test/TEST-30-ONCLOCKCHANGE/testsuite.sh b/test/TEST-30-ONCLOCKCHANGE/testsuite.sh
new file mode 100755
index 0000000000..8ded85633a
--- /dev/null
+++ b/test/TEST-30-ONCLOCKCHANGE/testsuite.sh
@@ -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