From da1474b1589ae93167112234799faa8c77898e4c Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Sun, 28 Apr 2024 12:24:06 -0500 Subject: [PATCH] Fix hwclock to work with current glibc and musl, which have different bugs. Glibc 2.31 broke the settimeofday() wrapper to return EINVAL if you set the time and kernel timezone in the same call. (I have no idea why.) Musl broke the settimeofday() wrapper to never call the settimeofday syscall at all, which is the only way to set kernel timezone, and then for good measure removed the __NR_settimeofday symbol from their headers because people were still using it. (So use kernel header instead.) (The kernel timezone is used on machines that dual boot with windows, where the hardware clock is generally set to local time to humor windows. Various filesystem code uses it to adjust the time read from and saved to filesystems shared with windows.) --- toys/other/hwclock.c | 81 ++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/toys/other/hwclock.c b/toys/other/hwclock.c index 4087ec93..5186a2d1 100644 --- a/toys/other/hwclock.c +++ b/toys/other/hwclock.c @@ -2,27 +2,35 @@ * * Copyright 2014 Bilal Qureshi * - * No standard, but see Documentation/rtc.txt in the linux kernel source.. + * No standard, but see Documentation/rtc.txt in the linux kernel source. * + * TODO: get/set subsecond time USE_HWCLOCK(NEWTOY(hwclock, ">0(fast)f(rtc):u(utc)l(localtime)t(systz)s(hctosys)r(show)w(systohc)[-ul][!rtsw]", TOYFLAG_SBIN)) config HWCLOCK bool "hwclock" default y help - usage: hwclock [-rswtluf] + usage: hwclock [-rswtlu] [-f FILE] - Get/set the hardware clock. + Get/set the hardware clock. Default is hwclock -ruf /dev/rtc0 - -f FILE Use specified device file instead of /dev/rtc0 (--rtc) + -f Use specified device FILE instead of /dev/rtc0 (--rtc) -l Hardware clock uses localtime (--localtime) -r Show hardware clock time (--show) -s Set system time from hardware clock (--hctosys) - -t Set the system time based on the current timezone (--systz) + -t Inform kernel of non-UTC clock's timezone so it returns UTC (--systz) -u Hardware clock uses UTC (--utc) -w Set hardware clock from system time (--systohc) */ + +// Bug workaround for musl commit 5a105f19b5aa which removed a symbol the +// kernel headers have. (Can't copy it here, varies wildly by architecture.) +#if __has_include() +#include +#endif + #define FOR_hwclock #include "toys.h" #include @@ -31,58 +39,51 @@ GLOBALS( char *f; ) +// Bug workaround for musl commit 2c2c3605d3b3 which rewrote the syscall +// wrapper to not use the syscall, which is the only way to set kernel's sys_tz +#define settimeofday(x, tz) syscall(__NR_settimeofday, (void *)0, (void *)tz) + void hwclock_main() { - struct timezone tzone; - struct timeval timeval; + struct timezone tz = {0}; + struct timespec ts = {0}; struct tm tm; - int fd = -1, utc; + int fd = -1; - if (FLAG(u)) utc = 1; - else if (FLAG(l)) utc = 0; - else utc = !readfile("/etc/adjtime", toybuf, sizeof(toybuf)) || - !!strstr(toybuf, "UTC"); + // -t without -u implies -l + if (FLAG(t)&&!FLAG(u)) toys.optflags |= FLAG_l; + if (FLAG(l)) { + // sets globals timezone and daylight from sys/time.h + // Handle dst adjustment ourselves. (Rebooting during dst transition is + // just conceptually unpleasant, linux uses UTC for a reason.) + tzset(); + tz.tz_minuteswest = timezone/60 - 60*daylight; + } if (!FLAG(t)) { - if (!TT.f) TT.f = "/dev/rtc0"; - fd = xopen(TT.f, O_WRONLY*FLAG(w)); + fd = xopen(TT.f ? : "/dev/rtc0", O_WRONLY*FLAG(w)); - // Get current time in seconds from rtc device. TODO: get subsecond time + // Get current time in seconds from rtc device. if (!FLAG(w)) { xioctl(fd, RTC_RD_TIME, &tm); - timeval.tv_sec = xmktime(&tm, utc); - timeval.tv_usec = 0; // TODO: fixit + ts.tv_sec = xmktime(&tm, !FLAG(l)); } } if (FLAG(w) || FLAG(t)) { - if (gettimeofday(&timeval, 0)) perror_exit("gettimeofday failed"); - if (!(utc ? gmtime_r : localtime_r)(&timeval.tv_sec, &tm)) - error_exit(utc ? "gmtime_r failed" : "localtime_r failed"); - } - - if (FLAG(w)) { - /* The value of tm_isdst is positive if daylight saving time is in effect, - * zero if it is not and negative if the information is not available. - * TODO: so why isn't this negative...? */ - tm.tm_isdst = 0; - xioctl(fd, RTC_SET_TIME, &tm); + if (FLAG(w)) { + if (clock_gettime(CLOCK_REALTIME, &ts)) perror_exit("clock_gettime"); + if (!(FLAG(l) ? localtime_r : gmtime_r)(&ts.tv_sec, &tm)) + error_exit("%s failed", FLAG(l) ? "localtime_r" : "gmtime_r"); + xioctl(fd, RTC_SET_TIME, &tm); + } + if (settimeofday(0, &tz)) perror_exit("settimeofday"); } else if (FLAG(s)) { - tzone.tz_minuteswest = timezone / 60 - 60 * daylight; - } else if (FLAG(t)) { - // Adjust seconds for timezone and daylight saving time - // extern long timezone is defined in header sys/time.h - tzone.tz_minuteswest = timezone / 60; - if (tm.tm_isdst) tzone.tz_minuteswest -= 60; - if (!utc) timeval.tv_sec += tzone.tz_minuteswest * 60; + if (clock_settime(CLOCK_REALTIME, &ts)) perror_exit("clock_settime"); } else { strftime(toybuf, sizeof(toybuf), "%F %T%z", &tm); xputs(toybuf); } - if (FLAG(t) || FLAG(s)) { - tzone.tz_dsttime = 0; - if (settimeofday(&timeval, &tzone)) perror_exit("settimeofday failed"); - } - xclose(fd); + if (CFG_TOYBOX_FREE) xclose(fd); } -- 2.39.2