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