#include "time_impl.h" #include #include #include #include #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]!='>' && 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>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 (a == n-1) return -1; if (a == 0) { x = zi_read32(trans + (a<>(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; }