Systemd/src/timedate/timedated.c
Zbigniew Jędrzejewski-Szmek 11a1589223 tree-wide: drop license boilerplate
Files which are installed as-is (any .service and other unit files, .conf
files, .policy files, etc), are left as is. My assumption is that SPDX
identifiers are not yet that well known, so it's better to retain the
extended header to avoid any doubt.

I also kept any copyright lines. We can probably remove them, but it'd nice to
obtain explicit acks from all involved authors before doing that.
2018-04-06 18:58:55 +02:00

733 lines
23 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2011 Lennart Poettering
***/
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-event.h"
#include "sd-messages.h"
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-util.h"
#include "clock-util.h"
#include "def.h"
#include "fileio-label.h"
#include "fs-util.h"
#include "path-util.h"
#include "selinux-util.h"
#include "strv.h"
#include "user-util.h"
#include "util.h"
#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = {
SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP),
SD_BUS_ERROR_MAP_END
};
typedef struct Context {
char *zone;
bool local_rtc;
bool can_ntp;
bool use_ntp;
Hashmap *polkit_registry;
} Context;
static void context_free(Context *c) {
assert(c);
free(c->zone);
bus_verify_polkit_async_registry_free(c->polkit_registry);
}
static int context_read_data(Context *c) {
_cleanup_free_ char *t = NULL;
int r;
assert(c);
r = get_timezone(&t);
if (r == -EINVAL)
log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
else if (r < 0)
log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
free_and_replace(c->zone, t);
c->local_rtc = clock_is_localtime(NULL) > 0;
return 0;
}
static int context_write_data_timezone(Context *c) {
_cleanup_free_ char *p = NULL;
int r = 0;
assert(c);
if (isempty(c->zone)) {
if (unlink("/etc/localtime") < 0 && errno != ENOENT)
r = -errno;
return r;
}
p = strappend("../usr/share/zoneinfo/", c->zone);
if (!p)
return log_oom();
r = symlink_atomic(p, "/etc/localtime");
if (r < 0)
return r;
return 0;
}
static int context_write_data_local_rtc(Context *c) {
int r;
_cleanup_free_ char *s = NULL, *w = NULL;
assert(c);
r = read_full_file("/etc/adjtime", &s, NULL);
if (r < 0) {
if (r != -ENOENT)
return r;
if (!c->local_rtc)
return 0;
w = strdup(NULL_ADJTIME_LOCAL);
if (!w)
return -ENOMEM;
} else {
char *p;
const char *e = "\n"; /* default if there is less than 3 lines */
const char *prepend = "";
size_t a, b;
p = strchrnul(s, '\n');
if (*p == '\0')
/* only one line, no \n terminator */
prepend = "\n0\n";
else if (p[1] == '\0') {
/* only one line, with \n terminator */
++p;
prepend = "0\n";
} else {
p = strchr(p+1, '\n');
if (!p) {
/* only two lines, no \n terminator */
prepend = "\n";
p = s + strlen(s);
} else {
char *end;
/* third line might have a \n terminator or not */
p++;
end = strchr(p, '\n');
/* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
if (end)
e = end;
}
}
a = p - s;
b = strlen(e);
w = new(char, a + (c->local_rtc ? 5 : 3) + strlen(prepend) + b + 1);
if (!w)
return -ENOMEM;
*(char*) mempcpy(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
if (streq(w, NULL_ADJTIME_UTC)) {
if (unlink("/etc/adjtime") < 0)
if (errno != ENOENT)
return -errno;
return 0;
}
}
mac_selinux_init();
return write_string_file_atomic_label("/etc/adjtime", w);
}
static int context_read_ntp(Context *c, sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *s;
int r;
assert(c);
assert(bus);
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"GetUnitFileState",
&error,
&reply,
"s",
"systemd-timesyncd.service");
if (r < 0) {
if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
return 0;
return r;
}
r = sd_bus_message_read(reply, "s", &s);
if (r < 0)
return r;
c->can_ntp = true;
c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
return 0;
}
static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
int r;
assert(bus);
assert(error);
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
enabled ? "StartUnit" : "StopUnit",
error,
NULL,
"ss",
"systemd-timesyncd.service",
"replace");
if (r < 0) {
if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
return r;
}
return 0;
}
static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
int r;
assert(bus);
assert(error);
if (enabled)
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"EnableUnitFiles",
error,
NULL,
"asbb", 1,
"systemd-timesyncd.service",
false, true);
else
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"DisableUnitFiles",
error,
NULL,
"asb", 1,
"systemd-timesyncd.service",
false);
if (r < 0) {
if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
return r;
}
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"Reload",
error,
NULL,
NULL);
if (r < 0)
return r;
return 0;
}
static int property_get_rtc_time(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
struct tm tm;
usec_t t;
int r;
zero(tm);
r = clock_get_hwclock(&tm);
if (r == -EBUSY) {
log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
t = 0;
} else if (r == -ENOENT) {
log_debug("/dev/rtc not found.");
t = 0; /* no RTC found */
} else if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
else
t = (usec_t) timegm(&tm) * USEC_PER_SEC;
return sd_bus_message_append(reply, "t", t);
}
static int property_get_time(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
}
static int property_get_ntp_sync(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
return sd_bus_message_append(reply, "b", ntp_synced());
}
static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
Context *c = userdata;
const char *z;
int interactive;
char *t;
int r;
assert(m);
assert(c);
r = sd_bus_message_read(m, "sb", &z, &interactive);
if (r < 0)
return r;
if (!timezone_is_valid(z))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
if (streq_ptr(z, c->zone))
return sd_bus_reply_method_return(m, NULL);
r = bus_verify_polkit_async(
m,
CAP_SYS_TIME,
"org.freedesktop.timedate1.set-timezone",
NULL,
interactive,
UID_INVALID,
&c->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
t = strdup(z);
if (!t)
return -ENOMEM;
free(c->zone);
c->zone = t;
/* 1. Write new configuration file */
r = context_write_data_timezone(c);
if (r < 0) {
log_error_errno(r, "Failed to set time zone: %m");
return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m");
}
/* 2. Tell the kernel our timezone */
clock_set_timezone(NULL);
if (c->local_rtc) {
struct timespec ts;
struct tm *tm;
/* 3. Sync RTC from system clock, with the new delta */
assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
assert_se(tm = localtime(&ts.tv_sec));
clock_set_hwclock(tm);
}
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR,
"TIMEZONE=%s", c->zone,
LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
NULL);
(void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
return sd_bus_reply_method_return(m, NULL);
}
static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) {
int lrtc, fix_system, interactive;
Context *c = userdata;
struct timespec ts;
int r;
assert(m);
assert(c);
r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
if (r < 0)
return r;
if (lrtc == c->local_rtc)
return sd_bus_reply_method_return(m, NULL);
r = bus_verify_polkit_async(
m,
CAP_SYS_TIME,
"org.freedesktop.timedate1.set-local-rtc",
NULL,
interactive,
UID_INVALID,
&c->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1;
c->local_rtc = lrtc;
/* 1. Write new configuration file */
r = context_write_data_local_rtc(c);
if (r < 0) {
log_error_errno(r, "Failed to set RTC to local/UTC: %m");
return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m");
}
/* 2. Tell the kernel our timezone */
clock_set_timezone(NULL);
/* 3. Synchronize clocks */
assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
if (fix_system) {
struct tm tm;
/* Sync system clock from RTC; first,
* initialize the timezone fields of
* struct tm. */
if (c->local_rtc)
tm = *localtime(&ts.tv_sec);
else
tm = *gmtime(&ts.tv_sec);
/* Override the main fields of
* struct tm, but not the timezone
* fields */
if (clock_get_hwclock(&tm) >= 0) {
/* And set the system clock
* with this */
if (c->local_rtc)
ts.tv_sec = mktime(&tm);
else
ts.tv_sec = timegm(&tm);
clock_settime(CLOCK_REALTIME, &ts);
}
} else {
struct tm *tm;
/* Sync RTC from system clock */
if (c->local_rtc)
tm = localtime(&ts.tv_sec);
else
tm = gmtime(&ts.tv_sec);
clock_set_hwclock(tm);
}
log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
(void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
return sd_bus_reply_method_return(m, NULL);
}
static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
int relative, interactive;
Context *c = userdata;
int64_t utc;
struct timespec ts;
usec_t start;
struct tm* tm;
int r;
assert(m);
assert(c);
if (c->use_ntp)
return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
/* this only gets used if dbus does not provide a timestamp */
start = now(CLOCK_MONOTONIC);
r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
if (r < 0)
return r;
if (!relative && utc <= 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
if (relative && utc == 0)
return sd_bus_reply_method_return(m, NULL);
if (relative) {
usec_t n, x;
n = now(CLOCK_REALTIME);
x = n + utc;
if ((utc > 0 && x < n) ||
(utc < 0 && x > n))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
timespec_store(&ts, x);
} else
timespec_store(&ts, (usec_t) utc);
r = bus_verify_polkit_async(
m,
CAP_SYS_TIME,
"org.freedesktop.timedate1.set-time",
NULL,
interactive,
UID_INVALID,
&c->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1;
/* adjust ts for time spent in program */
r = sd_bus_message_get_monotonic_usec(m, &start);
/* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
if (r < 0 && r != -ENODATA)
return r;
timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
/* Set system clock */
if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
log_error_errno(errno, "Failed to set local time: %m");
return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
}
/* Sync down to RTC */
if (c->local_rtc)
tm = localtime(&ts.tv_sec);
else
tm = gmtime(&ts.tv_sec);
clock_set_hwclock(tm);
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR,
"REALTIME="USEC_FMT, timespec_load(&ts),
LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
NULL);
return sd_bus_reply_method_return(m, NULL);
}
static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
int enabled, interactive;
Context *c = userdata;
int r;
assert(m);
assert(c);
r = sd_bus_message_read(m, "bb", &enabled, &interactive);
if (r < 0)
return r;
if ((bool)enabled == c->use_ntp)
return sd_bus_reply_method_return(m, NULL);
r = bus_verify_polkit_async(
m,
CAP_SYS_TIME,
"org.freedesktop.timedate1.set-ntp",
NULL,
interactive,
UID_INVALID,
&c->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1;
r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled);
if (r < 0)
return r;
r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled);
if (r < 0)
return r;
c->use_ntp = enabled;
log_info("Set NTP to %sd", enable_disable(enabled));
(void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
return sd_bus_reply_method_return(m, NULL);
}
static const sd_bus_vtable timedate_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
assert(c);
assert(event);
assert(_bus);
r = sd_bus_default_system(&bus);
if (r < 0)
return log_error_errno(r, "Failed to get system bus connection: %m");
r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
if (r < 0)
return log_error_errno(r, "Failed to register object: %m");
r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.timedate1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
r = sd_bus_attach_event(bus, event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
*_bus = TAKE_PTR(bus);
return 0;
}
int main(int argc, char *argv[]) {
Context context = {};
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
umask(0022);
if (argc != 1) {
log_error("This program takes no arguments.");
r = -EINVAL;
goto finish;
}
r = sd_event_default(&event);
if (r < 0) {
log_error_errno(r, "Failed to allocate event loop: %m");
goto finish;
}
sd_event_set_watchdog(event, true);
r = connect_bus(&context, event, &bus);
if (r < 0)
goto finish;
(void) sd_bus_negotiate_timestamp(bus, true);
r = context_read_data(&context);
if (r < 0) {
log_error_errno(r, "Failed to read time zone data: %m");
goto finish;
}
r = context_read_ntp(&context, bus);
if (r < 0) {
log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
goto finish;
}
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
if (r < 0) {
log_error_errno(r, "Failed to run event loop: %m");
goto finish;
}
finish:
context_free(&context);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}