diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.c | 7 | ||||
-rw-r--r-- | src/timedated.c | 98 | ||||
-rw-r--r-- | src/util.c | 71 | ||||
-rw-r--r-- | src/util.h | 5 |
4 files changed, 139 insertions, 42 deletions
diff --git a/src/main.c b/src/main.c index 11379f6bb4..045203383d 100644 --- a/src/main.c +++ b/src/main.c @@ -1050,11 +1050,14 @@ int main(int argc, char *argv[]) { if (label_init() < 0) goto finish; - if (hwclock_is_localtime()) { + if (hwclock_is_localtime() > 0) { int min; 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 { arg_running_as = MANAGER_USER; diff --git a/src/timedated.c b/src/timedated.c index 4749648df9..ad7b881daf 100644 --- a/src/timedated.c +++ b/src/timedated.c @@ -50,6 +50,7 @@ " </method>\n" \ " <method name=\"SetLocalRTC\">\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" \ " </method>\n" \ " </interface>\n" \ @@ -151,7 +152,6 @@ static void verify_timezone(void) { static int read_data(void) { int r; - FILE *f; free_data(); @@ -161,25 +161,7 @@ static int read_data(void) { verify_timezone(); - f = fopen("/etc/adjtime", "r"); - 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; + local_rtc = hwclock_is_localtime() > 0; return 0; } @@ -333,12 +315,26 @@ static DBusHandlerResult timedate_message_handler( free(zone); zone = t; + /* 1. Write new configuration file */ r = write_data_timezone(); if (r < 0) { log_error("Failed to set timezone: %s", strerror(-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); 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")) { dbus_bool_t lrtc; + dbus_bool_t correct_system; dbus_bool_t interactive; if (!dbus_message_get_args( message, &error, DBUS_TYPE_BOOLEAN, &lrtc, + DBUS_TYPE_BOOLEAN, &correct_system, DBUS_TYPE_BOOLEAN, &interactive, DBUS_TYPE_INVALID)) return bus_send_error_reply(connection, message, &error, -EINVAL); if (lrtc != local_rtc) { + struct timespec ts; + r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, &error); if (r < 0) return bus_send_error_reply(connection, message, &error, r); local_rtc = lrtc; + /* 1. Write new configuration file */ r = write_data_local_rtc(); if (r < 0) { log_error("Failed to set RTC to local/UTC: %s", strerror(-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)); changed = bus_properties_changed_new( @@ -403,6 +451,7 @@ static DBusHandlerResult timedate_message_handler( if (!relative || utc != 0) { struct timespec ts; + struct tm* tm; r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, &error); if (r < 0) @@ -413,11 +462,20 @@ static DBusHandlerResult timedate_message_handler( else timespec_store(&ts, utc); + /* Set system clock */ if (clock_settime(CLOCK_REALTIME, &ts) < 0) { log_error("Failed to set local time: %m"); 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)); } diff --git a/src/util.c b/src/util.c index dfb153bbb6..2047ebd7bd 100644 --- a/src/util.c +++ b/src/util.c @@ -4785,40 +4785,50 @@ finish: return r; } -bool hwclock_is_localtime(void) { +int hwclock_is_localtime(void) { FILE *f; - char line[LINE_MAX]; bool local = false; /* * The third line of adjtime is "UTC" or "LOCAL" or nothing. * # /etc/adjtime - * 0.0 0 0.0 + * 0.0 0 0 * 0 * UTC */ f = fopen("/etc/adjtime", "re"); if (f) { - if (fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f) ) { - if (!strcmp(line, "LOCAL\n")) - local = true; - } + 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 = streq(line, "LOCAL"); + + } else if (errno != -ENOENT) + return -errno; + return local; } int hwclock_apply_localtime_delta(void) { const struct timeval *tv_null = NULL; - struct timeval tv; + struct timespec ts; struct tm *tm; int minuteswest; struct timezone tz; - gettimeofday(&tv, NULL); - tm = localtime(&tv.tv_sec); + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); minuteswest = tm->tm_gmtoff / 60; tz.tz_minuteswest = -minuteswest; @@ -4831,20 +4841,43 @@ int hwclock_apply_localtime_delta(void) { */ if (settimeofday(tv_null, &tz) < 0) 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 fd; int err = 0; + assert(tm); + fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + + /* This leaves the timezone fields of struct tm + * uninitialized! */ if (ioctl(fd, RTC_RD_TIME, tm) < 0) 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; } @@ -4853,12 +4886,16 @@ int hwclock_set_time(const struct tm *tm) { int fd; int err = 0; + assert(tm); + fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + if (ioctl(fd, RTC_SET_TIME, tm) < 0) err = -errno; - close(fd); + + close_nointr_nofail(fd); return err; } diff --git a/src/util.h b/src/util.h index bd98b654fd..3863a08b63 100644 --- a/src/util.h +++ b/src/util.h @@ -431,12 +431,11 @@ int fchmod_umask(int fd, mode_t mode); 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_reset_localtime_delta(void); int hwclock_get_time(struct tm *tm); - int hwclock_set_time(const struct tm *tm); #define NULSTR_FOREACH(i, l) \ |