hwclock: make it report system/rtc clock difference
[platform/upstream/busybox.git] / util-linux / hwclock.c
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 tarball for details.
8 */
9
10 #include "libbb.h"
11 /* After libbb.h, since it needs sys/types.h on some systems */
12 #include <sys/utsname.h>
13 #include "rtc_.h"
14
15 #if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS
16 # ifndef _GNU_SOURCE
17 #  define _GNU_SOURCE
18 # endif
19 #endif
20
21 static const char *rtcname;
22
23 static time_t read_rtc(struct timeval *sys_tv, int utc)
24 {
25         struct tm tm;
26         int fd;
27         int before;
28
29         fd = rtc_xopen(&rtcname, O_RDONLY);
30
31         rtc_read_tm(&tm, fd);
32         before = tm.tm_sec;
33         while (1) {
34                 rtc_read_tm(&tm, fd);
35                 gettimeofday(sys_tv, NULL);
36                 if (before != tm.tm_sec)
37                         break;
38         }
39
40         if (ENABLE_FEATURE_CLEAN_UP)
41                 close(fd);
42
43         return rtc_tm2time(&tm, utc);
44 }
45
46 static void show_clock(int utc)
47 {
48         struct timeval sys_tv;
49         time_t t;
50         long diff;
51         char *cp;
52
53         t = read_rtc(&sys_tv, utc);
54
55         cp = ctime(&t);
56         strchrnul(cp, '\n')[0] = '\0';
57
58         //printf("%s  0.000000 seconds %s\n", cp, utc ? "" : (ptm->tm_isdst ? tzname[1] : tzname[0]));
59         diff = sys_tv.tv_sec - t;
60         if (diff < 0 /*&& tv.tv_usec != 0*/) {
61                 /* Why? */
62                 /* diff >= 0 is ok:   diff < 0, can't just use tv.tv_usec: */
63                 /*   45.520820          43.520820 */
64                 /* - 44.000000        - 45.000000 */
65                 /* =  0.520820        = -1.479180, not -2.520820! */
66                 diff++;
67                 /* should be 1000000 - tv.tv_usec, but then we must check tv.tv_usec != 0 */
68                 sys_tv.tv_usec = 999999 - sys_tv.tv_usec;
69         }
70         printf("%s  %ld.%06lu seconds\n", cp, diff, (unsigned long)sys_tv.tv_usec);
71 }
72
73 static void to_sys_clock(int utc)
74 {
75         struct timeval tv;
76         struct timezone tz;
77
78         tz.tz_minuteswest = timezone/60 - 60*daylight;
79         tz.tz_dsttime = 0;
80
81         tv.tv_sec = read_rtc(NULL, utc);
82         tv.tv_usec = 0;
83         if (settimeofday(&tv, &tz))
84                 bb_perror_msg_and_die("settimeofday() failed");
85 }
86
87 static void from_sys_clock(int utc)
88 {
89 #define TWEAK_USEC 200
90         struct tm tm;
91         struct timeval tv;
92         unsigned adj = TWEAK_USEC;
93         int rtc = rtc_xopen(&rtcname, O_WRONLY);
94
95         /* Try to catch the moment when whole second is close */
96         while (1) {
97                 unsigned rem_usec;
98                 time_t t;
99
100                 gettimeofday(&tv, NULL);
101
102                 t = tv.tv_sec;
103                 rem_usec = 1000000 - tv.tv_usec;
104                 if (rem_usec < 1024) {
105                         /* Less than 1ms to next second. Good enough */
106  small_rem:
107                         t++;
108                 }
109
110                 /* Prepare tm */
111                 if (utc)
112                         gmtime_r(&t, &tm); /* may read /etc/xxx (it takes time) */
113                 else
114                         localtime_r(&t, &tm); /* same */
115                 tm.tm_isdst = 0;
116
117                 /* gmtime/localtime took some time, re-get cur time */
118                 gettimeofday(&tv, NULL);
119
120                 if (tv.tv_sec < t /* may happen if rem_usec was < 1024 */
121                  || (tv.tv_sec == t && tv.tv_usec < 1024)
122                 ) {
123                         /* We are not too far into next second. Good. */
124                         break;
125                 }
126                 adj += 32; /* 2^(10-5) = 2^5 = 32 iterations max */
127                 if (adj >= 1024) {
128                         /* Give up trying to sync */
129                         break;
130                 }
131
132                 /* Try to sync up by sleeping */
133                 rem_usec = 1000000 - tv.tv_usec;
134                 if (rem_usec < 1024) {
135                         goto small_rem; /* already close, don't sleep */
136                 }
137                 /* Need to sleep.
138                  * Note that small adj on slow processors can make us
139                  * to always overshoot tv.tv_usec < 1024 check on next
140                  * iteration. That's why adj is increased on each iteration.
141                  * This also allows it to be reused as a loop limiter.
142                  */
143                 usleep(rem_usec - adj);
144         }
145
146         xioctl(rtc, RTC_SET_TIME, &tm);
147
148         /* Debug aid to find "good" TWEAK_USEC.
149          * Look for a value which makes tv_usec close to 999999 or 0.
150          * for 2.20GHz Intel Core 2: TWEAK_USEC ~= 200
151          */
152         //bb_error_msg("tv.tv_usec:%d adj:%d", (int)tv.tv_usec, adj);
153
154         if (ENABLE_FEATURE_CLEAN_UP)
155                 close(rtc);
156 }
157
158 #define HWCLOCK_OPT_LOCALTIME   0x01
159 #define HWCLOCK_OPT_UTC         0x02
160 #define HWCLOCK_OPT_SHOW        0x04
161 #define HWCLOCK_OPT_HCTOSYS     0x08
162 #define HWCLOCK_OPT_SYSTOHC     0x10
163 #define HWCLOCK_OPT_RTCFILE     0x20
164
165 int hwclock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
166 int hwclock_main(int argc UNUSED_PARAM, char **argv)
167 {
168         unsigned opt;
169         int utc;
170
171 #if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS
172         static const char hwclock_longopts[] ALIGN1 =
173                 "localtime\0" No_argument "l"
174                 "utc\0"       No_argument "u"
175                 "show\0"      No_argument "r"
176                 "hctosys\0"   No_argument "s"
177                 "systohc\0"   No_argument "w"
178                 "file\0"      Required_argument "f"
179                 ;
180         applet_long_options = hwclock_longopts;
181 #endif
182         opt_complementary = "r--ws:w--rs:s--wr:l--u:u--l";
183         opt = getopt32(argv, "lurswf:", &rtcname);
184
185         /* If -u or -l wasn't given check if we are using utc */
186         if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME))
187                 utc = (opt & HWCLOCK_OPT_UTC);
188         else
189                 utc = rtc_adjtime_is_utc();
190
191         if (opt & HWCLOCK_OPT_HCTOSYS)
192                 to_sys_clock(utc);
193         else if (opt & HWCLOCK_OPT_SYSTOHC)
194                 from_sys_clock(utc);
195         else
196                 /* default HWCLOCK_OPT_SHOW */
197                 show_clock(utc);
198
199         return 0;
200 }