#define _ALL_SOURCE #include #include #include #include #include #include #include #include #include /* ntoh* */ struct dhcptzinfo { int64_t next_transition; int32_t tzoffset[2]; /* Current and next */ char posixstr[256]; }; struct tzhead { uint32_t tzh_magic; char tzh_version; char tzh_reserved[15]; uint32_t tzh_ttisgmtcnt; uint32_t tzh_ttisstdcnt; uint32_t tzh_leapcnt; uint32_t tzh_timecnt; uint32_t tzh_typecnt; uint32_t tzh_charcnt; }; #define TZ_MAGIC (('T' << 24)+('Z' << 16)+('i' << 8)+('f')) struct ttinfo { int32_t tt_gmtoff; int8_t tt_isdst; uint8_t tt_abbrind; }; #ifdef ntohq /* We're good */ #elif defined(be64toh) # define ntohq(x) be64toh(x) #else static uint64_t ntohq(uint64_t netquad) { union { uint64_t q; uint32_t l[2]; } v; v.q = netquad; return ((uint64_t)ntohl(v.l[0]) << 32) + ntohl(v.l[1]); } #endif static int64_t gettime(const void *base, size_t idx, bool newfmt) { if (newfmt) { const int64_t *p = (const int64_t *)base; return (int64_t)ntohq(p[idx]); } else { const int32_t *p = (const int32_t *)base; return (int64_t)ntohl(p[idx]); } } static void ntohtzhead(struct tzhead *tzh) { uint32_t *p; int i; p = &tzh->tzh_ttisgmtcnt; for (i = 0; i < 6; i++) p[i] = ntohl(p[i]); } static const char zonedir[] = "/usr/share/zoneinfo"; const struct dhcptzinfo *get_timezone_info(const char *zonename, time_t now) { struct tzhead tzh; static struct dhcptzinfo tzinfo; struct ttinfo tti; char *path; bool newfmt; size_t datalen; int32_t lo, hi; int64_t t0, t1; size_t n; int i; FILE *f = NULL; char *tzdata = NULL; const char *tzp; uint8_t type; bool valid = false; memset(&tzinfo, 0, sizeof tzinfo); path = malloc(strlen(zonedir)+strlen(zonename)+1); if (!path) goto done; sprintf(path, "%s/%s", zonedir, zonename); f = fopen(path, "rb"); free(path); if (!f) goto done; if (fread(&tzh, 1, sizeof tzh, f) != sizeof tzh) goto done; ntohtzhead(&tzh); if (tzh.tzh_magic != htonl(TZ_MAGIC)) goto done; newfmt = false; datalen = tzh.tzh_timecnt * 5 + tzh.tzh_typecnt * 6 + tzh.tzh_charcnt + tzh.tzh_leapcnt * 8 + tzh.tzh_ttisstdcnt + tzh.tzh_ttisgmtcnt; /* * Hopefully future versions will be backwards compatible, or * change the "TZif" magic to something else. */ if (tzh.tzh_version >= '2') { struct tzhead tzh2; if (fseek(f, datalen, SEEK_CUR)) goto fail; if (fread(&tzh2, 1, sizeof tzh2, f) != sizeof tzh2) goto fail; ntohtzhead(&tzh2); if (tzh2.tzh_magic != htonl(TZ_MAGIC)) goto fail; if (tzh2.tzh_version != tzh.tzh_version) goto fail; tzh = tzh2; datalen = tzh.tzh_timecnt * 9 + tzh.tzh_typecnt * 6 + tzh.tzh_charcnt + tzh.tzh_leapcnt * 12 + tzh.tzh_ttisstdcnt + tzh.tzh_ttisgmtcnt; newfmt = true; fail: if (!newfmt) fseek(f, sizeof tzh, SEEK_SET); } #ifdef DEBUG printf("Format: %s\n" "timecnt: %u\n" "typecnt: %u\n" "datalen: %zu\n", newfmt ? "new" : "old", tzh.tzh_timecnt, tzh.tzh_typecnt, datalen); #endif tzdata = malloc(datalen); if (!tzdata) goto done; if (fread(tzdata, 1, datalen, f) != datalen) goto done; /* Open-coded binary search as it is likely to be optimizable */ tzp = tzdata; lo = 0; hi = tzh.tzh_timecnt - 1; for (;;) { n = (lo + hi) >> 1; t0 = n > 0 ? gettime(tzp, n, newfmt) : INT64_MIN; t1 = (n < tzh.tzh_timecnt - 1) ? gettime(tzp, n+1, newfmt) : INT64_MAX; if (now < t0) hi = n-1; else if (now >= t1) lo = n+1; else break; } #ifdef DEBUG printf("n = %zu, range = [%"PRId64",%"PRId64")\n", n, t0, t1); #endif tzinfo.next_transition = t1; tzp += tzh.tzh_timecnt * (newfmt ? 8 : 4); for (i = 0; i < 2; i++) { size_t ni = n+i; type = (ni >= tzh.tzh_timecnt) ? tzp[tzh.tzh_timecnt-1] : tzp[ni]; if (type > tzh.tzh_typecnt) type = 0; memcpy(&tti, tzp + tzh.tzh_timecnt + (type*6), 6); tzinfo.tzoffset[i] = ntohl(tti.tt_gmtoff); } valid = true; /* POSIX string present? */ if (newfmt && getc(f) == '\n') { char *ep; fgets(tzinfo.posixstr, sizeof tzinfo.posixstr, f); ep = strchr(tzinfo.posixstr, '\n'); if (ep) *ep = '\0'; } done: if (tzdata) free((void *)tzdata); if (f) fclose(f); return valid ? &tzinfo : NULL; } static const char *print_time(int64_t t) { const struct tm *tm; static char buf[256]; time_t tt = t; if (t == INT64_MIN) return "-inf"; if (t == INT64_MAX) return "+inf"; if (t != (int64_t)tt) return "???"; tm = gmtime(&tt); strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S UTC", tm); return buf; } int main(int argc, char *argv[]) { int i; int err = 0; int64_t now = strtoimax(argv[1], NULL, 0); const char *zonename; const struct dhcptzinfo *tzinfo; printf("now = %"PRId64"\n", (uint64_t)now); for (i = 2; i < argc; i++) { zonename = argv[i]; if (zonename[0] == ':') zonename++; tzinfo = get_timezone_info(zonename, now); if (tzinfo) { printf("Timezone name = %s\n", zonename); printf("Timezone offset = %+"PRId32" s\n", tzinfo->tzoffset[0]); printf("Next transition = %"PRId64" (%s) (%"PRId64" s from now)\n", tzinfo->next_transition, print_time(tzinfo->next_transition), tzinfo->next_transition - now); printf("Next offset = %+"PRId32" s\n", tzinfo->tzoffset[1]); printf("POSIX string = %s\n", tzinfo->posixstr); } else { printf("Nonexistent or invalid timezone: %s\n", zonename); } } return err; }