| #include "time_impl.h" |
| #include <stdint.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "libc.h" |
| |
| long __timezone = 0; |
| int __daylight = 0; |
| char* __tzname[2] = {0, 0}; |
| |
| weak_alias(__timezone, timezone); |
| weak_alias(__daylight, daylight); |
| weak_alias(__tzname, tzname); |
| |
| static char std_name[TZNAME_MAX + 1]; |
| static char dst_name[TZNAME_MAX + 1]; |
| const char __gmt[] = "GMT"; |
| |
| static int dst_off; |
| static int r0[5], r1[5]; |
| |
| static const unsigned char *zi, *trans, *index, *types, *abbrevs, *abbrevs_end; |
| static size_t map_size; |
| |
| static char old_tz_buf[32]; |
| static char* old_tz = old_tz_buf; |
| static size_t old_tz_size = sizeof old_tz_buf; |
| |
| static volatile int lock[2]; |
| |
| static int getint(const char** p) { |
| unsigned x; |
| for (x = 0; **p - '0' < 10U; (*p)++) |
| x = **p - '0' + 10 * x; |
| return x; |
| } |
| |
| static int getoff(const char** p) { |
| int neg = 0; |
| if (**p == '-') { |
| ++*p; |
| neg = 1; |
| } else if (**p == '+') { |
| ++*p; |
| } |
| int off = 3600 * getint(p); |
| if (**p == ':') { |
| ++*p; |
| off += 60 * getint(p); |
| if (**p == ':') { |
| ++*p; |
| off += getint(p); |
| } |
| } |
| return neg ? -off : off; |
| } |
| |
| static void getrule(const char** p, int rule[5]) { |
| int r = rule[0] = **p; |
| |
| if (r != 'M') { |
| if (r == 'J') |
| ++*p; |
| else |
| rule[0] = 0; |
| rule[1] = getint(p); |
| } else { |
| ++*p; |
| rule[1] = getint(p); |
| ++*p; |
| rule[2] = getint(p); |
| ++*p; |
| rule[3] = getint(p); |
| } |
| |
| if (**p == '/') { |
| ++*p; |
| rule[4] = getoff(p); |
| } else { |
| rule[4] = 7200; |
| } |
| } |
| |
| static void getname(char* d, const char** p) { |
| int i; |
| if (**p == '<') { |
| ++*p; |
| for (i = 0; **p != '>' && i < TZNAME_MAX; i++) |
| d[i] = (*p)[i]; |
| ++*p; |
| } else { |
| for (i = 0; ((*p)[i] | 32) - 'a' < 26U && i < TZNAME_MAX; i++) |
| d[i] = (*p)[i]; |
| } |
| *p += i; |
| d[i] = 0; |
| } |
| |
| #define VEC(...) ((const unsigned char[]){__VA_ARGS__}) |
| |
| static uint32_t zi_read32(const unsigned char* z) { |
| return (unsigned)z[0] << 24 | z[1] << 16 | z[2] << 8 | z[3]; |
| } |
| |
| static size_t zi_dotprod(const unsigned char* z, |
| const unsigned char* v, |
| size_t n) { |
| size_t y; |
| uint32_t x; |
| for (y = 0; n; n--, z += 4, v++) { |
| x = zi_read32(z); |
| y += x * *v; |
| } |
| return y; |
| } |
| |
| int __munmap(void*, size_t); |
| |
| static void do_tzset() { |
| char buf[NAME_MAX + 25], *pathname = buf + 24; |
| const char* try |
| , *s, *p; |
| const unsigned char* map = 0; |
| size_t i; |
| static const char search[] = |
| "/usr/share/zoneinfo/\0/share/zoneinfo/\0/etc/zoneinfo/\0"; |
| |
| s = getenv("TZ"); |
| if (!s) |
| s = "/etc/localtime"; |
| if (!*s) |
| s = __gmt; |
| |
| if (old_tz && !strcmp(s, old_tz)) |
| return; |
| |
| if (zi) |
| __munmap((void*)zi, map_size); |
| |
| /* Cache the old value of TZ to check if it has changed. Avoid |
| * free so as not to pull it into static programs. Growth |
| * strategy makes it so free would have minimal benefit anyway. */ |
| i = strlen(s); |
| if (i > PATH_MAX + 1) |
| s = __gmt, i = 3; |
| if (i >= old_tz_size) { |
| old_tz_size *= 2; |
| if (i >= old_tz_size) |
| old_tz_size = i + 1; |
| if (old_tz_size > PATH_MAX + 2) |
| old_tz_size = PATH_MAX + 2; |
| old_tz = malloc(old_tz_size); |
| } |
| if (old_tz) |
| memcpy(old_tz, s, i + 1); |
| |
| /* Non-suid can use an absolute tzfile pathname or a relative |
| * pathame beginning with "."; in secure mode, only the |
| * standard path will be searched. */ |
| if (*s == ':' || ((p = strchr(s, '/')) && !memchr(s, ',', p - s))) { |
| if (*s == ':') |
| s++; |
| if (*s == '/' || *s == '.') { |
| if (!libc.secure || !strcmp(s, "/etc/localtime")) |
| map = __map_file(s, &map_size); |
| } else { |
| size_t l = strlen(s); |
| if (l <= NAME_MAX && !strchr(s, '.')) { |
| memcpy(pathname, s, l + 1); |
| pathname[l] = 0; |
| for (try = search; !map && *try; try += l + 1) { |
| l = strlen(try); |
| memcpy(pathname - l, try, l); |
| map = __map_file(pathname - l, &map_size); |
| } |
| } |
| } |
| if (!map) |
| s = __gmt; |
| } |
| if (map && (map_size < 44 || memcmp(map, "TZif", 4))) { |
| __munmap((void*)map, map_size); |
| map = 0; |
| s = __gmt; |
| } |
| |
| zi = map; |
| if (map) { |
| int scale = 2; |
| if (sizeof(time_t) > 4 && map[4] == '2') { |
| size_t skip = zi_dotprod(zi + 20, VEC(1, 1, 8, 5, 6, 1), 6); |
| trans = zi + skip + 44 + 44; |
| scale++; |
| } else { |
| trans = zi + 44; |
| } |
| index = trans + (zi_read32(trans - 12) << scale); |
| types = index + zi_read32(trans - 12); |
| abbrevs = types + 6 * zi_read32(trans - 8); |
| abbrevs_end = abbrevs + zi_read32(trans - 4); |
| if (zi[map_size - 1] == '\n') { |
| for (s = (const char*)zi + map_size - 2; *s != '\n'; s--) |
| ; |
| s++; |
| } else { |
| const unsigned char* p; |
| __tzname[0] = __tzname[1] = 0; |
| __daylight = __timezone = dst_off = 0; |
| for (i = 0; i < 5; i++) |
| r0[i] = r1[i] = 0; |
| for (p = types; p < abbrevs; p += 6) { |
| if (!p[4] && !__tzname[0]) { |
| __tzname[0] = (char*)abbrevs + p[5]; |
| __timezone = -zi_read32(p); |
| } |
| if (p[4] && !__tzname[1]) { |
| __tzname[1] = (char*)abbrevs + p[5]; |
| dst_off = -zi_read32(p); |
| __daylight = 1; |
| } |
| } |
| if (!__tzname[0]) |
| __tzname[0] = __tzname[1]; |
| if (!__tzname[0]) |
| __tzname[0] = (char*)__gmt; |
| if (!__daylight) { |
| __tzname[1] = __tzname[0]; |
| dst_off = __timezone; |
| } |
| return; |
| } |
| } |
| |
| if (!s) |
| s = __gmt; |
| getname(std_name, &s); |
| __tzname[0] = std_name; |
| __timezone = getoff(&s); |
| getname(dst_name, &s); |
| __tzname[1] = dst_name; |
| if (dst_name[0]) { |
| __daylight = 1; |
| if (*s == '+' || *s == '-' || *s - '0' < 10U) |
| dst_off = getoff(&s); |
| else |
| dst_off = __timezone - 3600; |
| } else { |
| __daylight = 0; |
| dst_off = 0; |
| } |
| |
| if (*s == ',') |
| s++, getrule(&s, r0); |
| if (*s == ',') |
| s++, getrule(&s, r1); |
| } |
| |
| /* Search zoneinfo rules to find the one that applies to the given time, |
| * and determine alternate opposite-DST-status rule that may be needed. */ |
| |
| static size_t scan_trans(long long t, int local, size_t* alt) { |
| int scale = 3 - (trans == zi + 44); |
| uint64_t x; |
| int off = 0; |
| |
| size_t a = 0, n = (index - trans) >> scale, m; |
| |
| if (!n) { |
| if (alt) |
| *alt = 0; |
| return 0; |
| } |
| |
| /* Binary search for 'most-recent rule before t'. */ |
| while (n > 1) { |
| m = a + n / 2; |
| x = zi_read32(trans + (m << scale)); |
| if (scale == 3) |
| x = x << 32 | zi_read32(trans + (m << scale) + 4); |
| else |
| x = (int32_t)x; |
| if (local) |
| off = (int32_t)zi_read32(types + 6 * index[m - 1]); |
| if (t - off < (int64_t)x) { |
| n /= 2; |
| } else { |
| a = m; |
| n -= n / 2; |
| } |
| } |
| |
| /* First and last entry are special. First means to use lowest-index |
| * non-DST type. Last means to apply POSIX-style rule if available. */ |
| n = (index - trans) >> scale; |
| if (a == n - 1) |
| return -1; |
| if (a == 0) { |
| x = zi_read32(trans + (a << scale)); |
| if (scale == 3) |
| x = x << 32 | zi_read32(trans + (a << scale) + 4); |
| else |
| x = (int32_t)x; |
| if (local) |
| off = (int32_t)zi_read32(types + 6 * index[a - 1]); |
| if (t - off < (int64_t)x) { |
| for (a = 0; a < (abbrevs - types) / 6; a++) { |
| if (types[6 * a + 4] != types[4]) |
| break; |
| } |
| if (a == (abbrevs - types) / 6) |
| a = 0; |
| if (types[6 * a + 4]) { |
| *alt = a; |
| return 0; |
| } else { |
| *alt = 0; |
| return a; |
| } |
| } |
| } |
| |
| /* Try to find a neighboring opposite-DST-status rule. */ |
| if (alt) { |
| if (a && types[6 * index[a - 1] + 4] != types[6 * index[a] + 4]) |
| *alt = index[a - 1]; |
| else if (a + 1 < n && |
| types[6 * index[a + 1] + 4] != types[6 * index[a] + 4]) |
| *alt = index[a + 1]; |
| else |
| *alt = index[a]; |
| } |
| |
| return index[a]; |
| } |
| |
| static int days_in_month(int m, int is_leap) { |
| if (m == 2) |
| return 28 + is_leap; |
| else |
| return 30 + ((0xad5 >> (m - 1)) & 1); |
| } |
| |
| /* Convert a POSIX DST rule plus year to seconds since epoch. */ |
| |
| static long long rule_to_secs(const int* rule, int year) { |
| int is_leap; |
| long long t = __year_to_secs(year, &is_leap); |
| int x, m, n, d; |
| if (rule[0] != 'M') { |
| x = rule[1]; |
| if (rule[0] == 'J' && (x < 60 || !is_leap)) |
| x--; |
| t += 86400 * x; |
| } else { |
| m = rule[1]; |
| n = rule[2]; |
| d = rule[3]; |
| t += __month_to_secs(m - 1, is_leap); |
| int wday = (int)((t + 4 * 86400) % (7 * 86400)) / 86400; |
| int days = d - wday; |
| if (days < 0) |
| days += 7; |
| if (n == 5 && days + 28 >= days_in_month(m, is_leap)) |
| n = 4; |
| t += 86400 * (days + 7 * (n - 1)); |
| } |
| t += rule[4]; |
| return t; |
| } |
| |
| /* Determine the time zone in effect for a given time in seconds since the |
| * epoch. It can be given in local or universal time. The results will |
| * indicate whether DST is in effect at the queried time, and will give both |
| * the GMT offset for the active zone/DST rule and the opposite DST. This |
| * enables a caller to efficiently adjust for the case where an explicit |
| * DST specification mismatches what would be in effect at the time. */ |
| |
| void __secs_to_zone(long long t, |
| int local, |
| int* isdst, |
| long* offset, |
| long* oppoff, |
| const char** zonename) { |
| LOCK(lock); |
| |
| do_tzset(); |
| |
| if (zi) { |
| size_t alt, i = scan_trans(t, local, &alt); |
| if (i != -1) { |
| *isdst = types[6 * i + 4]; |
| *offset = (int32_t)zi_read32(types + 6 * i); |
| *zonename = (const char*)abbrevs + types[6 * i + 5]; |
| if (oppoff) |
| *oppoff = (int32_t)zi_read32(types + 6 * alt); |
| UNLOCK(lock); |
| return; |
| } |
| } |
| |
| if (!__daylight) |
| goto std; |
| |
| /* FIXME: may be broken if DST changes right at year boundary? |
| * Also, this could be more efficient.*/ |
| long long y = t / 31556952 + 70; |
| while (__year_to_secs(y, 0) > t) |
| y--; |
| while (__year_to_secs(y + 1, 0) < t) |
| y++; |
| |
| long long t0 = rule_to_secs(r0, y); |
| long long t1 = rule_to_secs(r1, y); |
| |
| if (t0 < t1) { |
| if (!local) { |
| t0 += __timezone; |
| t1 += dst_off; |
| } |
| if (t >= t0 && t < t1) |
| goto dst; |
| goto std; |
| } else { |
| if (!local) { |
| t1 += __timezone; |
| t0 += dst_off; |
| } |
| if (t >= t1 && t < t0) |
| goto std; |
| goto dst; |
| } |
| std: |
| *isdst = 0; |
| *offset = -__timezone; |
| if (oppoff) |
| *oppoff = -dst_off; |
| *zonename = __tzname[0]; |
| UNLOCK(lock); |
| return; |
| dst: |
| *isdst = 1; |
| *offset = -dst_off; |
| if (oppoff) |
| *oppoff = -__timezone; |
| *zonename = __tzname[1]; |
| UNLOCK(lock); |
| } |
| |
| void __tzset() { |
| LOCK(lock); |
| do_tzset(); |
| UNLOCK(lock); |
| } |
| |
| weak_alias(__tzset, tzset); |
| |
| const char* __tm_to_tzname(const struct tm* tm) { |
| const void* p = tm->__tm_zone; |
| LOCK(lock); |
| do_tzset(); |
| if (p != __gmt && p != __tzname[0] && p != __tzname[1] && |
| (!zi || (uintptr_t)p - (uintptr_t)abbrevs >= abbrevs_end - abbrevs)) |
| p = ""; |
| UNLOCK(lock); |
| return p; |
| } |