getopt32: remove applet_long_options
[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 source tree.
8  */
9 //config:config HWCLOCK
10 //config:       bool "hwclock (5.8 kb)"
11 //config:       default y
12 //config:       select PLATFORM_LINUX
13 //config:       help
14 //config:       The hwclock utility is used to read and set the hardware clock
15 //config:       on a system. This is primarily used to set the current time on
16 //config:       shutdown in the hardware clock, so the hardware will keep the
17 //config:       correct time when Linux is _not_ running.
18 //config:
19 //config:config FEATURE_HWCLOCK_ADJTIME_FHS
20 //config:       bool "Use FHS /var/lib/hwclock/adjtime"
21 //config:       default n  # util-linux-ng in Fedora 13 still uses /etc/adjtime
22 //config:       depends on HWCLOCK
23 //config:       help
24 //config:       Starting with FHS 2.3, the adjtime state file is supposed to exist
25 //config:       at /var/lib/hwclock/adjtime instead of /etc/adjtime. If you wish
26 //config:       to use the FHS behavior, answer Y here, otherwise answer N for the
27 //config:       classic /etc/adjtime path.
28 //config:
29 //config:       pathname.com/fhs/pub/fhs-2.3.html#VARLIBHWCLOCKSTATEDIRECTORYFORHWCLO
30
31 //applet:IF_HWCLOCK(APPLET(hwclock, BB_DIR_SBIN, BB_SUID_DROP))
32
33 //kbuild:lib-$(CONFIG_HWCLOCK) += hwclock.o
34
35 #include "libbb.h"
36 /* After libbb.h, since it needs sys/types.h on some systems */
37 #include <sys/utsname.h>
38 #include "rtc_.h"
39
40 /* diff code is disabled: it's not sys/hw clock diff, it's some useless
41  * "time between hwclock was started and we saw CMOS tick" quantity.
42  * It's useless since hwclock is started at a random moment,
43  * thus the quantity is also random, useless. Showing 0.000000 does not
44  * deprive us from any useful info.
45  *
46  * SHOW_HWCLOCK_DIFF code in this file shows the difference between system
47  * and hw clock. It is useful, but not compatible with standard hwclock.
48  * Thus disabled.
49  */
50 #define SHOW_HWCLOCK_DIFF 0
51
52
53 #if !SHOW_HWCLOCK_DIFF
54 # define read_rtc(pp_rtcname, sys_tv, utc) read_rtc(pp_rtcname, utc)
55 #endif
56 static time_t read_rtc(const char **pp_rtcname, struct timeval *sys_tv, int utc)
57 {
58         struct tm tm_time;
59         int fd;
60
61         fd = rtc_xopen(pp_rtcname, O_RDONLY);
62
63         rtc_read_tm(&tm_time, fd);
64
65 #if SHOW_HWCLOCK_DIFF
66         {
67                 int before = tm_time.tm_sec;
68                 while (1) {
69                         rtc_read_tm(&tm_time, fd);
70                         gettimeofday(sys_tv, NULL);
71                         if (before != (int)tm_time.tm_sec)
72                                 break;
73                 }
74         }
75 #endif
76
77         if (ENABLE_FEATURE_CLEAN_UP)
78                 close(fd);
79
80         return rtc_tm2time(&tm_time, utc);
81 }
82
83 static void show_clock(const char **pp_rtcname, int utc)
84 {
85 #if SHOW_HWCLOCK_DIFF
86         struct timeval sys_tv;
87 #endif
88         time_t t = read_rtc(pp_rtcname, &sys_tv, utc);
89
90 #if ENABLE_LOCALE_SUPPORT
91         /* Standard hwclock uses locale-specific output format */
92         char cp[64];
93         struct tm *ptm = localtime(&t);
94         strftime(cp, sizeof(cp), "%c", ptm);
95 #else
96         char *cp = ctime(&t);
97         chomp(cp);
98 #endif
99
100 #if !SHOW_HWCLOCK_DIFF
101         printf("%s  0.000000 seconds\n", cp);
102 #else
103         {
104                 long diff = sys_tv.tv_sec - t;
105                 if (diff < 0 /*&& tv.tv_usec != 0*/) {
106                         /* Why we need diff++? */
107                         /* diff >= 0 is ok: | diff < 0, can't just use tv.tv_usec: */
108                         /*   45.520820      |   43.520820 */
109                         /* - 44.000000      | - 45.000000 */
110                         /* =  1.520820      | = -1.479180, not -2.520820! */
111                         diff++;
112                         /* Should be 1000000 - tv.tv_usec, but then we must check tv.tv_usec != 0 */
113                         sys_tv.tv_usec = 999999 - sys_tv.tv_usec;
114                 }
115                 printf("%s  %ld.%06lu seconds\n", cp, diff, (unsigned long)sys_tv.tv_usec);
116         }
117 #endif
118 }
119
120 static void to_sys_clock(const char **pp_rtcname, int utc)
121 {
122         struct timeval tv;
123         struct timezone tz;
124
125         tz.tz_minuteswest = timezone/60;
126         /* ^^^ used to also subtract 60*daylight, but it's wrong:
127          * daylight!=0 means "this timezone has some DST
128          * during the year", not "DST is in effect now".
129          */
130         tz.tz_dsttime = 0;
131
132         tv.tv_sec = read_rtc(pp_rtcname, NULL, utc);
133         tv.tv_usec = 0;
134         if (settimeofday(&tv, &tz))
135                 bb_perror_msg_and_die("settimeofday");
136 }
137
138 static void from_sys_clock(const char **pp_rtcname, int utc)
139 {
140 #if 1
141         struct timeval tv;
142         struct tm tm_time;
143         int rtc;
144
145         rtc = rtc_xopen(pp_rtcname, O_WRONLY);
146         gettimeofday(&tv, NULL);
147         /* Prepare tm_time */
148         if (sizeof(time_t) == sizeof(tv.tv_sec)) {
149                 if (utc)
150                         gmtime_r((time_t*)&tv.tv_sec, &tm_time);
151                 else
152                         localtime_r((time_t*)&tv.tv_sec, &tm_time);
153         } else {
154                 time_t t = tv.tv_sec;
155                 if (utc)
156                         gmtime_r(&t, &tm_time);
157                 else
158                         localtime_r(&t, &tm_time);
159         }
160 #else
161 /* Bloated code which tries to set hw clock with better precision.
162  * On x86, even though code does set hw clock within <1ms of exact
163  * whole seconds, apparently hw clock (at least on some machines)
164  * doesn't reset internal fractional seconds to 0,
165  * making all this a pointless exercise.
166  */
167         /* If we see that we are N usec away from whole second,
168          * we'll sleep for N-ADJ usecs. ADJ corrects for the fact
169          * that CPU is not infinitely fast.
170          * On infinitely fast CPU, next wakeup would be
171          * on (exactly_next_whole_second - ADJ). On real CPUs,
172          * this difference between current time and whole second
173          * is less than ADJ (assuming system isn't heavily loaded).
174          */
175         /* Small value of 256us gives very precise sync for 2+ GHz CPUs.
176          * Slower CPUs will fail to sync and will go to bigger
177          * ADJ values. qemu-emulated armv4tl with ~100 MHz
178          * performance ends up using ADJ ~= 4*1024 and it takes
179          * 2+ secs (2 tries with successively larger ADJ)
180          * to sync. Even straced one on the same qemu (very slow)
181          * takes only 4 tries.
182          */
183 #define TWEAK_USEC 256
184         unsigned adj = TWEAK_USEC;
185         struct tm tm_time;
186         struct timeval tv;
187         int rtc = rtc_xopen(pp_rtcname, O_WRONLY);
188
189         /* Try to catch the moment when whole second is close */
190         while (1) {
191                 unsigned rem_usec;
192                 time_t t;
193
194                 gettimeofday(&tv, NULL);
195
196                 t = tv.tv_sec;
197                 rem_usec = 1000000 - tv.tv_usec;
198                 if (rem_usec < adj) {
199                         /* Close enough */
200  small_rem:
201                         t++;
202                 }
203
204                 /* Prepare tm_time from t */
205                 if (utc)
206                         gmtime_r(&t, &tm_time); /* may read /etc/xxx (it takes time) */
207                 else
208                         localtime_r(&t, &tm_time); /* same */
209
210                 if (adj >= 32*1024) {
211                         break; /* 32 ms diff and still no luck?? give up trying to sync */
212                 }
213
214                 /* gmtime/localtime took some time, re-get cur time */
215                 gettimeofday(&tv, NULL);
216
217                 if (tv.tv_sec < t /* we are still in old second */
218                  || (tv.tv_sec == t && tv.tv_usec < adj) /* not too far into next second */
219                 ) {
220                         break; /* good, we are in sync! */
221                 }
222
223                 rem_usec = 1000000 - tv.tv_usec;
224                 if (rem_usec < adj) {
225                         t = tv.tv_sec;
226                         goto small_rem; /* already close to next sec, don't sleep */
227                 }
228
229                 /* Try to sync up by sleeping */
230                 usleep(rem_usec - adj);
231
232                 /* Jump to 1ms diff, then increase fast (x2): EVERY loop
233                  * takes ~1 sec, people won't like slowly converging code here!
234                  */
235         //bb_error_msg("adj:%d tv.tv_usec:%d", adj, (int)tv.tv_usec);
236                 if (adj < 512)
237                         adj = 512;
238                 /* ... and if last "overshoot" does not look insanely big,
239                  * just use it as adj increment. This makes convergence faster.
240                  */
241                 if (tv.tv_usec < adj * 8) {
242                         adj += tv.tv_usec;
243                         continue;
244                 }
245                 adj *= 2;
246         }
247         /* Debug aid to find "optimal" TWEAK_USEC with nearly exact sync.
248          * Look for a value which makes tv_usec close to 999999 or 0.
249          * For 2.20GHz Intel Core 2: optimal TWEAK_USEC ~= 200
250          */
251         //bb_error_msg("tv.tv_usec:%d", (int)tv.tv_usec);
252 #endif
253
254         tm_time.tm_isdst = 0;
255         xioctl(rtc, RTC_SET_TIME, &tm_time);
256
257         if (ENABLE_FEATURE_CLEAN_UP)
258                 close(rtc);
259 }
260
261 /*
262  * At system boot, kernel may set system time from RTC,
263  * but it knows nothing about timezones. If RTC is in local time,
264  * then system time is wrong - it is offset by timezone.
265  * This option corrects system time if RTC is in local time,
266  * and (always) sets in-kernel timezone.
267  *
268  * This is an alternate option to --hctosys that does not read the
269  * hardware clock.
270  */
271 static void set_system_clock_timezone(int utc)
272 {
273         struct timeval tv;
274         struct tm *broken;
275         struct timezone tz;
276
277         gettimeofday(&tv, NULL);
278         broken = localtime(&tv.tv_sec);
279         tz.tz_minuteswest = timezone / 60;
280         if (broken->tm_isdst > 0)
281                 tz.tz_minuteswest -= 60;
282         tz.tz_dsttime = 0;
283         gettimeofday(&tv, NULL);
284         if (!utc)
285                 tv.tv_sec += tz.tz_minuteswest * 60;
286         if (settimeofday(&tv, &tz))
287                 bb_perror_msg_and_die("settimeofday");
288 }
289
290 //usage:#define hwclock_trivial_usage
291 //usage:        IF_LONG_OPTS(
292 //usage:       "[-r|--show] [-s|--hctosys] [-w|--systohc] [--systz]"
293 //usage:       " [--localtime] [-u|--utc]"
294 //usage:       " [-f|--rtc FILE]"
295 //usage:        )
296 //usage:        IF_NOT_LONG_OPTS(
297 //usage:       "[-r] [-s] [-w] [-t] [-l] [-u] [-f FILE]"
298 //usage:        )
299 //usage:#define hwclock_full_usage "\n\n"
300 //usage:       "Query and set hardware clock (RTC)\n"
301 //usage:     "\n        -r      Show hardware clock time"
302 //usage:     "\n        -s      Set system time from hardware clock"
303 //usage:     "\n        -w      Set hardware clock from system time"
304 //usage:        IF_LONG_OPTS(
305 //usage:     "\n        --systz Set in-kernel timezone, correct system time"
306 //usage:        )
307 //usage:     "\n                if hardware clock is in local time"
308 //usage:     "\n        -u      Assume hardware clock is kept in UTC"
309 //usage:        IF_LONG_OPTS(
310 //usage:     "\n        --localtime     Assume hardware clock is kept in local time"
311 //usage:        )
312 //usage:     "\n        -f FILE Use specified device (e.g. /dev/rtc2)"
313
314 //TODO: get rid of incompatible -t and -l aliases to --systz and --localtime
315
316 #define HWCLOCK_OPT_LOCALTIME   0x01
317 #define HWCLOCK_OPT_UTC         0x02
318 #define HWCLOCK_OPT_SHOW        0x04
319 #define HWCLOCK_OPT_HCTOSYS     0x08
320 #define HWCLOCK_OPT_SYSTOHC     0x10
321 #define HWCLOCK_OPT_SYSTZ       0x20
322 #define HWCLOCK_OPT_RTCFILE     0x40
323
324 int hwclock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
325 int hwclock_main(int argc UNUSED_PARAM, char **argv)
326 {
327         const char *rtcname = NULL;
328         unsigned opt;
329         int utc;
330
331 #if ENABLE_LONG_OPTS
332         static const char hwclock_longopts[] ALIGN1 =
333                 "localtime\0" No_argument "l" /* short opt is non-standard */
334                 "utc\0"       No_argument "u"
335                 "show\0"      No_argument "r"
336                 "hctosys\0"   No_argument "s"
337                 "systohc\0"   No_argument "w"
338                 "systz\0"     No_argument "t" /* short opt is non-standard */
339                 "rtc\0"       Required_argument "f"
340                 ;
341 #endif
342
343         /* Initialize "timezone" (libc global variable) */
344         tzset();
345
346         opt_complementary = "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l";
347         opt = getopt32long(argv, "lurswtf:", hwclock_longopts, &rtcname);
348
349         /* If -u or -l wasn't given check if we are using utc */
350         if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME))
351                 utc = (opt & HWCLOCK_OPT_UTC);
352         else
353                 utc = rtc_adjtime_is_utc();
354
355         if (opt & HWCLOCK_OPT_HCTOSYS)
356                 to_sys_clock(&rtcname, utc);
357         else if (opt & HWCLOCK_OPT_SYSTOHC)
358                 from_sys_clock(&rtcname, utc);
359         else if (opt & HWCLOCK_OPT_SYSTZ)
360                 set_system_clock_timezone(utc);
361         else
362                 /* default HWCLOCK_OPT_SHOW */
363                 show_clock(&rtcname, utc);
364
365         return 0;
366 }