chiark / gitweb /
doc: disable "make check" for gtk-doc
[elogind.git] / src / shared / hwclock.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010-2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <assert.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <sys/ioctl.h>
33 #include <stdarg.h>
34 #include <ctype.h>
35 #include <sys/prctl.h>
36 #include <sys/time.h>
37 #include <linux/rtc.h>
38
39 #include "macro.h"
40 #include "util.h"
41 #include "log.h"
42 #include "strv.h"
43 #include "hwclock.h"
44
45 static int rtc_open(int flags) {
46         int fd;
47         DIR *d;
48
49         /* First, we try to make use of the /dev/rtc symlink. If that
50          * doesn't exist, we open the first RTC which has hctosys=1
51          * set. If we don't find any we just take the first RTC that
52          * exists at all. */
53
54         fd = open("/dev/rtc", flags);
55         if (fd >= 0)
56                 return fd;
57
58         d = opendir("/sys/class/rtc");
59         if (!d)
60                 goto fallback;
61
62         for (;;) {
63                 char *p, *v;
64                 struct dirent *de;
65                 union dirent_storage buf;
66                 int r;
67
68                 r = readdir_r(d, &buf.de, &de);
69                 if (r != 0)
70                         goto fallback;
71
72                 if (!de)
73                         goto fallback;
74
75                 if (ignore_file(de->d_name))
76                         continue;
77
78                 p = strjoin("/sys/class/rtc/", de->d_name, "/hctosys", NULL);
79                 if (!p) {
80                         closedir(d);
81                         return -ENOMEM;
82                 }
83
84                 r = read_one_line_file(p, &v);
85                 free(p);
86
87                 if (r < 0)
88                         continue;
89
90                 r = parse_boolean(v);
91                 free(v);
92
93                 if (r <= 0)
94                         continue;
95
96                 p = strappend("/dev/", de->d_name);
97                 if (!p) {
98                         closedir(d);
99                         return -ENOMEM;
100                 }
101
102                 fd = open(p, flags);
103                 free(p);
104
105                 if (fd >= 0) {
106                         closedir(d);
107                         return fd;
108                 }
109         }
110
111 fallback:
112         if (d)
113                 closedir(d);
114
115         fd = open("/dev/rtc0", flags);
116         if (fd < 0)
117                 return -errno;
118
119         return fd;
120 }
121
122 int hwclock_get_time(struct tm *tm) {
123         int fd;
124         int err = 0;
125
126         assert(tm);
127
128         fd = rtc_open(O_RDONLY|O_CLOEXEC);
129         if (fd < 0)
130                 return -errno;
131
132         /* This leaves the timezone fields of struct tm
133          * uninitialized! */
134         if (ioctl(fd, RTC_RD_TIME, tm) < 0)
135                 err = -errno;
136
137         /* We don't know daylight saving, so we reset this in order not
138          * to confused mktime(). */
139         tm->tm_isdst = -1;
140
141         close_nointr_nofail(fd);
142
143         return err;
144 }
145
146 int hwclock_set_time(const struct tm *tm) {
147         int fd;
148         int err = 0;
149
150         assert(tm);
151
152         fd = rtc_open(O_RDONLY|O_CLOEXEC);
153         if (fd < 0)
154                 return -errno;
155
156         if (ioctl(fd, RTC_SET_TIME, tm) < 0)
157                 err = -errno;
158
159         close_nointr_nofail(fd);
160
161         return err;
162 }
163
164 int hwclock_is_localtime(void) {
165         FILE *f;
166         bool local = false;
167
168         /*
169          * The third line of adjtime is "UTC" or "LOCAL" or nothing.
170          *   # /etc/adjtime
171          *   0.0 0 0
172          *   0
173          *   UTC
174          */
175         f = fopen("/etc/adjtime", "re");
176         if (f) {
177                 char line[LINE_MAX];
178                 bool b;
179
180                 b = fgets(line, sizeof(line), f) &&
181                         fgets(line, sizeof(line), f) &&
182                         fgets(line, sizeof(line), f);
183
184                 fclose(f);
185
186                 if (!b)
187                         return -EIO;
188
189                 truncate_nl(line);
190                 local = streq(line, "LOCAL");
191
192         } else if (errno != -ENOENT)
193                 return -errno;
194
195         return local;
196 }
197
198 int hwclock_set_timezone(int *min) {
199         const struct timeval *tv_null = NULL;
200         struct timespec ts;
201         struct tm *tm;
202         int minutesdelta;
203         struct timezone tz;
204
205         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
206         assert_se(tm = localtime(&ts.tv_sec));
207         minutesdelta = tm->tm_gmtoff / 60;
208
209         tz.tz_minuteswest = -minutesdelta;
210         tz.tz_dsttime = 0; /* DST_NONE*/
211
212         /*
213          * If the hardware clock does not run in UTC, but in local time:
214          * The very first time we set the kernel's timezone, it will warp
215          * the clock so that it runs in UTC instead of local time.
216          */
217         if (settimeofday(tv_null, &tz) < 0)
218                 return -errno;
219         if (min)
220                 *min = minutesdelta;
221         return 0;
222 }
223
224 int hwclock_reset_timezone(void) {
225         const struct timeval *tv_null = NULL;
226         struct timezone tz;
227
228         tz.tz_minuteswest = 0;
229         tz.tz_dsttime = 0; /* DST_NONE*/
230
231         /*
232          * The very first time we set the kernel's timezone, it will warp
233          * the clock. Do a dummy call here, so the time warping is sealed
234          * and we set only the time zone with next call.
235          */
236         if (settimeofday(tv_null, &tz) < 0)
237                 return -errno;
238
239         return 0;
240 }