1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <stdbool.h>
7 #include <time.h>
8 #include <linux/rtc.h>
9 #include <stdio.h>
10 #include <sys/ioctl.h>
11 #include <sys/time.h>
12
13 #include "alloc-util.h"
14 #include "clock-util.h"
15 #include "errno-util.h"
16 #include "fd-util.h"
17 #include "fileio.h"
18 #include "macro.h"
19 #include "string-util.h"
20
clock_get_hwclock(struct tm * tm)21 int clock_get_hwclock(struct tm *tm) {
22 _cleanup_close_ int fd = -1;
23
24 assert(tm);
25
26 fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
27 if (fd < 0)
28 return -errno;
29
30 /* This leaves the timezone fields of struct tm
31 * uninitialized! */
32 if (ioctl(fd, RTC_RD_TIME, tm) < 0)
33 return -errno;
34
35 /* We don't know daylight saving, so we reset this in order not
36 * to confuse mktime(). */
37 tm->tm_isdst = -1;
38
39 return 0;
40 }
41
clock_set_hwclock(const struct tm * tm)42 int clock_set_hwclock(const struct tm *tm) {
43 _cleanup_close_ int fd = -1;
44
45 assert(tm);
46
47 fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
48 if (fd < 0)
49 return -errno;
50
51 return RET_NERRNO(ioctl(fd, RTC_SET_TIME, tm));
52 }
53
clock_is_localtime(const char * adjtime_path)54 int clock_is_localtime(const char* adjtime_path) {
55 _cleanup_fclose_ FILE *f = NULL;
56 int r;
57
58 if (!adjtime_path)
59 adjtime_path = "/etc/adjtime";
60
61 /*
62 * The third line of adjtime is "UTC" or "LOCAL" or nothing.
63 * # /etc/adjtime
64 * 0.0 0 0
65 * 0
66 * UTC
67 */
68 f = fopen(adjtime_path, "re");
69 if (f) {
70 _cleanup_free_ char *line = NULL;
71 unsigned i;
72
73 for (i = 0; i < 2; i++) { /* skip the first two lines */
74 r = read_line(f, LONG_LINE_MAX, NULL);
75 if (r < 0)
76 return r;
77 if (r == 0)
78 return false; /* less than three lines → default to UTC */
79 }
80
81 r = read_line(f, LONG_LINE_MAX, &line);
82 if (r < 0)
83 return r;
84 if (r == 0)
85 return false; /* less than three lines → default to UTC */
86
87 return streq(line, "LOCAL");
88
89 } else if (errno != ENOENT)
90 return -errno;
91
92 /* adjtime not present → default to UTC */
93 return false;
94 }
95
clock_set_timezone(int * ret_minutesdelta)96 int clock_set_timezone(int *ret_minutesdelta) {
97 struct timespec ts;
98 struct tm tm;
99 int minutesdelta;
100 struct timezone tz;
101
102 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
103 assert_se(localtime_r(&ts.tv_sec, &tm));
104 minutesdelta = tm.tm_gmtoff / 60;
105
106 tz = (struct timezone) {
107 .tz_minuteswest = -minutesdelta,
108 .tz_dsttime = 0, /* DST_NONE */
109 };
110
111 /* If the RTC does not run in UTC but in local time, the very first call to settimeofday() will set
112 * the kernel's timezone and will warp the system clock, so that it runs in UTC instead of the local
113 * time we have read from the RTC. */
114 if (settimeofday(NULL, &tz) < 0)
115 return -errno;
116
117 if (ret_minutesdelta)
118 *ret_minutesdelta = minutesdelta;
119
120 return 0;
121 }
122
clock_reset_timewarp(void)123 int clock_reset_timewarp(void) {
124 static const struct timezone tz = {
125 .tz_minuteswest = 0,
126 .tz_dsttime = 0, /* DST_NONE */
127 };
128
129 /* The very first call to settimeofday() does time warp magic. Do a dummy call here, so the time
130 * warping is sealed and all later calls behave as expected. */
131 return RET_NERRNO(settimeofday(NULL, &tz));
132 }
133
134 #define EPOCH_FILE "/usr/lib/clock-epoch"
135
clock_apply_epoch(ClockChangeDirection * ret_attempted_change)136 int clock_apply_epoch(ClockChangeDirection *ret_attempted_change) {
137 usec_t epoch_usec, now_usec;
138 struct stat st;
139
140 /* NB: we update *ret_attempted_change in *all* cases, both
141 * on success and failure, to indicate what we intended to do! */
142
143 assert(ret_attempted_change);
144
145 if (stat(EPOCH_FILE, &st) < 0) {
146 if (errno != ENOENT)
147 log_warning_errno(errno, "Cannot stat " EPOCH_FILE ": %m");
148
149 epoch_usec = (usec_t) TIME_EPOCH * USEC_PER_SEC;
150 } else
151 epoch_usec = timespec_load(&st.st_mtim);
152
153 now_usec = now(CLOCK_REALTIME);
154 if (now_usec < epoch_usec)
155 *ret_attempted_change = CLOCK_CHANGE_FORWARD;
156 else if (now_usec > usec_add(epoch_usec, CLOCK_VALID_RANGE_USEC_MAX))
157 *ret_attempted_change = CLOCK_CHANGE_BACKWARD;
158 else {
159 *ret_attempted_change = CLOCK_CHANGE_NOOP;
160 return 0;
161 }
162
163 if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(epoch_usec)) < 0)
164 return -errno;
165
166 return 1;
167 }
168