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