chiark / gitweb /
do not create /dev/rtc symlink, let systemd search for it if needed
[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 #include "fileio.h"
45
46 static int rtc_open(int flags) {
47         int fd;
48         DIR *d;
49
50         /*
51          * Some "chaotic platforms" have multiple RTCs and we need to
52          * find the "system RTC", which is in some setups /dev/rtc1.
53          *
54          * First, we try to find the RTC which has hctosys=1 set. If we
55          * don't find any we just take the first RTC that exists at all,
56          * then try to open /dev/rtc0.
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         _cleanup_fclose_ FILE *f;
166
167         /*
168          * The third line of adjtime is "UTC" or "LOCAL" or nothing.
169          *   # /etc/adjtime
170          *   0.0 0 0
171          *   0
172          *   UTC
173          */
174         f = fopen("/etc/adjtime", "re");
175         if (f) {
176                 char line[LINE_MAX];
177                 bool b;
178
179                 b = fgets(line, sizeof(line), f) &&
180                         fgets(line, sizeof(line), f) &&
181                         fgets(line, sizeof(line), f);
182                 if (!b)
183                         return -EIO;
184
185                 truncate_nl(line);
186                 return streq(line, "LOCAL");
187
188         } else if (errno != ENOENT)
189                 return -errno;
190
191         return 0;
192 }
193
194 int hwclock_set_timezone(int *min) {
195         const struct timeval *tv_null = NULL;
196         struct timespec ts;
197         struct tm *tm;
198         int minutesdelta;
199         struct timezone tz;
200
201         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
202         assert_se(tm = localtime(&ts.tv_sec));
203         minutesdelta = tm->tm_gmtoff / 60;
204
205         tz.tz_minuteswest = -minutesdelta;
206         tz.tz_dsttime = 0; /* DST_NONE*/
207
208         /*
209          * If the hardware clock does not run in UTC, but in local time:
210          * The very first time we set the kernel's timezone, it will warp
211          * the clock so that it runs in UTC instead of local time.
212          */
213         if (settimeofday(tv_null, &tz) < 0)
214                 return -errno;
215         if (min)
216                 *min = minutesdelta;
217         return 0;
218 }
219
220 int hwclock_reset_timezone(void) {
221         const struct timeval *tv_null = NULL;
222         struct timezone tz;
223
224         tz.tz_minuteswest = 0;
225         tz.tz_dsttime = 0; /* DST_NONE*/
226
227         /*
228          * The very first time we set the kernel's timezone, it will warp
229          * the clock. Do a dummy call here, so the time warping is sealed
230          * and we set only the time zone with next call.
231          */
232         if (settimeofday(tv_null, &tz) < 0)
233                 return -errno;
234
235         return 0;
236 }