1 /*-------------------------------------------------------------------------
4 * Timezone Library Integration Functions
6 * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
11 *-------------------------------------------------------------------------
20 #include "miscadmin.h"
22 #include "storage/fd.h"
23 #include "utils/hsearch.h"
26 /* Current session timezone (controlled by TimeZone GUC) */
27 pg_tz *session_timezone = NULL;
29 /* Current log timezone (controlled by log_timezone GUC) */
30 pg_tz *log_timezone = NULL;
33 static bool scan_directory_ci(const char *dirname,
34 const char *fname, int fnamelen,
35 char *canonname, int canonnamelen);
39 * Return full pathname of timezone data directory
45 /* normal case: timezone stuff is under our share dir */
46 static bool done_tzdir = false;
47 static char tzdir[MAXPGPATH];
52 get_share_path(my_exec_path, tzdir);
53 strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
58 /* we're configured to use system's timezone database */
65 * Given a timezone name, open() the timezone data file. Return the
66 * file descriptor if successful, -1 if not.
68 * The input name is searched for case-insensitively (we assume that the
69 * timezone database does not contain case-equivalent names).
71 * If "canonname" is not NULL, then on success the canonical spelling of the
72 * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
75 pg_open_tzfile(const char *name, char *canonname)
78 char fullname[MAXPGPATH];
83 * Loop to split the given name into directory levels; for each level,
84 * search using scan_directory_ci().
86 strlcpy(fullname, pg_TZDIR(), sizeof(fullname));
87 orignamelen = fullnamelen = strlen(fullname);
94 slashptr = strchr(fname, '/');
96 fnamelen = slashptr - fname;
98 fnamelen = strlen(fname);
99 if (fullnamelen + 1 + fnamelen >= MAXPGPATH)
100 return -1; /* not gonna fit */
101 if (!scan_directory_ci(fullname, fname, fnamelen,
102 fullname + fullnamelen + 1,
103 MAXPGPATH - fullnamelen - 1))
105 fullname[fullnamelen++] = '/';
106 fullnamelen += strlen(fullname + fullnamelen);
108 fname = slashptr + 1;
114 strlcpy(canonname, fullname + orignamelen + 1, TZ_STRLEN_MAX + 1);
116 return open(fullname, O_RDONLY | PG_BINARY, 0);
121 * Scan specified directory for a case-insensitive match to fname
122 * (of length fnamelen --- fname may not be null terminated!). If found,
123 * copy the actual filename into canonname and return true.
126 scan_directory_ci(const char *dirname, const char *fname, int fnamelen,
127 char *canonname, int canonnamelen)
131 struct dirent *direntry;
133 dirdesc = AllocateDir(dirname);
137 (errcode_for_file_access(),
138 errmsg("could not open directory \"%s\": %m", dirname)));
142 while ((direntry = ReadDir(dirdesc, dirname)) != NULL)
145 * Ignore . and .., plus any other "hidden" files. This is a security
146 * measure to prevent access to files outside the timezone directory.
148 if (direntry->d_name[0] == '.')
151 if (strlen(direntry->d_name) == fnamelen &&
152 pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
154 /* Found our match */
155 strlcpy(canonname, direntry->d_name, canonnamelen);
168 * We keep loaded timezones in a hashtable so we don't have to
169 * load and parse the TZ definition file every time one is selected.
170 * Because we want timezone names to be found case-insensitively,
171 * the hash key is the uppercased name of the zone.
175 /* tznameupper contains the all-upper-case name of the timezone */
176 char tznameupper[TZ_STRLEN_MAX + 1];
180 static HTAB *timezone_cache = NULL;
184 init_timezone_hashtable(void)
188 MemSet(&hash_ctl, 0, sizeof(hash_ctl));
190 hash_ctl.keysize = TZ_STRLEN_MAX + 1;
191 hash_ctl.entrysize = sizeof(pg_tz_cache);
193 timezone_cache = hash_create("Timezones",
204 * Load a timezone from file or from cache.
205 * Does not verify that the timezone is acceptable!
207 * "GMT" is always interpreted as the tzparse() definition, without attempting
208 * to load a definition from the filesystem. This has a number of benefits:
209 * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
210 * the bootstrap default timezone setting doesn't work (as could happen if
211 * the OS attempts to supply a leap-second-aware version of "GMT").
212 * 2. Because we aren't accessing the filesystem, we can safely initialize
213 * the "GMT" zone definition before my_exec_path is known.
214 * 3. It's quick enough that we don't waste much time when the bootstrap
215 * default timezone setting is later overridden from postgresql.conf.
218 pg_tzset(const char *name)
221 struct state tzstate;
222 char uppername[TZ_STRLEN_MAX + 1];
223 char canonname[TZ_STRLEN_MAX + 1];
226 if (strlen(name) > TZ_STRLEN_MAX)
227 return NULL; /* not going to fit */
230 if (!init_timezone_hashtable())
234 * Upcase the given name to perform a case-insensitive hashtable search.
235 * (We could alternatively downcase it, but we prefer upcase so that we
236 * can get consistently upcased results from tzparse() in case the name is
237 * a POSIX-style timezone spec.)
241 *p++ = pg_toupper((unsigned char) *name++);
244 tzp = (pg_tz_cache *) hash_search(timezone_cache,
250 /* Timezone found in cache, nothing more to do */
255 * "GMT" is always sent to tzparse(), as per discussion above.
257 if (strcmp(uppername, "GMT") == 0)
259 if (!tzparse(uppername, &tzstate, true))
261 /* This really, really should not happen ... */
262 elog(ERROR, "could not initialize GMT time zone");
264 /* Use uppercase name as canonical */
265 strcpy(canonname, uppername);
267 else if (tzload(uppername, canonname, &tzstate, true) != 0)
269 if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false))
271 /* Unknown timezone. Fail our call instead of loading GMT! */
274 /* For POSIX timezone specs, use uppercase name as canonical */
275 strcpy(canonname, uppername);
278 /* Save timezone in the cache */
279 tzp = (pg_tz_cache *) hash_search(timezone_cache,
284 /* hash_search already copied uppername into the hash key */
285 strcpy(tzp->tz.TZname, canonname);
286 memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
292 * Load a fixed-GMT-offset timezone.
293 * This is used for SQL-spec SET TIME ZONE INTERVAL 'foo' cases.
294 * It's otherwise equivalent to pg_tzset().
296 * The GMT offset is specified in seconds, positive values meaning west of
297 * Greenwich (ie, POSIX not ISO sign convention). However, we use ISO
298 * sign convention in the displayable abbreviation for the zone.
300 * Caution: this can fail (return NULL) if the specified offset is outside
301 * the range allowed by the zic library.
304 pg_tzset_offset(long gmtoffset)
306 long absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset;
310 snprintf(offsetstr, sizeof(offsetstr),
311 "%02ld", absoffset / SECSPERHOUR);
312 absoffset %= SECSPERHOUR;
315 snprintf(offsetstr + strlen(offsetstr),
316 sizeof(offsetstr) - strlen(offsetstr),
317 ":%02ld", absoffset / SECSPERMIN);
318 absoffset %= SECSPERMIN;
320 snprintf(offsetstr + strlen(offsetstr),
321 sizeof(offsetstr) - strlen(offsetstr),
322 ":%02ld", absoffset);
325 snprintf(tzname, sizeof(tzname), "<-%s>+%s",
326 offsetstr, offsetstr);
328 snprintf(tzname, sizeof(tzname), "<+%s>-%s",
329 offsetstr, offsetstr);
331 return pg_tzset(tzname);
336 * Initialize timezone library
338 * This is called before GUC variable initialization begins. Its purpose
339 * is to ensure that log_timezone has a valid value before any logging GUC
340 * variables could become set to values that require elog.c to provide
341 * timestamps (e.g., log_line_prefix). We may as well initialize
342 * session_timestamp to something valid, too.
345 pg_timezone_initialize(void)
348 * We may not yet know where PGSHAREDIR is (in particular this is true in
349 * an EXEC_BACKEND subprocess). So use "GMT", which pg_tzset forces to be
350 * interpreted without reference to the filesystem. This corresponds to
351 * the bootstrap default for these variables in guc.c, although in
352 * principle it could be different.
354 session_timezone = pg_tzset("GMT");
355 log_timezone = session_timezone;
360 * Functions to enumerate available timezones
362 * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
363 * structure, so the data is only valid up to the next call.
365 * All data is allocated using palloc in the current context.
367 #define MAX_TZDIR_DEPTH 10
373 DIR *dirdesc[MAX_TZDIR_DEPTH];
374 char *dirname[MAX_TZDIR_DEPTH];
378 /* typedef pg_tzenum is declared in pgtime.h */
381 pg_tzenumerate_start(void)
383 pg_tzenum *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum));
384 char *startdir = pstrdup(pg_TZDIR());
386 ret->baselen = strlen(startdir) + 1;
388 ret->dirname[0] = startdir;
389 ret->dirdesc[0] = AllocateDir(startdir);
390 if (!ret->dirdesc[0])
392 (errcode_for_file_access(),
393 errmsg("could not open directory \"%s\": %m", startdir)));
398 pg_tzenumerate_end(pg_tzenum *dir)
400 while (dir->depth >= 0)
402 FreeDir(dir->dirdesc[dir->depth]);
403 pfree(dir->dirname[dir->depth]);
410 pg_tzenumerate_next(pg_tzenum *dir)
412 while (dir->depth >= 0)
414 struct dirent *direntry;
415 char fullname[MAXPGPATH];
418 direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
422 /* End of this directory */
423 FreeDir(dir->dirdesc[dir->depth]);
424 pfree(dir->dirname[dir->depth]);
429 if (direntry->d_name[0] == '.')
432 snprintf(fullname, MAXPGPATH, "%s/%s",
433 dir->dirname[dir->depth], direntry->d_name);
434 if (stat(fullname, &statbuf) != 0)
436 (errcode_for_file_access(),
437 errmsg("could not stat \"%s\": %m", fullname)));
439 if (S_ISDIR(statbuf.st_mode))
441 /* Step into the subdirectory */
442 if (dir->depth >= MAX_TZDIR_DEPTH - 1)
444 (errmsg_internal("timezone directory stack overflow")));
446 dir->dirname[dir->depth] = pstrdup(fullname);
447 dir->dirdesc[dir->depth] = AllocateDir(fullname);
448 if (!dir->dirdesc[dir->depth])
450 (errcode_for_file_access(),
451 errmsg("could not open directory \"%s\": %m",
454 /* Start over reading in the new directory */
459 * Load this timezone using tzload() not pg_tzset(), so we don't fill
462 if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state,
465 /* Zone could not be loaded, ignore it */
469 if (!pg_tz_acceptable(&dir->tz))
471 /* Ignore leap-second zones */
475 /* Timezone loaded OK. */
479 /* Nothing more found */