timedated: sync clock down to RTC where necessary

This commit is contained in:
Lennart Poettering 2011-06-16 21:52:11 +02:00
parent d3fc81bd6a
commit 2076cf8831
5 changed files with 143 additions and 42 deletions

4
TODO
View file

@ -74,6 +74,10 @@ Features:
* support wildcard expansion in EnvironmentFile= and friends * support wildcard expansion in EnvironmentFile= and friends
* add JoinControllers= to system.conf to mount certain cgroup
controllers together in order to guarantee atomic creation/addition
of cgroups
* avoid DefaultStandardOutput=syslog to have any effect on StandardInput=socket services * avoid DefaultStandardOutput=syslog to have any effect on StandardInput=socket services
* fix alsa mixer restore to not print error when no config is stored * fix alsa mixer restore to not print error when no config is stored

View file

@ -1050,11 +1050,14 @@ int main(int argc, char *argv[]) {
if (label_init() < 0) if (label_init() < 0)
goto finish; goto finish;
if (hwclock_is_localtime()) { if (hwclock_is_localtime() > 0) {
int min; int min;
min = hwclock_apply_localtime_delta(); min = hwclock_apply_localtime_delta();
log_info("Hwclock configured in localtime, applying delta of %i minutes to system time", min); if (min < 0)
log_error("Failed to apply local time delta: %s", strerror(-min));
else
log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min);
} }
} else { } else {
arg_running_as = MANAGER_USER; arg_running_as = MANAGER_USER;

View file

@ -50,6 +50,7 @@
" </method>\n" \ " </method>\n" \
" <method name=\"SetLocalRTC\">\n" \ " <method name=\"SetLocalRTC\">\n" \
" <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \ " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
" <arg name=\"correct_system\" type=\"b\" direction=\"in\"/>\n" \
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \ " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
" </method>\n" \ " </method>\n" \
" </interface>\n" \ " </interface>\n" \
@ -151,7 +152,6 @@ static void verify_timezone(void) {
static int read_data(void) { static int read_data(void) {
int r; int r;
FILE *f;
free_data(); free_data();
@ -161,25 +161,7 @@ static int read_data(void) {
verify_timezone(); verify_timezone();
f = fopen("/etc/adjtime", "r"); local_rtc = hwclock_is_localtime() > 0;
if (f) {
char line[LINE_MAX];
bool b;
b = fgets(line, sizeof(line), f) &&
fgets(line, sizeof(line), f) &&
fgets(line, sizeof(line), f);
fclose(f);
if (!b)
return -EIO;
truncate_nl(line);
local_rtc = streq(line, "LOCAL");
} else if (errno != ENOENT)
return -errno;
return 0; return 0;
} }
@ -333,12 +315,26 @@ static DBusHandlerResult timedate_message_handler(
free(zone); free(zone);
zone = t; zone = t;
/* 1. Write new configuration file */
r = write_data_timezone(); r = write_data_timezone();
if (r < 0) { if (r < 0) {
log_error("Failed to set timezone: %s", strerror(-r)); log_error("Failed to set timezone: %s", strerror(-r));
return bus_send_error_reply(connection, message, NULL, r); return bus_send_error_reply(connection, message, NULL, r);
} }
if (local_rtc) {
struct timespec ts;
struct tm *tm;
/* 2. Teach kernel new timezone */
hwclock_apply_localtime_delta();
/* 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));
hwclock_set_time(tm);
}
log_info("Changed timezone to '%s'.", zone); log_info("Changed timezone to '%s'.", zone);
changed = bus_properties_changed_new( changed = bus_properties_changed_new(
@ -351,29 +347,81 @@ static DBusHandlerResult timedate_message_handler(
} else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) { } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
dbus_bool_t lrtc; dbus_bool_t lrtc;
dbus_bool_t correct_system;
dbus_bool_t interactive; dbus_bool_t interactive;
if (!dbus_message_get_args( if (!dbus_message_get_args(
message, message,
&error, &error,
DBUS_TYPE_BOOLEAN, &lrtc, DBUS_TYPE_BOOLEAN, &lrtc,
DBUS_TYPE_BOOLEAN, &correct_system,
DBUS_TYPE_BOOLEAN, &interactive, DBUS_TYPE_BOOLEAN, &interactive,
DBUS_TYPE_INVALID)) DBUS_TYPE_INVALID))
return bus_send_error_reply(connection, message, &error, -EINVAL); return bus_send_error_reply(connection, message, &error, -EINVAL);
if (lrtc != local_rtc) { if (lrtc != local_rtc) {
struct timespec ts;
r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, &error); r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, &error);
if (r < 0) if (r < 0)
return bus_send_error_reply(connection, message, &error, r); return bus_send_error_reply(connection, message, &error, r);
local_rtc = lrtc; local_rtc = lrtc;
/* 1. Write new configuration file */
r = write_data_local_rtc(); r = write_data_local_rtc();
if (r < 0) { if (r < 0) {
log_error("Failed to set RTC to local/UTC: %s", strerror(-r)); log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
return bus_send_error_reply(connection, message, NULL, r); return bus_send_error_reply(connection, message, NULL, r);
} }
/* 2. Teach kernel new timezone */
if (local_rtc)
hwclock_apply_localtime_delta();
else
hwclock_reset_localtime_delta();
/* 3. Synchronize clocks */
assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
if (correct_system) {
struct tm tm;
/* Sync system clock from RTC; first,
* initialize the timezone fields of
* struct tm. */
if (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 (hwclock_get_time(&tm) >= 0) {
/* And set the system clock
* with this */
if (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 (local_rtc)
tm = localtime(&ts.tv_sec);
else
tm = gmtime(&ts.tv_sec);
hwclock_set_time(tm);
}
log_info("Changed local RTC setting to '%s'.", yes_no(local_rtc)); log_info("Changed local RTC setting to '%s'.", yes_no(local_rtc));
changed = bus_properties_changed_new( changed = bus_properties_changed_new(
@ -403,6 +451,7 @@ static DBusHandlerResult timedate_message_handler(
if (!relative || utc != 0) { if (!relative || utc != 0) {
struct timespec ts; struct timespec ts;
struct tm* tm;
r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, &error); r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, &error);
if (r < 0) if (r < 0)
@ -413,11 +462,20 @@ static DBusHandlerResult timedate_message_handler(
else else
timespec_store(&ts, utc); timespec_store(&ts, utc);
/* Set system clock */
if (clock_settime(CLOCK_REALTIME, &ts) < 0) { if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
log_error("Failed to set local time: %m"); log_error("Failed to set local time: %m");
return bus_send_error_reply(connection, message, NULL, -errno); return bus_send_error_reply(connection, message, NULL, -errno);
} }
/* Sync down to RTC */
if (local_rtc)
tm = localtime(&ts.tv_sec);
else
tm = gmtime(&ts.tv_sec);
hwclock_set_time(tm);
log_info("Changed local time to %s", ctime(&ts.tv_sec)); log_info("Changed local time to %s", ctime(&ts.tv_sec));
} }

View file

@ -4785,40 +4785,50 @@ finish:
return r; return r;
} }
bool hwclock_is_localtime(void) { int hwclock_is_localtime(void) {
FILE *f; FILE *f;
char line[LINE_MAX];
bool local = false; bool local = false;
/* /*
* The third line of adjtime is "UTC" or "LOCAL" or nothing. * The third line of adjtime is "UTC" or "LOCAL" or nothing.
* # /etc/adjtime * # /etc/adjtime
* 0.0 0 0.0 * 0.0 0 0
* 0 * 0
* UTC * UTC
*/ */
f = fopen("/etc/adjtime", "re"); f = fopen("/etc/adjtime", "re");
if (f) { if (f) {
if (fgets(line, sizeof(line), f) && char line[LINE_MAX];
bool b;
b = fgets(line, sizeof(line), f) &&
fgets(line, sizeof(line), f) && fgets(line, sizeof(line), f) &&
fgets(line, sizeof(line), f) ) { fgets(line, sizeof(line), f);
if (!strcmp(line, "LOCAL\n"))
local = true;
}
fclose(f); fclose(f);
}
if (!b)
return -EIO;
truncate_nl(line);
local = streq(line, "LOCAL");
} else if (errno != -ENOENT)
return -errno;
return local; return local;
} }
int hwclock_apply_localtime_delta(void) { int hwclock_apply_localtime_delta(void) {
const struct timeval *tv_null = NULL; const struct timeval *tv_null = NULL;
struct timeval tv; struct timespec ts;
struct tm *tm; struct tm *tm;
int minuteswest; int minuteswest;
struct timezone tz; struct timezone tz;
gettimeofday(&tv, NULL); assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
tm = localtime(&tv.tv_sec); assert_se(tm = localtime(&ts.tv_sec));
minuteswest = tm->tm_gmtoff / 60; minuteswest = tm->tm_gmtoff / 60;
tz.tz_minuteswest = -minuteswest; tz.tz_minuteswest = -minuteswest;
@ -4831,20 +4841,43 @@ int hwclock_apply_localtime_delta(void) {
*/ */
if (settimeofday(tv_null, &tz) < 0) if (settimeofday(tv_null, &tz) < 0)
return -errno; return -errno;
else
return minuteswest; return minuteswest;
} }
int hwclock_reset_localtime_delta(void) {
const struct timeval *tv_null = NULL;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0; /* DST_NONE*/
if (settimeofday(tv_null, &tz) < 0)
return -errno;
return 0;
}
int hwclock_get_time(struct tm *tm) { int hwclock_get_time(struct tm *tm) {
int fd; int fd;
int err = 0; int err = 0;
assert(tm);
fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC);
if (fd < 0) if (fd < 0)
return -errno; return -errno;
/* This leaves the timezone fields of struct tm
* uninitialized! */
if (ioctl(fd, RTC_RD_TIME, tm) < 0) if (ioctl(fd, RTC_RD_TIME, tm) < 0)
err = -errno; err = -errno;
close(fd);
/* We don't now daylight saving, so we reset this in order not
* to confused mktime(). */
tm->tm_isdst = -1;
close_nointr_nofail(fd);
return err; return err;
} }
@ -4853,12 +4886,16 @@ int hwclock_set_time(const struct tm *tm) {
int fd; int fd;
int err = 0; int err = 0;
assert(tm);
fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC);
if (fd < 0) if (fd < 0)
return -errno; return -errno;
if (ioctl(fd, RTC_SET_TIME, tm) < 0) if (ioctl(fd, RTC_SET_TIME, tm) < 0)
err = -errno; err = -errno;
close(fd);
close_nointr_nofail(fd);
return err; return err;
} }

View file

@ -431,12 +431,11 @@ int fchmod_umask(int fd, mode_t mode);
int conf_files_list(char ***strv, const char *suffix, const char *dir, ...); int conf_files_list(char ***strv, const char *suffix, const char *dir, ...);
bool hwclock_is_localtime(void); int hwclock_is_localtime(void);
int hwclock_apply_localtime_delta(void); int hwclock_apply_localtime_delta(void);
int hwclock_reset_localtime_delta(void);
int hwclock_get_time(struct tm *tm); int hwclock_get_time(struct tm *tm);
int hwclock_set_time(const struct tm *tm); int hwclock_set_time(const struct tm *tm);
#define NULSTR_FOREACH(i, l) \ #define NULSTR_FOREACH(i, l) \