/* * Load a timezone from file or from cache. * Does not verify that the timezone is acceptable! */ struct pg_tz * pg_tzset(const char *name) { pg_tz_cache *tzp; struct state tzstate; char uppername[TZ_STRLEN_MAX + 1]; char canonname[TZ_STRLEN_MAX + 1]; char *p; if (strlen(name) > TZ_STRLEN_MAX) return NULL; /* not going to fit */ if (!timezone_cache) if (!init_timezone_hashtable()) return NULL; /* * Upcase the given name to perform a case-insensitive hashtable search. * (We could alternatively downcase it, but we prefer upcase so that we * can get consistently upcased results from tzparse() in case the name is * a POSIX-style timezone spec.) */ p = uppername; while (*name) *p++ = pg_toupper((unsigned char) *name++); *p = '\0'; tzp = (pg_tz_cache *) hash_search(timezone_cache, uppername, HASH_FIND, NULL); if (tzp) { /* Timezone found in cache, nothing more to do */ return &tzp->tz; } if (tzload(uppername, canonname, &tzstate, TRUE) != 0) { if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0) { /* Unknown timezone. Fail our call instead of loading GMT! */ return NULL; } /* For POSIX timezone specs, use uppercase name as canonical */ strcpy(canonname, uppername); } /* Save timezone in the cache */ tzp = (pg_tz_cache *) hash_search(timezone_cache, uppername, HASH_ENTER, NULL); /* hash_search already copied uppername into the hash key */ strcpy(tzp->tz.TZname, canonname); memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate)); return &tzp->tz; }
/* * Load a timezone definition. * Does not verify that the timezone is acceptable! * * This corresponds to the backend's pg_tzset(), except that we only support * one loaded timezone at a time. */ static pg_tz * pg_load_tz(const char *name) { static pg_tz tz; if (strlen(name) > TZ_STRLEN_MAX) return NULL; /* not going to fit */ /* * "GMT" is always sent to tzparse(); see comments for pg_tzset(). */ if (strcmp(name, "GMT") == 0) { if (!tzparse(name, &tz.state, true)) { /* This really, really should not happen ... */ return NULL; } } else if (tzload(name, NULL, &tz.state, true) != 0) { if (name[0] == ':' || !tzparse(name, &tz.state, false)) { return NULL; /* unknown timezone */ } } strcpy(tz.TZname, name); return &tz; }
/* * Load a timezone from file or from cache. * Does not verify that the timezone is acceptable! */ struct pg_tz * pg_tzset(const char *name) { pg_tz *tzp; pg_tz tz; if (strlen(name) > TZ_STRLEN_MAX) return NULL; /* not going to fit */ if (!timezone_cache) if (!init_timezone_hashtable()) return NULL; tzp = (pg_tz *) hash_search(timezone_cache, name, HASH_FIND, NULL); if (tzp) { /* Timezone found in cache, nothing more to do */ return tzp; } if (tzload(name, &tz.state) != 0) { if (name[0] == ':' || tzparse(name, &tz.state, FALSE) != 0) { /* Unknown timezone. Fail our call instead of loading GMT! */ return NULL; } } strcpy(tz.TZname, name); /* Save timezone in the cache */ tzp = hash_search(timezone_cache, name, HASH_ENTER, NULL); strcpy(tzp->TZname, tz.TZname); memcpy(&tzp->state, &tz.state, sizeof(tz.state)); return tzp; }
static void tzset_basic(int rdlocked) { const char * name; name = getenv("TZ"); if (name == NULL) { tzsetwall_basic(rdlocked); return; } if (!rdlocked) _RWLOCK_RDLOCK(&lcl_rwlock); if (lcl_is_set > 0 && strcmp(lcl_TZname, name) == 0) { if (!rdlocked) _RWLOCK_UNLOCK(&lcl_rwlock); return; } _RWLOCK_UNLOCK(&lcl_rwlock); _RWLOCK_WRLOCK(&lcl_rwlock); lcl_is_set = strlen(name) < sizeof lcl_TZname; if (lcl_is_set) strcpy(lcl_TZname, name); if (*name == '\0') { /* ** User wants it fast rather than right. */ lclptr->leapcnt = 0; /* so, we're off a little */ lclptr->timecnt = 0; lclptr->typecnt = 0; lclptr->ttis[0].tt_isdst = 0; lclptr->ttis[0].tt_gmtoff = 0; lclptr->ttis[0].tt_abbrind = 0; strcpy(lclptr->chars, gmt); } else if (tzload(name, lclptr, TRUE) != 0) if (name[0] == ':' || tzparse(name, lclptr, FALSE) != 0) gmtload(lclptr); settzname(); _RWLOCK_UNLOCK(&lcl_rwlock); if (rdlocked) _RWLOCK_RDLOCK(&lcl_rwlock); }
static void tzsetwall_basic(int rdlocked) { if (!rdlocked) _RWLOCK_RDLOCK(&lcl_rwlock); if (lcl_is_set < 0) { if (!rdlocked) _RWLOCK_UNLOCK(&lcl_rwlock); return; } _RWLOCK_UNLOCK(&lcl_rwlock); _RWLOCK_WRLOCK(&lcl_rwlock); lcl_is_set = -1; if (tzload(NULL, lclptr, TRUE) != 0) gmtload(lclptr); settzname(); _RWLOCK_UNLOCK(&lcl_rwlock); if (rdlocked) _RWLOCK_RDLOCK(&lcl_rwlock); }
static void gmtload(struct state * sp) { if (tzload(gmt, NULL, sp, TRUE) != 0) (void) tzparse(gmt, sp, TRUE); }
int tzparse(const char *name, struct state * sp, int lastditch) { const char *stdname; const char *dstname = NULL; size_t stdlen; size_t dstlen; long stdoffset; long dstoffset; pg_time_t *atp; unsigned char *typep; char *cp; int load_result; stdname = name; if (lastditch) { stdlen = strlen(name); /* length of standard zone name */ name += stdlen; if (stdlen >= sizeof sp->chars) stdlen = (sizeof sp->chars) - 1; stdoffset = 0; /* * Unlike the original zic library, do NOT invoke tzload() here; we * can't assume pg_open_tzfile() is sane yet, and we don't care about * leap seconds anyway. */ load_result = -1; } else { if (*name == '<') { name++; stdname = name; name = getqzname(name, '>'); if (*name != '>') return (-1); stdlen = name - stdname; name++; } else { name = getzname(name); stdlen = name - stdname; } if (*name == '\0') return -1; name = getoffset(name, &stdoffset); if (name == NULL) return -1; load_result = tzload(TZDEFRULES, NULL, sp, FALSE); } if (load_result != 0) sp->leapcnt = 0; /* so, we're off a little */ if (*name != '\0') { if (*name == '<') { dstname = ++name; name = getqzname(name, '>'); if (*name != '>') return -1; dstlen = name - dstname; name++; } else { dstname = name; name = getzname(name); dstlen = name - dstname; /* length of DST zone name */ } if (*name != '\0' && *name != ',' && *name != ';') { name = getoffset(name, &dstoffset); if (name == NULL) return -1; } else dstoffset = stdoffset - SECSPERHOUR; if (*name == '\0' && load_result != 0) name = TZDEFRULESTRING; if (*name == ',' || *name == ';') { struct rule start; struct rule end; int year; pg_time_t janfirst; pg_time_t starttime; pg_time_t endtime; ++name; if ((name = getrule(name, &start)) == NULL) return -1; if (*name++ != ',') return -1; if ((name = getrule(name, &end)) == NULL) return -1; if (*name != '\0') return -1; sp->typecnt = 2; /* standard time and DST */ /* * Two transitions per year, from EPOCH_YEAR forward. */ sp->ttis[0].tt_gmtoff = -dstoffset; sp->ttis[0].tt_isdst = 1; sp->ttis[0].tt_abbrind = stdlen + 1; sp->ttis[1].tt_gmtoff = -stdoffset; sp->ttis[1].tt_isdst = 0; sp->ttis[1].tt_abbrind = 0; atp = sp->ats; typep = sp->types; janfirst = 0; sp->timecnt = 0; for (year = EPOCH_YEAR; sp->timecnt + 2 <= TZ_MAX_TIMES; ++year) { pg_time_t newfirst; starttime = transtime(janfirst, year, &start, stdoffset); endtime = transtime(janfirst, year, &end, dstoffset); if (starttime > endtime) { *atp++ = endtime; *typep++ = 1; /* DST ends */ *atp++ = starttime; *typep++ = 0; /* DST begins */ } else { *atp++ = starttime; *typep++ = 0; /* DST begins */ *atp++ = endtime; *typep++ = 1; /* DST ends */ } sp->timecnt += 2; newfirst = janfirst; newfirst += year_lengths[isleap(year)] * SECSPERDAY; if (newfirst <= janfirst) break; janfirst = newfirst; } } else { long theirstdoffset; long theirdstoffset; long theiroffset; int isdst; int i; int j; if (*name != '\0') return -1; /* * Initial values of theirstdoffset and theirdstoffset. */ theirstdoffset = 0; for (i = 0; i < sp->timecnt; ++i) { j = sp->types[i]; if (!sp->ttis[j].tt_isdst) { theirstdoffset = -sp->ttis[j].tt_gmtoff; break; } } theirdstoffset = 0; for (i = 0; i < sp->timecnt; ++i) { j = sp->types[i]; if (sp->ttis[j].tt_isdst) { theirdstoffset = -sp->ttis[j].tt_gmtoff; break; } } /* * Initially we're assumed to be in standard time. */ isdst = FALSE; theiroffset = theirstdoffset; /* * Now juggle transition times and types tracking offsets as you * do. */ for (i = 0; i < sp->timecnt; ++i) { j = sp->types[i]; sp->types[i] = sp->ttis[j].tt_isdst; if (sp->ttis[j].tt_ttisgmt) { /* No adjustment to transition time */ } else { /* * If summer time is in effect, and the transition time * was not specified as standard time, add the summer time * offset to the transition time; otherwise, add the * standard time offset to the transition time. */ /* * Transitions from DST to DDST will effectively disappear * since POSIX provides for only one DST offset. */ if (isdst && !sp->ttis[j].tt_ttisstd) { sp->ats[i] += dstoffset - theirdstoffset; } else { sp->ats[i] += stdoffset - theirstdoffset; } } theiroffset = -sp->ttis[j].tt_gmtoff; if (sp->ttis[j].tt_isdst) theirdstoffset = theiroffset; else theirstdoffset = theiroffset; } /* * Finally, fill in ttis. ttisstd and ttisgmt need not be handled. */ sp->ttis[0].tt_gmtoff = -stdoffset; sp->ttis[0].tt_isdst = FALSE; sp->ttis[0].tt_abbrind = 0; sp->ttis[1].tt_gmtoff = -dstoffset; sp->ttis[1].tt_isdst = TRUE; sp->ttis[1].tt_abbrind = stdlen + 1; sp->typecnt = 2; } } else { dstlen = 0; sp->typecnt = 1; /* only standard time */ sp->timecnt = 0; sp->ttis[0].tt_gmtoff = -stdoffset; sp->ttis[0].tt_isdst = 0; sp->ttis[0].tt_abbrind = 0; } sp->charcnt = stdlen + 1; if (dstlen != 0) sp->charcnt += dstlen + 1; if ((size_t) sp->charcnt > sizeof sp->chars) return -1; cp = sp->chars; (void) strncpy(cp, stdname, stdlen); cp += stdlen; *cp++ = '\0'; if (dstlen != 0) { (void) strncpy(cp, dstname, dstlen); *(cp + dstlen) = '\0'; } return 0; }
pg_tz * pg_tzenumerate_next(pg_tzenum *dir) { while (dir->depth >= 0) { struct dirent *direntry; char fullname[MAXPGPATH * 2]; struct stat statbuf; direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]); if (!direntry) { /* End of this directory */ FreeDir(dir->dirdesc[dir->depth]); pfree(dir->dirname[dir->depth]); dir->depth--; continue; } if (direntry->d_name[0] == '.') continue; snprintf(fullname, sizeof(fullname), "%s/%s", dir->dirname[dir->depth], direntry->d_name); if (stat(fullname, &statbuf) != 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat \"%s\": %m", fullname))); if (S_ISDIR(statbuf.st_mode)) { /* Step into the subdirectory */ if (dir->depth >= MAX_TZDIR_DEPTH - 1) ereport(ERROR, (errmsg_internal("timezone directory stack overflow"))); dir->depth++; dir->dirname[dir->depth] = pstrdup(fullname); dir->dirdesc[dir->depth] = AllocateDir(fullname); if (!dir->dirdesc[dir->depth]) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", fullname))); /* Start over reading in the new directory */ continue; } /* * Load this timezone using tzload() not pg_tzset(), so we don't fill * the cache */ if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state, true) != 0) { /* Zone could not be loaded, ignore it */ continue; } if (!pg_tz_acceptable(&dir->tz)) { /* Ignore leap-second zones */ continue; } /* Timezone loaded OK. */ return &dir->tz; } /* Nothing more found */ return NULL; }
/* * See how well a specific timezone setting matches the system behavior * * We score a timezone setting according to the number of test times it * matches. (The test times are ordered later-to-earlier, but this routine * doesn't actually know that; it just scans until the first non-match.) * * We return -1 for a completely unusable setting; this is worse than the * score of zero for a setting that works but matches not even the first * test time. */ static int score_timezone(const char *tzname, struct tztry * tt) { int i; pg_time_t pgtt; struct tm* systm; struct pg_tm* pgtm; char cbuf[TZ_STRLEN_MAX + 1]; pg_tz tz; /* * Load timezone directly. Don't use pg_tzset, because we don't want all * timezones loaded in the cache at startup. */ if (tzload(tzname, NULL, &tz.state, TRUE) != 0) { if (tzname[0] == ':' || tzparse(tzname, &tz.state, FALSE) != 0) { return -1; /* can't handle the TZ name at all */ } } /* Reject if leap seconds involved */ if (!tz_acceptable(&tz)) { elog(DEBUG4, "Reject TZ \"%s\": uses leap seconds", tzname); return -1; } /* Check for match at all the test times */ for (i = 0; i < tt->n_test_times; i++) { pgtt = (pg_time_t) (tt->test_times[i]); pgtm = pg_localtime(&pgtt, &tz); if (!pgtm) return -1; /* probably shouldn't happen */ systm = localtime(&(tt->test_times[i])); if (!systm) { elog(DEBUG4, "TZ \"%s\" scores %d:" " at %ld %04d-%02d-%02d %02d:%02d:%02d %s, system had no data", tzname, i, (long) pgtt, pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday, pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec, pgtm->tm_isdst ? "dst" : "std"); return i; } if (!compare_tm(systm, pgtm)) { elog(DEBUG4, "TZ \"%s\" scores %d:" " at %ld %04d-%02d-%02d %02d:%02d:%02d %s" " versus %04d-%02d-%02d %02d:%02d:%02d %s", tzname, i, (long) pgtt, pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday, pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec, pgtm->tm_isdst ? "dst" : "std", systm->tm_year + 1900, systm->tm_mon + 1, systm->tm_mday, systm->tm_hour, systm->tm_min, systm->tm_sec, systm->tm_isdst ? "dst" : "std"); return i; } if (systm->tm_isdst >= 0) { /* Check match of zone names, too */ if (pgtm->tm_zone == NULL) return -1; /* probably shouldn't happen */ memset(cbuf, 0, sizeof(cbuf)); strftime(cbuf, sizeof(cbuf) - 1, "%Z", systm); /* zone abbr */ if (strcmp(cbuf, pgtm->tm_zone) != 0) { elog(DEBUG4, "TZ \"%s\" scores %d: at %ld \"%s\" versus \"%s\"", tzname, i, (long) pgtt, pgtm->tm_zone, cbuf); return i; } } } elog(DEBUG4, "TZ \"%s\" gets max score %d", tzname, i); return i; }
static void gmtload(struct state * const sp) { if (tzload(gmt, sp, TRUE) != 0) tzparse(gmt, sp, TRUE); }