chiark / gitweb /
tests: add missing entry to test-tables
[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 #include "util.h"
42
43 /*
44  * If tzh_version is '2' or greater, the above is followed by a second instance
45  * of tzhead and a second instance of the data in which each coded transition
46  * time uses 8 rather than 4 chars, then a POSIX-TZ-environment-variable-style
47  * string for use in handling instants after the last transition time stored in
48  * the file * (with nothing between the newlines if there is no POSIX
49  * representation for such instants).
50  */
51 #define TZ_MAGIC                "TZif"
52 struct tzhead {
53         char tzh_magic[4];      /* TZ_MAGIC */
54         char tzh_version[1];    /* '\0' or '2' as of 2005 */
55         char tzh_reserved[15];  /* reserved--must be zero */
56         char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
57         char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
58         char tzh_leapcnt[4];    /* coded number of leap seconds */
59         char tzh_timecnt[4];    /* coded number of transition times */
60         char tzh_typecnt[4];    /* coded number of local time types */
61         char tzh_charcnt[4];    /* coded number of abbr. chars */
62 };
63
64 struct ttinfo {
65         long int offset;        /* Seconds east of GMT.  */
66         unsigned char isdst;    /* Used to set tm_isdst.  */
67         unsigned char idx;      /* Index into `zone_names'.  */
68         unsigned char isstd;    /* Transition times are in standard time.  */
69         unsigned char isgmt;    /* Transition times are in GMT.  */
70 };
71
72 struct leap {
73         time_t transition;      /* Time the transition takes effect.  */
74         long int change;        /* Seconds of correction to apply.  */
75 };
76
77 static inline int decode(const void *ptr) {
78         return be32toh(*(int *)ptr);
79 }
80
81 static inline int64_t decode64(const void *ptr) {
82         return be64toh(*(int64_t *)ptr);
83 }
84
85 int time_get_dst(time_t date, const char *tzfile,
86                  time_t *switch_cur, char **zone_cur, bool *dst_cur,
87                  time_t *switch_next, int *delta_next, char **zone_next, bool *dst_next) {
88         unsigned char *type_idxs = 0;
89         size_t num_types = 0;
90         struct ttinfo *types = NULL;
91         char *zone_names = NULL;
92         struct stat st;
93         size_t num_isstd, num_isgmt;
94         struct tzhead tzhead;
95         size_t chars;
96         size_t i;
97         size_t total_size;
98         size_t types_idx;
99         int trans_width = 4;
100         size_t tzspec_len;
101         size_t num_leaps;
102         size_t lo, hi;
103         size_t num_transitions = 0;
104         _cleanup_free_ time_t *transitions = NULL;
105         _cleanup_fclose_ FILE *f;
106
107         f = fopen(tzfile, "re");
108         if (f == NULL)
109                 return -errno;
110
111         if (fstat(fileno(f), &st) < 0)
112                 return -errno;
113
114 read_again:
115         if (fread((void *)&tzhead, sizeof(tzhead), 1, f) != 1 ||
116             memcmp(tzhead.tzh_magic, TZ_MAGIC, sizeof(tzhead.tzh_magic)) != 0)
117                 return -EINVAL;
118
119         num_transitions = (size_t)decode(tzhead.tzh_timecnt);
120         num_types = (size_t)decode(tzhead.tzh_typecnt);
121         chars = (size_t)decode(tzhead.tzh_charcnt);
122         num_leaps = (size_t)decode(tzhead.tzh_leapcnt);
123         num_isstd = (size_t)decode(tzhead.tzh_ttisstdcnt);
124         num_isgmt = (size_t)decode(tzhead.tzh_ttisgmtcnt);
125
126         /* For platforms with 64-bit time_t we use the new format if available.  */
127         if (sizeof(time_t) == 8 && trans_width == 4 && tzhead.tzh_version[0] != '\0') {
128                 size_t to_skip;
129
130                 /* We use the 8-byte format.  */
131                 trans_width = 8;
132
133                 /* Position the stream before the second header.  */
134                 to_skip = (num_transitions * (4 + 1)
135                            + num_types * 6
136                            + chars
137                            + num_leaps * 8 + num_isstd + num_isgmt);
138                 if (fseek(f, to_skip, SEEK_CUR) != 0)
139                         return -EINVAL;
140
141                 goto read_again;
142         }
143
144         if (num_transitions > ((SIZE_MAX - (__alignof__(struct ttinfo) - 1)) / (sizeof(time_t) + 1)))
145                  return -EINVAL;
146
147         total_size = num_transitions * (sizeof(time_t) + 1);
148         total_size = ((total_size + __alignof__(struct ttinfo) - 1) & ~(__alignof__(struct ttinfo) - 1));
149         types_idx = total_size;
150         if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct ttinfo))
151                 return -EINVAL;
152
153         total_size += num_types * sizeof(struct ttinfo);
154         if (chars > SIZE_MAX - total_size)
155                 return -EINVAL;
156
157         total_size += chars;
158         if (__alignof__(struct leap) - 1 > SIZE_MAX - total_size)
159                  return -EINVAL;
160
161         total_size = ((total_size + __alignof__(struct leap) - 1) & ~(__alignof__(struct leap) - 1));
162         if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct leap))
163                 return -EINVAL;
164
165         total_size += num_leaps * sizeof(struct leap);
166         tzspec_len = 0;
167         if (sizeof(time_t) == 8 && trans_width == 8) {
168                 off_t rem = st.st_size - ftello(f);
169
170                 if (rem < 0 || (size_t) rem < (num_transitions * (8 + 1) + num_types * 6 + chars))
171                         return -EINVAL;
172                 tzspec_len = (size_t) rem - (num_transitions * (8 + 1) + num_types * 6 + chars);
173                 if (num_leaps > SIZE_MAX / 12 || tzspec_len < num_leaps * 12)
174                         return -EINVAL;
175                 tzspec_len -= num_leaps * 12;
176                 if (tzspec_len < num_isstd)
177                         return -EINVAL;
178                 tzspec_len -= num_isstd;
179                 if (tzspec_len == 0 || tzspec_len - 1 < num_isgmt)
180                         return -EINVAL;
181                 tzspec_len -= num_isgmt + 1;
182                 if (SIZE_MAX - total_size < tzspec_len)
183                         return -EINVAL;
184         }
185
186         transitions = malloc0(total_size + tzspec_len);
187         if (transitions == NULL)
188                 return -EINVAL;
189
190         type_idxs = (unsigned char *)transitions + (num_transitions
191                                                     * sizeof(time_t));
192         types = (struct ttinfo *)((char *)transitions + types_idx);
193         zone_names = (char *)types + num_types * sizeof(struct ttinfo);
194
195         if (sizeof(time_t) == 4 || trans_width == 8) {
196                 if (fread(transitions, trans_width + 1, num_transitions, f) != num_transitions)
197                         return -EINVAL;
198         } else {
199                 if (fread(transitions, 4, num_transitions, f) != num_transitions ||
200                     fread(type_idxs, 1, num_transitions, f) != num_transitions)
201                         return -EINVAL;
202         }
203
204         /* Check for bogus indices in the data file, so we can hereafter
205            safely use type_idxs[T] as indices into `types' and never crash.  */
206         for (i = 0; i < num_transitions; ++i)
207                 if (type_idxs[i] >= num_types)
208                         return -EINVAL;
209
210         if (__BYTE_ORDER == __BIG_ENDIAN ? sizeof(time_t) == 8 && trans_width == 4
211                                          : sizeof(time_t) == 4 || trans_width == 4) {
212                 /* Decode the transition times, stored as 4-byte integers in
213                    network (big-endian) byte order.  We work from the end of
214                    the array so as not to clobber the next element to be
215                    processed when sizeof (time_t) > 4.  */
216                 i = num_transitions;
217                 while (i-- > 0)
218                         transitions[i] = decode((char *)transitions + i * 4);
219         } else if (__BYTE_ORDER != __BIG_ENDIAN && sizeof(time_t) == 8) {
220                 /* Decode the transition times, stored as 8-byte integers in
221                    network (big-endian) byte order.  */
222                 for (i = 0; i < num_transitions; ++i)
223                         transitions[i] = decode64((char *)transitions + i * 8);
224         }
225
226         for (i = 0; i < num_types; ++i) {
227                 unsigned char x[4];
228                 int c;
229
230                 if (fread(x, 1, sizeof(x), f) != sizeof(x))
231                         return -EINVAL;
232                 c = getc(f);
233                 if ((unsigned int)c > 1u)
234                         return -EINVAL;
235                 types[i].isdst = c;
236                 c = getc(f);
237                 if ((size_t) c > chars)
238                         /* Bogus index in data file.  */
239                         return -EINVAL;
240                 types[i].idx = c;
241                 types[i].offset = (long int)decode(x);
242         }
243
244         if (fread(zone_names, 1, chars, f) != chars)
245                 return -EINVAL;
246
247         for (i = 0; i < num_isstd; ++i) {
248                 int c = getc(f);
249                 if (c == EOF)
250                         return -EINVAL;
251                 types[i].isstd = c != 0;
252         }
253
254         while (i < num_types)
255                 types[i++].isstd = 0;
256
257         for (i = 0; i < num_isgmt; ++i) {
258                 int c = getc(f);
259                 if (c == EOF)
260                         return -EINVAL;
261                 types[i].isgmt = c != 0;
262         }
263
264         while (i < num_types)
265                 types[i++].isgmt = 0;
266
267         if (num_transitions == 0)
268                return -EINVAL;
269
270         if (date < transitions[0] || date >= transitions[num_transitions - 1])
271                return -EINVAL;
272
273         /* Find the first transition after TIMER, and
274            then pick the type of the transition before it.  */
275         lo = 0;
276         hi = num_transitions - 1;
277
278         /* Assume that DST is changing twice a year and guess initial
279            search spot from it.
280            Half of a gregorian year has on average 365.2425 * 86400 / 2
281            = 15778476 seconds.  */
282         i = (transitions[num_transitions - 1] - date) / 15778476;
283         if (i < num_transitions) {
284                 i = num_transitions - 1 - i;
285                 if (date < transitions[i]) {
286                         if (i < 10 || date >= transitions[i - 10]) {
287                                 /* Linear search.  */
288                                 while (date < transitions[i - 1])
289                                         i--;
290                                 goto found;
291                         }
292                         hi = i - 10;
293                 } else {
294                         if (i + 10 >= num_transitions || date < transitions[i + 10]) {
295                                 /* Linear search.  */
296                                 while (date >= transitions[i])
297                                         i++;
298                                 goto found;
299                         }
300                         lo = i + 10;
301                 }
302         }
303
304         /* Binary search. */
305         while (lo + 1 < hi) {
306                 i = (lo + hi) / 2;
307                 if (date < transitions[i])
308                         hi = i;
309                 else
310                         lo = i;
311         }
312         i = hi;
313
314 found:
315         if (switch_cur)
316                 *switch_cur = transitions[i-1];
317         if (zone_cur)
318                 *zone_cur = strdup(&zone_names[types[type_idxs[i - 1]].idx]);
319         if (dst_cur)
320                 *dst_cur = types[type_idxs[i-1]].isdst;
321
322         if (switch_next)
323                 *switch_next = transitions[i];
324         if (delta_next)
325                 *delta_next = (types[type_idxs[i]].offset - types[type_idxs[i-1]].offset) / 60;
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         return 0;
332 }