diff options
-rw-r--r-- | util-linux/hwclock.c | 128 |
1 files changed, 75 insertions, 53 deletions
diff --git a/util-linux/hwclock.c b/util-linux/hwclock.c index 791525fc2..44cb4794e 100644 --- a/util-linux/hwclock.c +++ b/util-linux/hwclock.c @@ -36,6 +36,19 @@ #include <sys/utsname.h> #include "rtc_.h" + +//musl has no __MUSL__ or similar define to check for, +//but its <sys/types.h> has these lines: +// #define __NEED_fsblkcnt_t +// #define __NEED_fsfilcnt_t +#if defined(__linux__) && defined(__NEED_fsblkcnt_t) && defined(__NEED_fsfilcnt_t) +# define LIBC_IS_MUSL 1 +# include <sys/syscall.h> +#else +# define LIBC_IS_MUSL 0 +#endif + + /* diff code is disabled: it's not sys/hw clock diff, it's some useless * "time between hwclock was started and we saw CMOS tick" quantity. * It's useless since hwclock is started at a random moment, @@ -116,26 +129,73 @@ static void show_clock(const char **pp_rtcname, int utc) #endif } -static void to_sys_clock(const char **pp_rtcname, int utc) +static void set_kernel_tz(const struct timezone *tz) { - struct timeval tv; - struct timezone tz; - - tz.tz_minuteswest = timezone / 60; - /* ^^^ used to also subtract 60*daylight, but it's wrong: - * daylight!=0 means "this timezone has some DST - * during the year", not "DST is in effect now". +#if LIBC_IS_MUSL + /* musl libc does not pass tz argument to syscall + * because "it's deprecated by POSIX, therefore it's fine + * if we gratuitously break stuff" :( */ - tz.tz_dsttime = 0; - - /* glibc v2.31+ returns an error if both args are non-NULL */ - if (settimeofday(NULL, &tz)) +#if !defined(SYS_settimeofday) && defined(SYS_settimeofday_time32) +# define SYS_settimeofday SYS_settimeofday_time32 +#endif + int ret = syscall(SYS_settimeofday, NULL, tz); +#else + int ret = settimeofday(NULL, tz); +#endif + if (ret) bb_simple_perror_msg_and_die("settimeofday"); +} + +/* + * At system boot, kernel may set system time from RTC, + * but it knows nothing about timezones. If RTC is in local time, + * then system time is wrong - it is offset by timezone. + * --systz option corrects system time if RTC is in local time, + * and (always) sets in-kernel timezone. + * + * This is an alternate option to --hctosys that does not read the + * hardware clock. + * + * util-linux's code has this comment: + * RTC | settimeofday calls + * ------|------------------------------------------------- + * Local | 1) warps system time*, sets PCIL* and kernel tz + * UTC | 1st) locks warp_clock 2nd) sets kernel tz + * * only on first call after boot + * (PCIL is "persistent_clock_is_local" kernel internal flag, + * it makes kernel save RTC in local time, not UTC.) + */ +static void set_kernel_timezone_and_clock(int utc, const struct timeval *hctosys) +{ + time_t cur; + struct tm *broken; + struct timezone tz = { 0 }; + + /* if --utc, prevent kernel's warp_clock() with a dummy call */ + if (utc) + set_kernel_tz(&tz); + + /* Set kernel's timezone offset based on userspace one */ + cur = time(NULL); + broken = localtime(&cur); + tz.tz_minuteswest = -broken->tm_gmtoff / 60; + /*tz.tz_dsttime = 0; already is */ + set_kernel_tz(&tz); /* MIGHT warp_clock() if 1st call since boot */ + + if (hctosys) { /* it's --hctosys: set time too */ + if (settimeofday(hctosys, NULL)) + bb_simple_perror_msg_and_die("settimeofday"); + } +} + +static void to_sys_clock(const char **pp_rtcname, int utc) +{ + struct timeval tv; tv.tv_sec = read_rtc(pp_rtcname, NULL, utc); tv.tv_usec = 0; - if (settimeofday(&tv, NULL)) - bb_simple_perror_msg_and_die("settimeofday"); + return set_kernel_timezone_and_clock(utc, &tv); } static void from_sys_clock(const char **pp_rtcname, int utc) @@ -261,39 +321,6 @@ static void from_sys_clock(const char **pp_rtcname, int utc) close(rtc); } -/* - * At system boot, kernel may set system time from RTC, - * but it knows nothing about timezones. If RTC is in local time, - * then system time is wrong - it is offset by timezone. - * This option corrects system time if RTC is in local time, - * and (always) sets in-kernel timezone. - * - * This is an alternate option to --hctosys that does not read the - * hardware clock. - */ -static void set_system_clock_timezone(int utc) -{ - struct timeval tv; - struct tm *broken; - struct timezone tz; - - gettimeofday(&tv, NULL); - broken = localtime(&tv.tv_sec); - tz.tz_minuteswest = timezone / 60; - if (broken->tm_isdst > 0) - tz.tz_minuteswest -= 60; - tz.tz_dsttime = 0; - gettimeofday(&tv, NULL); - if (!utc) - tv.tv_sec += tz.tz_minuteswest * 60; - - /* glibc v2.31+ returns an error if both args are non-NULL */ - if (settimeofday(NULL, &tz)) - bb_simple_perror_msg_and_die("settimeofday"); - if (settimeofday(&tv, NULL)) - bb_simple_perror_msg_and_die("settimeofday"); -} - //usage:#define hwclock_trivial_usage //usage: IF_LONG_OPTS( //usage: "[-swu] [--systz] [--localtime] [-f FILE]" @@ -333,7 +360,6 @@ int hwclock_main(int argc UNUSED_PARAM, char **argv) const char *rtcname = NULL; unsigned opt; int utc; - #if ENABLE_LONG_OPTS static const char hwclock_longopts[] ALIGN1 = "localtime\0" No_argument "l" /* short opt is non-standard */ @@ -345,10 +371,6 @@ int hwclock_main(int argc UNUSED_PARAM, char **argv) "rtc\0" Required_argument "f" ; #endif - - /* Initialize "timezone" (libc global variable) */ - tzset(); - opt = getopt32long(argv, "^lurswtf:" "\0" "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l", hwclock_longopts, @@ -366,7 +388,7 @@ int hwclock_main(int argc UNUSED_PARAM, char **argv) else if (opt & HWCLOCK_OPT_SYSTOHC) from_sys_clock(&rtcname, utc); else if (opt & HWCLOCK_OPT_SYSTZ) - set_system_clock_timezone(utc); + set_kernel_timezone_and_clock(utc, NULL); else /* default HWCLOCK_OPT_SHOW */ show_clock(&rtcname, utc); |