diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/shared/time-dst.c | 338 | ||||
-rw-r--r-- | src/shared/time-dst.h | 29 |
2 files changed, 367 insertions, 0 deletions
diff --git a/src/shared/time-dst.c b/src/shared/time-dst.c new file mode 100644 index 0000000000..8f3cafd5eb --- /dev/null +++ b/src/shared/time-dst.c @@ -0,0 +1,338 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Timezone file reading code from glibc 2.16. + + Copyright (C) 1991-2012 Free Software Foundation, Inc. + Copyright 2012 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ +#include <ctype.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <endian.h> +#include <byteswap.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <sys/stat.h> + +#include "time-dst.h" + +/* + * If tzh_version is '2' or greater, the above is followed by a second instance + * of tzhead and a second instance of the data in which each coded transition + * time uses 8 rather than 4 chars, then a POSIX-TZ-environment-variable-style + * string for use in handling instants after the last transition time stored in + * the file * (with nothing between the newlines if there is no POSIX + * representation for such instants). + */ +#define TZ_MAGIC "TZif" +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2' as of 2005 */ + char tzh_reserved[15]; /* reserved--must be zero */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +struct ttinfo { + long int offset; /* Seconds east of GMT. */ + unsigned char isdst; /* Used to set tm_isdst. */ + unsigned char idx; /* Index into `zone_names'. */ + unsigned char isstd; /* Transition times are in standard time. */ + unsigned char isgmt; /* Transition times are in GMT. */ +}; + +struct leap { + time_t transition; /* Time the transition takes effect. */ + long int change; /* Seconds of correction to apply. */ +}; + +static inline int decode(const void *ptr) { + return be32toh(*(int *)ptr); +} + +static inline int64_t decode64(const void *ptr) { + return be64toh(*(int64_t *)ptr); +} + +int time_get_dst(time_t date, const char *tzfile, + time_t *switch_cur, char **zone_cur, bool *dst_cur, + time_t *switch_next, char **zone_next, bool *dst_next) { + time_t *transitions = NULL; + size_t num_transitions = 0; + unsigned char *type_idxs = 0; + size_t num_types = 0; + struct ttinfo *types = NULL; + char *zone_names = NULL; + struct stat st; + size_t num_isstd, num_isgmt; + FILE *f; + struct tzhead tzhead; + size_t chars; + size_t i; + size_t total_size; + size_t types_idx; + int trans_width = 4; + size_t tzspec_len; + size_t num_leaps; + size_t lo, hi; + int err = 0; + + f = fopen(tzfile, "re"); + if (f == NULL) + return -errno; + + if (fstat(fileno(f), &st) < 0) { + err = -errno; + fclose(f); + return err; + } + +read_again: + if (fread((void *)&tzhead, sizeof(tzhead), 1, f) != 1 || + memcmp(tzhead.tzh_magic, TZ_MAGIC, sizeof(tzhead.tzh_magic)) != 0) + goto lose; + + num_transitions = (size_t)decode(tzhead.tzh_timecnt); + num_types = (size_t)decode(tzhead.tzh_typecnt); + chars = (size_t)decode(tzhead.tzh_charcnt); + num_leaps = (size_t)decode(tzhead.tzh_leapcnt); + num_isstd = (size_t)decode(tzhead.tzh_ttisstdcnt); + num_isgmt = (size_t)decode(tzhead.tzh_ttisgmtcnt); + + /* For platforms with 64-bit time_t we use the new format if available. */ + if (sizeof(time_t) == 8 && trans_width == 4 && tzhead.tzh_version[0] != '\0') { + size_t to_skip; + + /* We use the 8-byte format. */ + trans_width = 8; + + /* Position the stream before the second header. */ + to_skip = (num_transitions * (4 + 1) + + num_types * 6 + + chars + + num_leaps * 8 + num_isstd + num_isgmt); + if (fseek(f, to_skip, SEEK_CUR) != 0) + goto lose; + + goto read_again; + } + + if (num_transitions > ((SIZE_MAX - (__alignof__(struct ttinfo) - 1)) / (sizeof(time_t) + 1))) + goto lose; + + total_size = num_transitions * (sizeof(time_t) + 1); + total_size = ((total_size + __alignof__(struct ttinfo) - 1) & ~(__alignof__(struct ttinfo) - 1)); + types_idx = total_size; + if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct ttinfo)) + goto lose; + + total_size += num_types * sizeof(struct ttinfo); + if (chars > SIZE_MAX - total_size) + goto lose; + + total_size += chars; + if (__alignof__(struct leap) - 1 > SIZE_MAX - total_size) + goto lose; + + total_size = ((total_size + __alignof__(struct leap) - 1) & ~(__alignof__(struct leap) - 1)); + if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct leap)) + goto lose; + + total_size += num_leaps * sizeof(struct leap); + tzspec_len = 0; + if (sizeof(time_t) == 8 && trans_width == 8) { + off_t rem = st.st_size - ftello(f); + + if (rem < 0 || (size_t) rem < (num_transitions * (8 + 1) + num_types * 6 + chars)) + goto lose; + tzspec_len = (size_t) rem - (num_transitions * (8 + 1) + num_types * 6 + chars); + if (num_leaps > SIZE_MAX / 12 || tzspec_len < num_leaps * 12) + goto lose; + tzspec_len -= num_leaps * 12; + if (tzspec_len < num_isstd) + goto lose; + tzspec_len -= num_isstd; + if (tzspec_len == 0 || tzspec_len - 1 < num_isgmt) + goto lose; + tzspec_len -= num_isgmt + 1; + if (SIZE_MAX - total_size < tzspec_len) + goto lose; + } + + transitions = (time_t *)calloc(total_size + tzspec_len, 1); + if (transitions == NULL) + goto lose; + + type_idxs = (unsigned char *)transitions + (num_transitions + * sizeof(time_t)); + types = (struct ttinfo *)((char *)transitions + types_idx); + zone_names = (char *)types + num_types * sizeof(struct ttinfo); + + if (sizeof(time_t) == 4 || trans_width == 8) { + if (fread(transitions, trans_width + 1, num_transitions, f) != num_transitions) + goto lose; + } else { + if (fread(transitions, 4, num_transitions, f) != num_transitions || + fread(type_idxs, 1, num_transitions, f) != num_transitions) + goto lose; + } + + /* Check for bogus indices in the data file, so we can hereafter + safely use type_idxs[T] as indices into `types' and never crash. */ + for (i = 0; i < num_transitions; ++i) + if (type_idxs[i] >= num_types) + goto lose; + + if ((BYTE_ORDER != BIG_ENDIAN && (sizeof(time_t) == 4 || trans_width == 4)) || + (BYTE_ORDER == BIG_ENDIAN && sizeof(time_t) == 8 && trans_width == 4)) { + /* Decode the transition times, stored as 4-byte integers in + network (big-endian) byte order. We work from the end of + the array so as not to clobber the next element to be + processed when sizeof (time_t) > 4. */ + i = num_transitions; + while (i-- > 0) + transitions[i] = decode((char *)transitions + i * 4); + } else if (BYTE_ORDER != BIG_ENDIAN && sizeof(time_t) == 8) { + /* Decode the transition times, stored as 8-byte integers in + network (big-endian) byte order. */ + for (i = 0; i < num_transitions; ++i) + transitions[i] = decode64((char *)transitions + i * 8); + } + + for (i = 0; i < num_types; ++i) { + unsigned char x[4]; + int c; + + if (fread(x, 1, sizeof(x), f) != sizeof(x)) + goto lose; + c = getc(f); + if ((unsigned int)c > 1u) + goto lose; + types[i].isdst = c; + c = getc(f); + if ((size_t) c > chars) + /* Bogus index in data file. */ + goto lose; + types[i].idx = c; + types[i].offset = (long int)decode(x); + } + + if (fread(zone_names, 1, chars, f) != chars) + goto lose; + + for (i = 0; i < num_isstd; ++i) { + int c = getc(f); + if (c == EOF) + goto lose; + types[i].isstd = c != 0; + } + + while (i < num_types) + types[i++].isstd = 0; + + for (i = 0; i < num_isgmt; ++i) { + int c = getc(f); + if (c == EOF) + goto lose; + types[i].isgmt = c != 0; + } + + while (i < num_types) + types[i++].isgmt = 0; + + if (num_transitions == 0) + goto lose; + + if (date < transitions[0] || date >= transitions[num_transitions - 1]) + goto lose; + + /* Find the first transition after TIMER, and + then pick the type of the transition before it. */ + lo = 0; + hi = num_transitions - 1; + + /* Assume that DST is changing twice a year and guess initial + search spot from it. + Half of a gregorian year has on average 365.2425 * 86400 / 2 + = 15778476 seconds. */ + i = (transitions[num_transitions - 1] - date) / 15778476; + if (i < num_transitions) { + i = num_transitions - 1 - i; + if (date < transitions[i]) { + if (i < 10 || date >= transitions[i - 10]) { + /* Linear search. */ + while (date < transitions[i - 1]) + i--; + goto found; + } + hi = i - 10; + } else { + if (i + 10 >= num_transitions || date < transitions[i + 10]) { + /* Linear search. */ + while (date >= transitions[i]) + i++; + goto found; + } + lo = i + 10; + } + } + + /* Binary search. */ + while (lo + 1 < hi) { + i = (lo + hi) / 2; + if (date < transitions[i]) + hi = i; + else + lo = i; + } + i = hi; + +found: + if (switch_cur) + *switch_cur = transitions[i-1]; + if (zone_cur) + *zone_cur = strdup(&zone_names[types[type_idxs[i - 1]].idx]); + if (dst_cur) + *dst_cur = types[type_idxs[i-1]].isdst; + if (switch_next) + *switch_next = transitions[i]; + if (zone_next) + *zone_next = strdup(&zone_names[types[type_idxs[i]].idx]); + if (dst_next) + *dst_next = types[type_idxs[i]].isdst; + + free(transitions); + fclose(f); + return 0; +lose: + free(transitions); + fclose(f); + return err; +} diff --git a/src/shared/time-dst.h b/src/shared/time-dst.h new file mode 100644 index 0000000000..3fa6c4423c --- /dev/null +++ b/src/shared/time-dst.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef footimedst +#define footimedst + +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int time_get_dst(time_t date, const char *tzfile, + time_t *switch_cur, char **zone_cur, bool *dst_cur, + time_t *switch_next, char **zone_next, bool *dst_next); + +#endif |