chiark / gitweb /
util: define union dirent_storage and make use of it everywhere
[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                 fd = open(p, flags);
98                 free(p);
99
100                 if (fd >= 0) {
101                         closedir(d);
102                         return fd;
103                 }
104         }
105
106 fallback:
107         if (d)
108                 closedir(d);
109
110         fd = open("/dev/rtc0", flags);
111         if (fd < 0)
112                 return -errno;
113
114         return fd;
115 }
116
117 int hwclock_get_time(struct tm *tm) {
118         int fd;
119         int err = 0;
120
121         assert(tm);
122
123         fd = rtc_open(O_RDONLY|O_CLOEXEC);
124         if (fd < 0)
125                 return -errno;
126
127         /* This leaves the timezone fields of struct tm
128          * uninitialized! */
129         if (ioctl(fd, RTC_RD_TIME, tm) < 0)
130                 err = -errno;
131
132         /* We don't know daylight saving, so we reset this in order not
133          * to confused mktime(). */
134         tm->tm_isdst = -1;
135
136         close_nointr_nofail(fd);
137
138         return err;
139 }
140
141 int hwclock_set_time(const struct tm *tm) {
142         int fd;
143         int err = 0;
144
145         assert(tm);
146
147         fd = rtc_open(O_RDONLY|O_CLOEXEC);
148         if (fd < 0)
149                 return -errno;
150
151         if (ioctl(fd, RTC_SET_TIME, tm) < 0)
152                 err = -errno;
153
154         close_nointr_nofail(fd);
155
156         return err;
157 }
158
159 int hwclock_is_localtime(void) {
160         FILE *f;
161         bool local = false;
162
163         /*
164          * The third line of adjtime is "UTC" or "LOCAL" or nothing.
165          *   # /etc/adjtime
166          *   0.0 0 0
167          *   0
168          *   UTC
169          */
170         f = fopen("/etc/adjtime", "re");
171         if (f) {
172                 char line[LINE_MAX];
173                 bool b;
174
175                 b = fgets(line, sizeof(line), f) &&
176                         fgets(line, sizeof(line), f) &&
177                         fgets(line, sizeof(line), f);
178
179                 fclose(f);
180
181                 if (!b)
182                         return -EIO;
183
184                 truncate_nl(line);
185                 local = streq(line, "LOCAL");
186
187         } else if (errno != -ENOENT)
188                 return -errno;
189
190         return local;
191 }
192
193 int hwclock_set_timezone(int *min) {
194         const struct timeval *tv_null = NULL;
195         struct timespec ts;
196         struct tm *tm;
197         int minuteswest;
198         struct timezone tz;
199
200         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
201         assert_se(tm = localtime(&ts.tv_sec));
202         minuteswest = tm->tm_gmtoff / 60;
203
204         tz.tz_minuteswest = -minuteswest;
205         tz.tz_dsttime = 0; /* DST_NONE*/
206
207         /*
208          * If the hardware clock does not run in UTC, but in local time:
209          * The very first time we set the kernel's timezone, it will warp
210          * the clock so that it runs in UTC instead of local time.
211          */
212         if (settimeofday(tv_null, &tz) < 0)
213                 return -errno;
214         if (min)
215                 *min = minuteswest;
216         return 0;
217 }
218
219 int hwclock_reset_timezone(void) {
220         const struct timeval *tv_null = NULL;
221         struct timezone tz;
222
223         tz.tz_minuteswest = 0;
224         tz.tz_dsttime = 0; /* DST_NONE*/
225
226         /*
227          * The very first time we set the kernel's timezone, it will warp
228          * the clock. Do a dummy call here, so the time warping is sealed
229          * and we set only the time zone with next call.
230          */
231         if (settimeofday(tv_null, &tz) < 0)
232                 return -errno;
233
234         return 0;
235 }