diff options
Diffstat (limited to 'util-linux/hwclock.c')
-rw-r--r-- | util-linux/hwclock.c | 193 |
1 files changed, 119 insertions, 74 deletions
diff --git a/util-linux/hwclock.c b/util-linux/hwclock.c index e85bca2b2..723b09589 100644 --- a/util-linux/hwclock.c +++ b/util-linux/hwclock.c | |||
@@ -36,6 +36,19 @@ | |||
36 | #include <sys/utsname.h> | 36 | #include <sys/utsname.h> |
37 | #include "rtc_.h" | 37 | #include "rtc_.h" |
38 | 38 | ||
39 | |||
40 | //musl has no __MUSL__ or similar define to check for, | ||
41 | //but its <sys/types.h> has these lines: | ||
42 | // #define __NEED_fsblkcnt_t | ||
43 | // #define __NEED_fsfilcnt_t | ||
44 | #if defined(__linux__) && defined(__NEED_fsblkcnt_t) && defined(__NEED_fsfilcnt_t) | ||
45 | # define LIBC_IS_MUSL 1 | ||
46 | # include <sys/syscall.h> | ||
47 | #else | ||
48 | # define LIBC_IS_MUSL 0 | ||
49 | #endif | ||
50 | |||
51 | |||
39 | /* diff code is disabled: it's not sys/hw clock diff, it's some useless | 52 | /* diff code is disabled: it's not sys/hw clock diff, it's some useless |
40 | * "time between hwclock was started and we saw CMOS tick" quantity. | 53 | * "time between hwclock was started and we saw CMOS tick" quantity. |
41 | * It's useless since hwclock is started at a random moment, | 54 | * It's useless since hwclock is started at a random moment, |
@@ -66,7 +79,7 @@ static time_t read_rtc(const char **pp_rtcname, struct timeval *sys_tv, int utc) | |||
66 | int before = tm_time.tm_sec; | 79 | int before = tm_time.tm_sec; |
67 | while (1) { | 80 | while (1) { |
68 | rtc_read_tm(&tm_time, fd); | 81 | rtc_read_tm(&tm_time, fd); |
69 | gettimeofday(sys_tv, NULL); | 82 | xgettimeofday(sys_tv); |
70 | if (before != (int)tm_time.tm_sec) | 83 | if (before != (int)tm_time.tm_sec) |
71 | break; | 84 | break; |
72 | } | 85 | } |
@@ -116,26 +129,72 @@ static void show_clock(const char **pp_rtcname, int utc) | |||
116 | #endif | 129 | #endif |
117 | } | 130 | } |
118 | 131 | ||
119 | static void to_sys_clock(const char **pp_rtcname, int utc) | 132 | static void set_kernel_tz(const struct timezone *tz) |
120 | { | 133 | { |
121 | struct timeval tv; | 134 | #if LIBC_IS_MUSL |
122 | struct timezone tz; | 135 | /* musl libc does not pass tz argument to syscall |
123 | 136 | * because "it's deprecated by POSIX, therefore it's fine | |
124 | tz.tz_minuteswest = timezone / 60; | 137 | * if we gratuitously break stuff" :( |
125 | /* ^^^ used to also subtract 60*daylight, but it's wrong: | ||
126 | * daylight!=0 means "this timezone has some DST | ||
127 | * during the year", not "DST is in effect now". | ||
128 | */ | 138 | */ |
129 | tz.tz_dsttime = 0; | 139 | #if !defined(SYS_settimeofday) && defined(SYS_settimeofday_time32) |
130 | 140 | # define SYS_settimeofday SYS_settimeofday_time32 | |
131 | /* glibc v2.31+ returns an error if both args are non-NULL */ | 141 | #endif |
132 | if (settimeofday(NULL, &tz)) | 142 | int ret = syscall(SYS_settimeofday, NULL, tz); |
143 | #else | ||
144 | int ret = settimeofday(NULL, tz); | ||
145 | #endif | ||
146 | if (ret) | ||
133 | bb_simple_perror_msg_and_die("settimeofday"); | 147 | bb_simple_perror_msg_and_die("settimeofday"); |
148 | } | ||
149 | |||
150 | /* | ||
151 | * At system boot, kernel may set system time from RTC, | ||
152 | * but it knows nothing about timezones. If RTC is in local time, | ||
153 | * then system time is wrong - it is offset by timezone. | ||
154 | * --systz option corrects system time if RTC is in local time, | ||
155 | * and (always) sets in-kernel timezone. | ||
156 | * (Unlike --hctosys, it does not read the RTC). | ||
157 | * | ||
158 | * util-linux's code has this comment: | ||
159 | * RTC | settimeofday calls | ||
160 | * ------|------------------------------------------------- | ||
161 | * Local | 1st) warps system time*, sets PCIL* and kernel tz | ||
162 | * UTC | 1st) locks warp_clock 2nd) sets kernel tz | ||
163 | * * only on first call after boot | ||
164 | * (PCIL is "persistent_clock_is_local" kernel internal flag, | ||
165 | * it makes kernel save RTC in local time, not UTC.) | ||
166 | */ | ||
167 | static void set_kernel_timezone_and_clock(int utc, const struct timeval *hctosys) | ||
168 | { | ||
169 | time_t cur; | ||
170 | struct tm *broken; | ||
171 | struct timezone tz = { 0 }; | ||
172 | |||
173 | /* if --utc, prevent kernel's warp_clock() with a dummy call */ | ||
174 | if (utc) | ||
175 | set_kernel_tz(&tz); | ||
176 | |||
177 | /* Set kernel's timezone offset based on userspace one */ | ||
178 | //It's tempting to call tzset() and use libc global "timezone" variable | ||
179 | //...but it does NOT include DST shift (IOW: it's WRONG, usually by one hour, | ||
180 | //if DST is in effect!) Thus this ridiculous dance: | ||
181 | cur = time(NULL); | ||
182 | broken = localtime(&cur); | ||
183 | tz.tz_minuteswest = -broken->tm_gmtoff / 60; | ||
184 | /*tz.tz_dsttime = 0; already is */ | ||
185 | set_kernel_tz(&tz); /* MIGHT warp_clock() if 1st call since boot */ | ||
186 | |||
187 | if (hctosys) /* it's --hctosys: set time too */ | ||
188 | xsettimeofday(hctosys); | ||
189 | } | ||
190 | |||
191 | static void to_sys_clock(const char **pp_rtcname, int utc) | ||
192 | { | ||
193 | struct timeval tv; | ||
134 | 194 | ||
135 | tv.tv_sec = read_rtc(pp_rtcname, NULL, utc); | 195 | tv.tv_sec = read_rtc(pp_rtcname, NULL, utc); |
136 | tv.tv_usec = 0; | 196 | tv.tv_usec = 0; |
137 | if (settimeofday(&tv, NULL)) | 197 | return set_kernel_timezone_and_clock(utc, &tv); |
138 | bb_simple_perror_msg_and_die("settimeofday"); | ||
139 | } | 198 | } |
140 | 199 | ||
141 | static void from_sys_clock(const char **pp_rtcname, int utc) | 200 | static void from_sys_clock(const char **pp_rtcname, int utc) |
@@ -146,7 +205,7 @@ static void from_sys_clock(const char **pp_rtcname, int utc) | |||
146 | int rtc; | 205 | int rtc; |
147 | 206 | ||
148 | rtc = rtc_xopen(pp_rtcname, O_WRONLY); | 207 | rtc = rtc_xopen(pp_rtcname, O_WRONLY); |
149 | gettimeofday(&tv, NULL); | 208 | xgettimeofday(&tv); |
150 | /* Prepare tm_time */ | 209 | /* Prepare tm_time */ |
151 | if (sizeof(time_t) == sizeof(tv.tv_sec)) { | 210 | if (sizeof(time_t) == sizeof(tv.tv_sec)) { |
152 | if (utc) | 211 | if (utc) |
@@ -194,7 +253,7 @@ static void from_sys_clock(const char **pp_rtcname, int utc) | |||
194 | unsigned rem_usec; | 253 | unsigned rem_usec; |
195 | time_t t; | 254 | time_t t; |
196 | 255 | ||
197 | gettimeofday(&tv, NULL); | 256 | xgettimeofday(&tv); |
198 | 257 | ||
199 | t = tv.tv_sec; | 258 | t = tv.tv_sec; |
200 | rem_usec = 1000000 - tv.tv_usec; | 259 | rem_usec = 1000000 - tv.tv_usec; |
@@ -215,7 +274,7 @@ static void from_sys_clock(const char **pp_rtcname, int utc) | |||
215 | } | 274 | } |
216 | 275 | ||
217 | /* gmtime/localtime took some time, re-get cur time */ | 276 | /* gmtime/localtime took some time, re-get cur time */ |
218 | gettimeofday(&tv, NULL); | 277 | xgettimeofday(&tv); |
219 | 278 | ||
220 | if (tv.tv_sec < t /* we are still in old second */ | 279 | if (tv.tv_sec < t /* we are still in old second */ |
221 | || (tv.tv_sec == t && tv.tv_usec < adj) /* not too far into next second */ | 280 | || (tv.tv_sec == t && tv.tv_usec < adj) /* not too far into next second */ |
@@ -261,64 +320,53 @@ static void from_sys_clock(const char **pp_rtcname, int utc) | |||
261 | close(rtc); | 320 | close(rtc); |
262 | } | 321 | } |
263 | 322 | ||
264 | /* | 323 | // hwclock from util-linux 2.36.1 |
265 | * At system boot, kernel may set system time from RTC, | 324 | // hwclock [function] [option...] |
266 | * but it knows nothing about timezones. If RTC is in local time, | 325 | //Functions: |
267 | * then system time is wrong - it is offset by timezone. | 326 | // -r, --show display the RTC time |
268 | * This option corrects system time if RTC is in local time, | 327 | // --get display drift corrected RTC time |
269 | * and (always) sets in-kernel timezone. | 328 | // --set set the RTC according to --date |
270 | * | 329 | // -s, --hctosys set the system time from the RTC |
271 | * This is an alternate option to --hctosys that does not read the | 330 | // -w, --systohc set the RTC from the system time |
272 | * hardware clock. | 331 | // --systz send timescale configurations to the kernel |
273 | */ | 332 | // -a, --adjust adjust the RTC to account for systematic drift |
274 | static void set_system_clock_timezone(int utc) | 333 | // --predict predict the drifted RTC time according to --date |
275 | { | 334 | //Options: |
276 | struct timeval tv; | 335 | // -u, --utc the RTC timescale is UTC |
277 | struct tm *broken; | 336 | // -l, --localtime the RTC timescale is Local |
278 | struct timezone tz; | 337 | // -f, --rtc <file> use an alternate file to /dev/rtc0 |
279 | 338 | // --directisa use the ISA bus instead of /dev/rtc0 access | |
280 | gettimeofday(&tv, NULL); | 339 | // --date <time> date/time input for --set and --predict |
281 | broken = localtime(&tv.tv_sec); | 340 | // --delay <sec> delay used when set new RTC time |
282 | tz.tz_minuteswest = timezone / 60; | 341 | // --update-drift update the RTC drift factor |
283 | if (broken->tm_isdst > 0) | 342 | // --noadjfile do not use /etc/adjtime |
284 | tz.tz_minuteswest -= 60; | 343 | // --adjfile <file> use an alternate file to /etc/adjtime |
285 | tz.tz_dsttime = 0; | 344 | // --test dry run; implies --verbose |
286 | gettimeofday(&tv, NULL); | 345 | // -v, --verbose display more details |
287 | if (!utc) | ||
288 | tv.tv_sec += tz.tz_minuteswest * 60; | ||
289 | |||
290 | /* glibc v2.31+ returns an error if both args are non-NULL */ | ||
291 | if (settimeofday(NULL, &tz)) | ||
292 | bb_simple_perror_msg_and_die("settimeofday"); | ||
293 | if (settimeofday(&tv, NULL)) | ||
294 | bb_simple_perror_msg_and_die("settimeofday"); | ||
295 | } | ||
296 | 346 | ||
297 | //usage:#define hwclock_trivial_usage | 347 | //usage:#define hwclock_trivial_usage |
298 | //usage: IF_LONG_OPTS( | 348 | //usage: IF_LONG_OPTS( |
299 | //usage: "[-r|--show] [-s|--hctosys] [-w|--systohc] [--systz]" | 349 | //usage: "[-swul] [--systz] [-f DEV]" |
300 | //usage: " [--localtime] [-u|--utc]" | ||
301 | //usage: " [-f|--rtc FILE]" | ||
302 | //usage: ) | 350 | //usage: ) |
303 | //usage: IF_NOT_LONG_OPTS( | 351 | //usage: IF_NOT_LONG_OPTS( |
304 | //usage: "[-r] [-s] [-w] [-t] [-l] [-u] [-f FILE]" | 352 | //usage: "[-swult] [-f DEV]" |
305 | //usage: ) | 353 | //usage: ) |
306 | //usage:#define hwclock_full_usage "\n\n" | 354 | //usage:#define hwclock_full_usage "\n\n" |
307 | //usage: "Query and set hardware clock (RTC)\n" | 355 | //usage: "Show or set hardware clock (RTC)\n" |
308 | //usage: "\n -r Show hardware clock time" | 356 | ///////: "\n -r Show RTC time" |
309 | //usage: "\n -s Set system time from hardware clock" | 357 | ///////-r is default, don't bother showing it in help |
310 | //usage: "\n -w Set hardware clock from system time" | 358 | //usage: "\n -s Set system time from RTC" |
359 | //usage: "\n -w Set RTC from system time" | ||
311 | //usage: IF_LONG_OPTS( | 360 | //usage: IF_LONG_OPTS( |
312 | //usage: "\n --systz Set in-kernel timezone, correct system time" | 361 | //usage: "\n --systz Set in-kernel timezone, correct system time" |
362 | //usage: "\n if RTC is kept in local time" | ||
313 | //usage: ) | 363 | //usage: ) |
314 | //usage: "\n if hardware clock is in local time" | 364 | //usage: "\n -f DEV Use specified device (e.g. /dev/rtc2)" |
315 | //usage: "\n -u Assume hardware clock is kept in UTC" | 365 | //usage: "\n -u Assume RTC is kept in UTC" |
316 | //usage: IF_LONG_OPTS( | 366 | //usage: "\n -l Assume RTC is kept in local time" |
317 | //usage: "\n --localtime Assume hardware clock is kept in local time" | 367 | //usage: "\n (if neither is given, read from "ADJTIME_PATH")" |
318 | //usage: ) | ||
319 | //usage: "\n -f FILE Use specified device (e.g. /dev/rtc2)" | ||
320 | 368 | ||
321 | //TODO: get rid of incompatible -t and -l aliases to --systz and --localtime | 369 | //TODO: get rid of incompatible -t alias to --systz? |
322 | 370 | ||
323 | #define HWCLOCK_OPT_LOCALTIME 0x01 | 371 | #define HWCLOCK_OPT_LOCALTIME 0x01 |
324 | #define HWCLOCK_OPT_UTC 0x02 | 372 | #define HWCLOCK_OPT_UTC 0x02 |
@@ -334,10 +382,9 @@ int hwclock_main(int argc UNUSED_PARAM, char **argv) | |||
334 | const char *rtcname = NULL; | 382 | const char *rtcname = NULL; |
335 | unsigned opt; | 383 | unsigned opt; |
336 | int utc; | 384 | int utc; |
337 | |||
338 | #if ENABLE_LONG_OPTS | 385 | #if ENABLE_LONG_OPTS |
339 | static const char hwclock_longopts[] ALIGN1 = | 386 | static const char hwclock_longopts[] ALIGN1 = |
340 | "localtime\0" No_argument "l" /* short opt is non-standard */ | 387 | "localtime\0" No_argument "l" |
341 | "utc\0" No_argument "u" | 388 | "utc\0" No_argument "u" |
342 | "show\0" No_argument "r" | 389 | "show\0" No_argument "r" |
343 | "hctosys\0" No_argument "s" | 390 | "hctosys\0" No_argument "s" |
@@ -346,17 +393,15 @@ int hwclock_main(int argc UNUSED_PARAM, char **argv) | |||
346 | "rtc\0" Required_argument "f" | 393 | "rtc\0" Required_argument "f" |
347 | ; | 394 | ; |
348 | #endif | 395 | #endif |
349 | |||
350 | /* Initialize "timezone" (libc global variable) */ | ||
351 | tzset(); | ||
352 | |||
353 | opt = getopt32long(argv, | 396 | opt = getopt32long(argv, |
354 | "^lurswtf:" "\0" "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l", | 397 | "^""lurswtf:v" /* -v is accepted and ignored */ |
398 | "\0" | ||
399 | "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l", | ||
355 | hwclock_longopts, | 400 | hwclock_longopts, |
356 | &rtcname | 401 | &rtcname |
357 | ); | 402 | ); |
358 | 403 | ||
359 | /* If -u or -l wasn't given check if we are using utc */ | 404 | /* If -u or -l wasn't given, check if we are using utc */ |
360 | if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME)) | 405 | if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME)) |
361 | utc = (opt & HWCLOCK_OPT_UTC); | 406 | utc = (opt & HWCLOCK_OPT_UTC); |
362 | else | 407 | else |
@@ -367,7 +412,7 @@ int hwclock_main(int argc UNUSED_PARAM, char **argv) | |||
367 | else if (opt & HWCLOCK_OPT_SYSTOHC) | 412 | else if (opt & HWCLOCK_OPT_SYSTOHC) |
368 | from_sys_clock(&rtcname, utc); | 413 | from_sys_clock(&rtcname, utc); |
369 | else if (opt & HWCLOCK_OPT_SYSTZ) | 414 | else if (opt & HWCLOCK_OPT_SYSTZ) |
370 | set_system_clock_timezone(utc); | 415 | set_kernel_timezone_and_clock(utc, NULL); |
371 | else | 416 | else |
372 | /* default HWCLOCK_OPT_SHOW */ | 417 | /* default HWCLOCK_OPT_SHOW */ |
373 | show_clock(&rtcname, utc); | 418 | show_clock(&rtcname, utc); |