busybox/util-linux/hwclock.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini hwclock implementation for busybox
   4 *
   5 * Copyright (C) 2002 Robert Griebl <griebl@gmx.de>
   6 *
   7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   8 */
   9//config:config HWCLOCK
  10//config:       bool "hwclock (5.8 kb)"
  11//config:       default y
  12//config:       help
  13//config:       The hwclock utility is used to read and set the hardware clock
  14//config:       on a system. This is primarily used to set the current time on
  15//config:       shutdown in the hardware clock, so the hardware will keep the
  16//config:       correct time when Linux is _not_ running.
  17//config:
  18//config:config FEATURE_HWCLOCK_ADJTIME_FHS
  19//config:       bool "Use FHS /var/lib/hwclock/adjtime"
  20//config:       default n  # util-linux-ng in Fedora 13 still uses /etc/adjtime
  21//config:       depends on HWCLOCK
  22//config:       help
  23//config:       Starting with FHS 2.3, the adjtime state file is supposed to exist
  24//config:       at /var/lib/hwclock/adjtime instead of /etc/adjtime. If you wish
  25//config:       to use the FHS behavior, answer Y here, otherwise answer N for the
  26//config:       classic /etc/adjtime path.
  27//config:
  28//config:       pathname.com/fhs/pub/fhs-2.3.html#VARLIBHWCLOCKSTATEDIRECTORYFORHWCLO
  29
  30//applet:IF_HWCLOCK(APPLET(hwclock, BB_DIR_SBIN, BB_SUID_DROP))
  31
  32//kbuild:lib-$(CONFIG_HWCLOCK) += hwclock.o
  33
  34#include "libbb.h"
  35/* After libbb.h, since it needs sys/types.h on some systems */
  36#include <sys/utsname.h>
  37#include "rtc_.h"
  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
  52/* diff code is disabled: it's not sys/hw clock diff, it's some useless
  53 * "time between hwclock was started and we saw CMOS tick" quantity.
  54 * It's useless since hwclock is started at a random moment,
  55 * thus the quantity is also random, useless. Showing 0.000000 does not
  56 * deprive us from any useful info.
  57 *
  58 * SHOW_HWCLOCK_DIFF code in this file shows the difference between system
  59 * and hw clock. It is useful, but not compatible with standard hwclock.
  60 * Thus disabled.
  61 */
  62#define SHOW_HWCLOCK_DIFF 0
  63
  64
  65#if !SHOW_HWCLOCK_DIFF
  66# define read_rtc(pp_rtcname, sys_tv, utc) read_rtc(pp_rtcname, utc)
  67#endif
  68static time_t read_rtc(const char **pp_rtcname, struct timeval *sys_tv, int utc)
  69{
  70        struct tm tm_time;
  71        int fd;
  72
  73        fd = rtc_xopen(pp_rtcname, O_RDONLY);
  74
  75        rtc_read_tm(&tm_time, fd);
  76
  77#if SHOW_HWCLOCK_DIFF
  78        {
  79                int before = tm_time.tm_sec;
  80                while (1) {
  81                        rtc_read_tm(&tm_time, fd);
  82                        xgettimeofday(sys_tv);
  83                        if (before != (int)tm_time.tm_sec)
  84                                break;
  85                }
  86        }
  87#endif
  88
  89        if (ENABLE_FEATURE_CLEAN_UP)
  90                close(fd);
  91
  92        return rtc_tm2time(&tm_time, utc);
  93}
  94
  95static void show_clock(const char **pp_rtcname, int utc)
  96{
  97#if SHOW_HWCLOCK_DIFF
  98        struct timeval sys_tv;
  99#endif
 100        time_t t = read_rtc(pp_rtcname, &sys_tv, utc);
 101
 102#if ENABLE_LOCALE_SUPPORT
 103        /* Standard hwclock uses locale-specific output format */
 104        char cp[64];
 105        struct tm *ptm = localtime(&t);
 106        strftime(cp, sizeof(cp), "%c", ptm);
 107#else
 108        char *cp = ctime(&t);
 109        chomp(cp);
 110#endif
 111
 112#if !SHOW_HWCLOCK_DIFF
 113        printf("%s  0.000000 seconds\n", cp);
 114#else
 115        {
 116                long diff = sys_tv.tv_sec - t;
 117                if (diff < 0 /*&& tv.tv_usec != 0*/) {
 118                        /* Why we need diff++? */
 119                        /* diff >= 0 is ok: | diff < 0, can't just use tv.tv_usec: */
 120                        /*   45.520820      |   43.520820 */
 121                        /* - 44.000000      | - 45.000000 */
 122                        /* =  1.520820      | = -1.479180, not -2.520820! */
 123                        diff++;
 124                        /* Should be 1000000 - tv.tv_usec, but then we must check tv.tv_usec != 0 */
 125                        sys_tv.tv_usec = 999999 - sys_tv.tv_usec;
 126                }
 127                printf("%s  %ld.%06lu seconds\n", cp, diff, (unsigned long)sys_tv.tv_usec);
 128        }
 129#endif
 130}
 131
 132static void set_kernel_tz(const struct timezone *tz)
 133{
 134#if LIBC_IS_MUSL
 135        /* musl libc does not pass tz argument to syscall
 136         * because "it's deprecated by POSIX, therefore it's fine
 137         * if we gratuitously break stuff" :(
 138         */
 139#if !defined(SYS_settimeofday) && defined(SYS_settimeofday_time32)
 140# define SYS_settimeofday SYS_settimeofday_time32
 141#endif
 142        int ret = syscall(SYS_settimeofday, NULL, tz);
 143#else
 144        int ret = settimeofday(NULL, tz);
 145#endif
 146        if (ret)
 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 */
 167static 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
 191static void to_sys_clock(const char **pp_rtcname, int utc)
 192{
 193        struct timeval tv;
 194
 195        tv.tv_sec = read_rtc(pp_rtcname, NULL, utc);
 196        tv.tv_usec = 0;
 197        return set_kernel_timezone_and_clock(utc, &tv);
 198}
 199
 200static void from_sys_clock(const char **pp_rtcname, int utc)
 201{
 202#if 1
 203        struct timeval tv;
 204        struct tm tm_time;
 205        int rtc;
 206
 207        rtc = rtc_xopen(pp_rtcname, O_WRONLY);
 208        xgettimeofday(&tv);
 209        /* Prepare tm_time */
 210        if (sizeof(time_t) == sizeof(tv.tv_sec)) {
 211                if (utc)
 212                        gmtime_r((time_t*)&tv.tv_sec, &tm_time);
 213                else
 214                        localtime_r((time_t*)&tv.tv_sec, &tm_time);
 215        } else {
 216                time_t t = tv.tv_sec;
 217                if (utc)
 218                        gmtime_r(&t, &tm_time);
 219                else
 220                        localtime_r(&t, &tm_time);
 221        }
 222#else
 223/* Bloated code which tries to set hw clock with better precision.
 224 * On x86, even though code does set hw clock within <1ms of exact
 225 * whole seconds, apparently hw clock (at least on some machines)
 226 * doesn't reset internal fractional seconds to 0,
 227 * making all this a pointless exercise.
 228 */
 229        /* If we see that we are N usec away from whole second,
 230         * we'll sleep for N-ADJ usecs. ADJ corrects for the fact
 231         * that CPU is not infinitely fast.
 232         * On infinitely fast CPU, next wakeup would be
 233         * on (exactly_next_whole_second - ADJ). On real CPUs,
 234         * this difference between current time and whole second
 235         * is less than ADJ (assuming system isn't heavily loaded).
 236         */
 237        /* Small value of 256us gives very precise sync for 2+ GHz CPUs.
 238         * Slower CPUs will fail to sync and will go to bigger
 239         * ADJ values. qemu-emulated armv4tl with ~100 MHz
 240         * performance ends up using ADJ ~= 4*1024 and it takes
 241         * 2+ secs (2 tries with successively larger ADJ)
 242         * to sync. Even straced one on the same qemu (very slow)
 243         * takes only 4 tries.
 244         */
 245#define TWEAK_USEC 256
 246        unsigned adj = TWEAK_USEC;
 247        struct tm tm_time;
 248        struct timeval tv;
 249        int rtc = rtc_xopen(pp_rtcname, O_WRONLY);
 250
 251        /* Try to catch the moment when whole second is close */
 252        while (1) {
 253                unsigned rem_usec;
 254                time_t t;
 255
 256                xgettimeofday(&tv);
 257
 258                t = tv.tv_sec;
 259                rem_usec = 1000000 - tv.tv_usec;
 260                if (rem_usec < adj) {
 261                        /* Close enough */
 262 small_rem:
 263                        t++;
 264                }
 265
 266                /* Prepare tm_time from t */
 267                if (utc)
 268                        gmtime_r(&t, &tm_time); /* may read /etc/xxx (it takes time) */
 269                else
 270                        localtime_r(&t, &tm_time); /* same */
 271
 272                if (adj >= 32*1024) {
 273                        break; /* 32 ms diff and still no luck?? give up trying to sync */
 274                }
 275
 276                /* gmtime/localtime took some time, re-get cur time */
 277                xgettimeofday(&tv);
 278
 279                if (tv.tv_sec < t /* we are still in old second */
 280                 || (tv.tv_sec == t && tv.tv_usec < adj) /* not too far into next second */
 281                ) {
 282                        break; /* good, we are in sync! */
 283                }
 284
 285                rem_usec = 1000000 - tv.tv_usec;
 286                if (rem_usec < adj) {
 287                        t = tv.tv_sec;
 288                        goto small_rem; /* already close to next sec, don't sleep */
 289                }
 290
 291                /* Try to sync up by sleeping */
 292                usleep(rem_usec - adj);
 293
 294                /* Jump to 1ms diff, then increase fast (x2): EVERY loop
 295                 * takes ~1 sec, people won't like slowly converging code here!
 296                 */
 297        //bb_error_msg("adj:%d tv.tv_usec:%d", adj, (int)tv.tv_usec);
 298                if (adj < 512)
 299                        adj = 512;
 300                /* ... and if last "overshoot" does not look insanely big,
 301                 * just use it as adj increment. This makes convergence faster.
 302                 */
 303                if (tv.tv_usec < adj * 8) {
 304                        adj += tv.tv_usec;
 305                        continue;
 306                }
 307                adj *= 2;
 308        }
 309        /* Debug aid to find "optimal" TWEAK_USEC with nearly exact sync.
 310         * Look for a value which makes tv_usec close to 999999 or 0.
 311         * For 2.20GHz Intel Core 2: optimal TWEAK_USEC ~= 200
 312         */
 313        //bb_error_msg("tv.tv_usec:%d", (int)tv.tv_usec);
 314#endif
 315
 316        tm_time.tm_isdst = 0;
 317        xioctl(rtc, RTC_SET_TIME, &tm_time);
 318
 319        if (ENABLE_FEATURE_CLEAN_UP)
 320                close(rtc);
 321}
 322
 323// hwclock from util-linux 2.36.1
 324// hwclock [function] [option...]
 325//Functions:
 326// -r, --show           display the RTC time
 327//     --get            display drift corrected RTC time
 328//     --set            set the RTC according to --date
 329// -s, --hctosys        set the system time from the RTC
 330// -w, --systohc        set the RTC from the system time
 331//     --systz          send timescale configurations to the kernel
 332// -a, --adjust         adjust the RTC to account for systematic drift
 333//     --predict        predict the drifted RTC time according to --date
 334//Options:
 335// -u, --utc            the RTC timescale is UTC
 336// -l, --localtime      the RTC timescale is Local
 337// -f, --rtc <file>     use an alternate file to /dev/rtc0
 338//     --directisa      use the ISA bus instead of /dev/rtc0 access
 339//     --date <time>    date/time input for --set and --predict
 340//     --delay <sec>    delay used when set new RTC time
 341//     --update-drift   update the RTC drift factor
 342//     --noadjfile      do not use /etc/adjtime
 343//     --adjfile <file> use an alternate file to /etc/adjtime
 344//     --test           dry run; implies --verbose
 345// -v, --verbose        display more details
 346
 347//usage:#define hwclock_trivial_usage
 348//usage:        IF_LONG_OPTS(
 349//usage:       "[-swul] [--systz] [-f DEV]"
 350//usage:        )
 351//usage:        IF_NOT_LONG_OPTS(
 352//usage:       "[-swult] [-f DEV]"
 353//usage:        )
 354//usage:#define hwclock_full_usage "\n\n"
 355//usage:       "Show or set hardware clock (RTC)\n"
 356///////:     "\n        -r      Show RTC time"
 357///////-r is default, don't bother showing it in help
 358//usage:     "\n        -s      Set system time from RTC"
 359//usage:     "\n        -w      Set RTC from system time"
 360//usage:        IF_LONG_OPTS(
 361//usage:     "\n        --systz Set in-kernel timezone, correct system time"
 362//usage:     "\n                if RTC is kept in local time"
 363//usage:        )
 364//usage:     "\n        -f DEV  Use specified device (e.g. /dev/rtc2)"
 365//usage:     "\n        -u      Assume RTC is kept in UTC"
 366//usage:     "\n        -l      Assume RTC is kept in local time"
 367//usage:     "\n                (if neither is given, read from "ADJTIME_PATH")"
 368
 369//TODO: get rid of incompatible -t alias to --systz?
 370
 371#define HWCLOCK_OPT_LOCALTIME   0x01
 372#define HWCLOCK_OPT_UTC         0x02
 373#define HWCLOCK_OPT_SHOW        0x04
 374#define HWCLOCK_OPT_HCTOSYS     0x08
 375#define HWCLOCK_OPT_SYSTOHC     0x10
 376#define HWCLOCK_OPT_SYSTZ       0x20
 377#define HWCLOCK_OPT_RTCFILE     0x40
 378
 379int hwclock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 380int hwclock_main(int argc UNUSED_PARAM, char **argv)
 381{
 382        const char *rtcname = NULL;
 383        unsigned opt;
 384        int utc;
 385#if ENABLE_LONG_OPTS
 386        static const char hwclock_longopts[] ALIGN1 =
 387                "localtime\0" No_argument "l"
 388                "utc\0"       No_argument "u"
 389                "show\0"      No_argument "r"
 390                "hctosys\0"   No_argument "s"
 391                "systohc\0"   No_argument "w"
 392                "systz\0"     No_argument "t" /* short opt is non-standard */
 393                "rtc\0"       Required_argument "f"
 394                ;
 395#endif
 396        opt = getopt32long(argv,
 397                "^""lurswtf:v" /* -v is accepted and ignored */
 398                "\0"
 399                "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l",
 400                hwclock_longopts,
 401                &rtcname
 402        );
 403
 404        /* If -u or -l wasn't given, check if we are using utc */
 405        if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME))
 406                utc = (opt & HWCLOCK_OPT_UTC);
 407        else
 408                utc = rtc_adjtime_is_utc();
 409
 410        if (opt & HWCLOCK_OPT_HCTOSYS)
 411                to_sys_clock(&rtcname, utc);
 412        else if (opt & HWCLOCK_OPT_SYSTOHC)
 413                from_sys_clock(&rtcname, utc);
 414        else if (opt & HWCLOCK_OPT_SYSTZ)
 415                set_kernel_timezone_and_clock(utc, NULL);
 416        else
 417                /* default HWCLOCK_OPT_SHOW */
 418                show_clock(&rtcname, utc);
 419
 420        return 0;
 421}
 422