chiark / gitweb /
8f3cafd5eb8064c62b33ad556decb2923f211c4f
[elogind.git] / src / shared / time-dst.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Timezone file reading code from glibc 2.16.
7
8   Copyright (C) 1991-2012 Free Software Foundation, Inc.
9   Copyright 2012 Kay Sievers
10
11   systemd is free software; you can redistribute it and/or modify it
12   under the terms of the GNU Lesser General Public License as published by
13   the Free Software Foundation; either version 2.1 of the License, or
14   (at your option) any later version.
15
16   systemd is distributed in the hope that it will be useful, but
17   WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19   Lesser General Public License for more details.
20
21   You should have received a copy of the GNU Lesser General Public License
22   along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 ***/
24 #include <ctype.h>
25 #include <errno.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <endian.h>
32 #include <byteswap.h>
33 #include <assert.h>
34 #include <limits.h>
35 #include <unistd.h>
36 #include <stdint.h>
37 #include <stdbool.h>
38 #include <sys/stat.h>
39
40 #include "time-dst.h"
41
42 /*
43  * If tzh_version is '2' or greater, the above is followed by a second instance
44  * of tzhead and a second instance of the data in which each coded transition
45  * time uses 8 rather than 4 chars, then a POSIX-TZ-environment-variable-style
46  * string for use in handling instants after the last transition time stored in
47  * the file * (with nothing between the newlines if there is no POSIX
48  * representation for such instants).
49  */
50 #define TZ_MAGIC                "TZif"
51 struct tzhead {
52         char tzh_magic[4];      /* TZ_MAGIC */
53         char tzh_version[1];    /* '\0' or '2' as of 2005 */
54         char tzh_reserved[15];  /* reserved--must be zero */
55         char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
56         char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
57         char tzh_leapcnt[4];    /* coded number of leap seconds */
58         char tzh_timecnt[4];    /* coded number of transition times */
59         char tzh_typecnt[4];    /* coded number of local time types */
60         char tzh_charcnt[4];    /* coded number of abbr. chars */
61 };
62
63 struct ttinfo {
64         long int offset;        /* Seconds east of GMT.  */
65         unsigned char isdst;    /* Used to set tm_isdst.  */
66         unsigned char idx;      /* Index into `zone_names'.  */
67         unsigned char isstd;    /* Transition times are in standard time.  */
68         unsigned char isgmt;    /* Transition times are in GMT.  */
69 };
70
71 struct leap {
72         time_t transition;      /* Time the transition takes effect.  */
73         long int change;        /* Seconds of correction to apply.  */
74 };
75
76 static inline int decode(const void *ptr) {
77         return be32toh(*(int *)ptr);
78 }
79
80 static inline int64_t decode64(const void *ptr) {
81         return be64toh(*(int64_t *)ptr);
82 }
83
84 int time_get_dst(time_t date, const char *tzfile,
85                  time_t *switch_cur, char **zone_cur, bool *dst_cur,
86                  time_t *switch_next, char **zone_next, bool *dst_next) {
87         time_t *transitions = NULL;
88         size_t num_transitions = 0;
89         unsigned char *type_idxs = 0;
90         size_t num_types = 0;
91         struct ttinfo *types = NULL;
92         char *zone_names = NULL;
93         struct stat st;
94         size_t num_isstd, num_isgmt;
95         FILE *f;
96         struct tzhead tzhead;
97         size_t chars;
98         size_t i;
99         size_t total_size;
100         size_t types_idx;
101         int trans_width = 4;
102         size_t tzspec_len;
103         size_t num_leaps;
104         size_t lo, hi;
105         int err = 0;
106
107         f = fopen(tzfile, "re");
108         if (f == NULL)
109                 return -errno;
110
111         if (fstat(fileno(f), &st) < 0) {
112                 err = -errno;
113                 fclose(f);
114                 return err;
115         }
116
117 read_again:
118         if (fread((void *)&tzhead, sizeof(tzhead), 1, f) != 1 ||
119             memcmp(tzhead.tzh_magic, TZ_MAGIC, sizeof(tzhead.tzh_magic)) != 0)
120                 goto lose;
121
122         num_transitions = (size_t)decode(tzhead.tzh_timecnt);
123         num_types = (size_t)decode(tzhead.tzh_typecnt);
124         chars = (size_t)decode(tzhead.tzh_charcnt);
125         num_leaps = (size_t)decode(tzhead.tzh_leapcnt);
126         num_isstd = (size_t)decode(tzhead.tzh_ttisstdcnt);
127         num_isgmt = (size_t)decode(tzhead.tzh_ttisgmtcnt);
128
129         /* For platforms with 64-bit time_t we use the new format if available.  */
130         if (sizeof(time_t) == 8 && trans_width == 4 && tzhead.tzh_version[0] != '\0') {
131                 size_t to_skip;
132
133                 /* We use the 8-byte format.  */
134                 trans_width = 8;
135
136                 /* Position the stream before the second header.  */
137                 to_skip = (num_transitions * (4 + 1)
138                            + num_types * 6
139                            + chars
140                            + num_leaps * 8 + num_isstd + num_isgmt);
141                 if (fseek(f, to_skip, SEEK_CUR) != 0)
142                         goto lose;
143
144                 goto read_again;
145         }
146
147         if (num_transitions > ((SIZE_MAX - (__alignof__(struct ttinfo) - 1)) / (sizeof(time_t) + 1)))
148                  goto lose;
149
150         total_size = num_transitions * (sizeof(time_t) + 1);
151         total_size = ((total_size + __alignof__(struct ttinfo) - 1) & ~(__alignof__(struct ttinfo) - 1));
152         types_idx = total_size;
153         if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct ttinfo))
154                 goto lose;
155
156         total_size += num_types * sizeof(struct ttinfo);
157         if (chars > SIZE_MAX - total_size)
158                 goto lose;
159
160         total_size += chars;
161         if (__alignof__(struct leap) - 1 > SIZE_MAX - total_size)
162                  goto lose;
163
164         total_size = ((total_size + __alignof__(struct leap) - 1) & ~(__alignof__(struct leap) - 1));
165         if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct leap))
166                 goto lose;
167
168         total_size += num_leaps * sizeof(struct leap);
169         tzspec_len = 0;
170         if (sizeof(time_t) == 8 && trans_width == 8) {
171                 off_t rem = st.st_size - ftello(f);
172
173                 if (rem < 0 || (size_t) rem < (num_transitions * (8 + 1) + num_types * 6 + chars))
174                         goto lose;
175                 tzspec_len = (size_t) rem - (num_transitions * (8 + 1) + num_types * 6 + chars);
176                 if (num_leaps > SIZE_MAX / 12 || tzspec_len < num_leaps * 12)
177                         goto lose;
178                 tzspec_len -= num_leaps * 12;
179                 if (tzspec_len < num_isstd)
180                         goto lose;
181                 tzspec_len -= num_isstd;
182                 if (tzspec_len == 0 || tzspec_len - 1 < num_isgmt)
183                         goto lose;
184                 tzspec_len -= num_isgmt + 1;
185                 if (SIZE_MAX - total_size < tzspec_len)
186                         goto lose;
187         }
188
189         transitions = (time_t *)calloc(total_size + tzspec_len, 1);
190         if (transitions == NULL)
191                 goto lose;
192
193         type_idxs = (unsigned char *)transitions + (num_transitions
194                                                     * sizeof(time_t));
195         types = (struct ttinfo *)((char *)transitions + types_idx);
196         zone_names = (char *)types + num_types * sizeof(struct ttinfo);
197
198         if (sizeof(time_t) == 4 || trans_width == 8) {
199                 if (fread(transitions, trans_width + 1, num_transitions, f) != num_transitions)
200                         goto lose;
201         } else {
202                 if (fread(transitions, 4, num_transitions, f) != num_transitions ||
203                     fread(type_idxs, 1, num_transitions, f) != num_transitions)
204                         goto lose;
205         }
206
207         /* Check for bogus indices in the data file, so we can hereafter
208            safely use type_idxs[T] as indices into `types' and never crash.  */
209         for (i = 0; i < num_transitions; ++i)
210                 if (type_idxs[i] >= num_types)
211                         goto lose;
212
213         if ((BYTE_ORDER != BIG_ENDIAN && (sizeof(time_t) == 4 || trans_width == 4)) ||
214             (BYTE_ORDER == BIG_ENDIAN && sizeof(time_t) == 8 && trans_width == 4)) {
215                 /* Decode the transition times, stored as 4-byte integers in
216                    network (big-endian) byte order.  We work from the end of
217                    the array so as not to clobber the next element to be
218                    processed when sizeof (time_t) > 4.  */
219                 i = num_transitions;
220                 while (i-- > 0)
221                         transitions[i] = decode((char *)transitions + i * 4);
222         } else if (BYTE_ORDER != BIG_ENDIAN && sizeof(time_t) == 8) {
223                 /* Decode the transition times, stored as 8-byte integers in
224                    network (big-endian) byte order.  */
225                 for (i = 0; i < num_transitions; ++i)
226                         transitions[i] = decode64((char *)transitions + i * 8);
227         }
228
229         for (i = 0; i < num_types; ++i) {
230                 unsigned char x[4];
231                 int c;
232
233                 if (fread(x, 1, sizeof(x), f) != sizeof(x))
234                         goto lose;
235                 c = getc(f);
236                 if ((unsigned int)c > 1u)
237                         goto lose;
238                 types[i].isdst = c;
239                 c = getc(f);
240                 if ((size_t) c > chars)
241                         /* Bogus index in data file.  */
242                         goto lose;
243                 types[i].idx = c;
244                 types[i].offset = (long int)decode(x);
245         }
246
247         if (fread(zone_names, 1, chars, f) != chars)
248                 goto lose;
249
250         for (i = 0; i < num_isstd; ++i) {
251                 int c = getc(f);
252                 if (c == EOF)
253                         goto lose;
254                 types[i].isstd = c != 0;
255         }
256
257         while (i < num_types)
258                 types[i++].isstd = 0;
259
260         for (i = 0; i < num_isgmt; ++i) {
261                 int c = getc(f);
262                 if (c == EOF)
263                         goto lose;
264                 types[i].isgmt = c != 0;
265         }
266
267         while (i < num_types)
268                 types[i++].isgmt = 0;
269
270         if (num_transitions == 0)
271                goto lose;
272
273         if (date < transitions[0] || date >= transitions[num_transitions - 1])
274                goto lose;
275
276         /* Find the first transition after TIMER, and
277            then pick the type of the transition before it.  */
278         lo = 0;
279         hi = num_transitions - 1;
280
281         /* Assume that DST is changing twice a year and guess initial
282            search spot from it.
283            Half of a gregorian year has on average 365.2425 * 86400 / 2
284            = 15778476 seconds.  */
285         i = (transitions[num_transitions - 1] - date) / 15778476;
286         if (i < num_transitions) {
287                 i = num_transitions - 1 - i;
288                 if (date < transitions[i]) {
289                         if (i < 10 || date >= transitions[i - 10]) {
290                                 /* Linear search.  */
291                                 while (date < transitions[i - 1])
292                                         i--;
293                                 goto found;
294                         }
295                         hi = i - 10;
296                 } else {
297                         if (i + 10 >= num_transitions || date < transitions[i + 10]) {
298                                 /* Linear search.  */
299                                 while (date >= transitions[i])
300                                         i++;
301                                 goto found;
302                         }
303                         lo = i + 10;
304                 }
305         }
306
307         /* Binary search. */
308         while (lo + 1 < hi) {
309                 i = (lo + hi) / 2;
310                 if (date < transitions[i])
311                         hi = i;
312                 else
313                         lo = i;
314         }
315         i = hi;
316
317 found:
318         if (switch_cur)
319                 *switch_cur = transitions[i-1];
320         if (zone_cur)
321                 *zone_cur = strdup(&zone_names[types[type_idxs[i - 1]].idx]);
322         if (dst_cur)
323                 *dst_cur = types[type_idxs[i-1]].isdst;
324         if (switch_next)
325                 *switch_next = transitions[i];
326         if (zone_next)
327                 *zone_next = strdup(&zone_names[types[type_idxs[i]].idx]);
328         if (dst_next)
329                 *dst_next = types[type_idxs[i]].isdst;
330
331         free(transitions);
332         fclose(f);
333         return 0;
334 lose:
335         free(transitions);
336         fclose(f);
337         return err;
338 }