1/*-------------------------------------------------------------------------
2 *
3 * pgtz.c
4 * Timezone Library Integration Functions
5 *
6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
7 *
8 * IDENTIFICATION
9 * src/timezone/pgtz.c
10 *
11 *-------------------------------------------------------------------------
12 */
13#include "postgres.h"
14
15#include <ctype.h>
16#include <fcntl.h>
17#include <sys/stat.h>
18#include <time.h>
19
20#include "datatype/timestamp.h"
21#include "miscadmin.h"
22#include "pgtz.h"
23#include "storage/fd.h"
24#include "utils/hsearch.h"
25
26
27/* Current session timezone (controlled by TimeZone GUC) */
28pg_tz *session_timezone = NULL;
29
30/* Current log timezone (controlled by log_timezone GUC) */
31pg_tz *log_timezone = NULL;
32
33
34static bool scan_directory_ci(const char *dirname,
35 const char *fname, int fnamelen,
36 char *canonname, int canonnamelen);
37
38
39/*
40 * Return full pathname of timezone data directory
41 */
42static const char *
43pg_TZDIR(void)
44{
45#ifndef SYSTEMTZDIR
46 /* normal case: timezone stuff is under our share dir */
47 static bool done_tzdir = false;
48 static char tzdir[MAXPGPATH];
49
50 if (done_tzdir)
51 return tzdir;
52
53 get_share_path(my_exec_path, tzdir);
54 strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
55
56 done_tzdir = true;
57 return tzdir;
58#else
59 /* we're configured to use system's timezone database */
60 return SYSTEMTZDIR;
61#endif
62}
63
64
65/*
66 * Given a timezone name, open() the timezone data file. Return the
67 * file descriptor if successful, -1 if not.
68 *
69 * The input name is searched for case-insensitively (we assume that the
70 * timezone database does not contain case-equivalent names).
71 *
72 * If "canonname" is not NULL, then on success the canonical spelling of the
73 * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
74 */
75int
76pg_open_tzfile(const char *name, char *canonname)
77{
78 const char *fname;
79 char fullname[MAXPGPATH];
80 int fullnamelen;
81 int orignamelen;
82
83 /* Initialize fullname with base name of tzdata directory */
84 strlcpy(fullname, pg_TZDIR(), sizeof(fullname));
85 orignamelen = fullnamelen = strlen(fullname);
86
87 if (fullnamelen + 1 + strlen(name) >= MAXPGPATH)
88 return -1; /* not gonna fit */
89
90 /*
91 * If the caller doesn't need the canonical spelling, first just try to
92 * open the name as-is. This can be expected to succeed if the given name
93 * is already case-correct, or if the filesystem is case-insensitive; and
94 * we don't need to distinguish those situations if we aren't tasked with
95 * reporting the canonical spelling.
96 */
97 if (canonname == NULL)
98 {
99 int result;
100
101 fullname[fullnamelen] = '/';
102 /* test above ensured this will fit: */
103 strcpy(fullname + fullnamelen + 1, name);
104 result = open(fullname, O_RDONLY | PG_BINARY, 0);
105 if (result >= 0)
106 return result;
107 /* If that didn't work, fall through to do it the hard way */
108 fullname[fullnamelen] = '\0';
109 }
110
111 /*
112 * Loop to split the given name into directory levels; for each level,
113 * search using scan_directory_ci().
114 */
115 fname = name;
116 for (;;)
117 {
118 const char *slashptr;
119 int fnamelen;
120
121 slashptr = strchr(fname, '/');
122 if (slashptr)
123 fnamelen = slashptr - fname;
124 else
125 fnamelen = strlen(fname);
126 if (!scan_directory_ci(fullname, fname, fnamelen,
127 fullname + fullnamelen + 1,
128 MAXPGPATH - fullnamelen - 1))
129 return -1;
130 fullname[fullnamelen++] = '/';
131 fullnamelen += strlen(fullname + fullnamelen);
132 if (slashptr)
133 fname = slashptr + 1;
134 else
135 break;
136 }
137
138 if (canonname)
139 strlcpy(canonname, fullname + orignamelen + 1, TZ_STRLEN_MAX + 1);
140
141 return open(fullname, O_RDONLY | PG_BINARY, 0);
142}
143
144
145/*
146 * Scan specified directory for a case-insensitive match to fname
147 * (of length fnamelen --- fname may not be null terminated!). If found,
148 * copy the actual filename into canonname and return true.
149 */
150static bool
151scan_directory_ci(const char *dirname, const char *fname, int fnamelen,
152 char *canonname, int canonnamelen)
153{
154 bool found = false;
155 DIR *dirdesc;
156 struct dirent *direntry;
157
158 dirdesc = AllocateDir(dirname);
159
160 while ((direntry = ReadDirExtended(dirdesc, dirname, LOG)) != NULL)
161 {
162 /*
163 * Ignore . and .., plus any other "hidden" files. This is a security
164 * measure to prevent access to files outside the timezone directory.
165 */
166 if (direntry->d_name[0] == '.')
167 continue;
168
169 if (strlen(direntry->d_name) == fnamelen &&
170 pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
171 {
172 /* Found our match */
173 strlcpy(canonname, direntry->d_name, canonnamelen);
174 found = true;
175 break;
176 }
177 }
178
179 FreeDir(dirdesc);
180
181 return found;
182}
183
184
185/*
186 * We keep loaded timezones in a hashtable so we don't have to
187 * load and parse the TZ definition file every time one is selected.
188 * Because we want timezone names to be found case-insensitively,
189 * the hash key is the uppercased name of the zone.
190 */
191typedef struct
192{
193 /* tznameupper contains the all-upper-case name of the timezone */
194 char tznameupper[TZ_STRLEN_MAX + 1];
195 pg_tz tz;
196} pg_tz_cache;
197
198static HTAB *timezone_cache = NULL;
199
200
201static bool
202init_timezone_hashtable(void)
203{
204 HASHCTL hash_ctl;
205
206 MemSet(&hash_ctl, 0, sizeof(hash_ctl));
207
208 hash_ctl.keysize = TZ_STRLEN_MAX + 1;
209 hash_ctl.entrysize = sizeof(pg_tz_cache);
210
211 timezone_cache = hash_create("Timezones",
212 4,
213 &hash_ctl,
214 HASH_ELEM);
215 if (!timezone_cache)
216 return false;
217
218 return true;
219}
220
221/*
222 * Load a timezone from file or from cache.
223 * Does not verify that the timezone is acceptable!
224 *
225 * "GMT" is always interpreted as the tzparse() definition, without attempting
226 * to load a definition from the filesystem. This has a number of benefits:
227 * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
228 * the bootstrap default timezone setting doesn't work (as could happen if
229 * the OS attempts to supply a leap-second-aware version of "GMT").
230 * 2. Because we aren't accessing the filesystem, we can safely initialize
231 * the "GMT" zone definition before my_exec_path is known.
232 * 3. It's quick enough that we don't waste much time when the bootstrap
233 * default timezone setting is later overridden from postgresql.conf.
234 */
235pg_tz *
236pg_tzset(const char *name)
237{
238 pg_tz_cache *tzp;
239 struct state tzstate;
240 char uppername[TZ_STRLEN_MAX + 1];
241 char canonname[TZ_STRLEN_MAX + 1];
242 char *p;
243
244 if (strlen(name) > TZ_STRLEN_MAX)
245 return NULL; /* not going to fit */
246
247 if (!timezone_cache)
248 if (!init_timezone_hashtable())
249 return NULL;
250
251 /*
252 * Upcase the given name to perform a case-insensitive hashtable search.
253 * (We could alternatively downcase it, but we prefer upcase so that we
254 * can get consistently upcased results from tzparse() in case the name is
255 * a POSIX-style timezone spec.)
256 */
257 p = uppername;
258 while (*name)
259 *p++ = pg_toupper((unsigned char) *name++);
260 *p = '\0';
261
262 tzp = (pg_tz_cache *) hash_search(timezone_cache,
263 uppername,
264 HASH_FIND,
265 NULL);
266 if (tzp)
267 {
268 /* Timezone found in cache, nothing more to do */
269 return &tzp->tz;
270 }
271
272 /*
273 * "GMT" is always sent to tzparse(), as per discussion above.
274 */
275 if (strcmp(uppername, "GMT") == 0)
276 {
277 if (!tzparse(uppername, &tzstate, true))
278 {
279 /* This really, really should not happen ... */
280 elog(ERROR, "could not initialize GMT time zone");
281 }
282 /* Use uppercase name as canonical */
283 strcpy(canonname, uppername);
284 }
285 else if (tzload(uppername, canonname, &tzstate, true) != 0)
286 {
287 if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false))
288 {
289 /* Unknown timezone. Fail our call instead of loading GMT! */
290 return NULL;
291 }
292 /* For POSIX timezone specs, use uppercase name as canonical */
293 strcpy(canonname, uppername);
294 }
295
296 /* Save timezone in the cache */
297 tzp = (pg_tz_cache *) hash_search(timezone_cache,
298 uppername,
299 HASH_ENTER,
300 NULL);
301
302 /* hash_search already copied uppername into the hash key */
303 strcpy(tzp->tz.TZname, canonname);
304 memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
305
306 return &tzp->tz;
307}
308
309/*
310 * Load a fixed-GMT-offset timezone.
311 * This is used for SQL-spec SET TIME ZONE INTERVAL 'foo' cases.
312 * It's otherwise equivalent to pg_tzset().
313 *
314 * The GMT offset is specified in seconds, positive values meaning west of
315 * Greenwich (ie, POSIX not ISO sign convention). However, we use ISO
316 * sign convention in the displayable abbreviation for the zone.
317 *
318 * Caution: this can fail (return NULL) if the specified offset is outside
319 * the range allowed by the zic library.
320 */
321pg_tz *
322pg_tzset_offset(long gmtoffset)
323{
324 long absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset;
325 char offsetstr[64];
326 char tzname[128];
327
328 snprintf(offsetstr, sizeof(offsetstr),
329 "%02ld", absoffset / SECS_PER_HOUR);
330 absoffset %= SECS_PER_HOUR;
331 if (absoffset != 0)
332 {
333 snprintf(offsetstr + strlen(offsetstr),
334 sizeof(offsetstr) - strlen(offsetstr),
335 ":%02ld", absoffset / SECS_PER_MINUTE);
336 absoffset %= SECS_PER_MINUTE;
337 if (absoffset != 0)
338 snprintf(offsetstr + strlen(offsetstr),
339 sizeof(offsetstr) - strlen(offsetstr),
340 ":%02ld", absoffset);
341 }
342 if (gmtoffset > 0)
343 snprintf(tzname, sizeof(tzname), "<-%s>+%s",
344 offsetstr, offsetstr);
345 else
346 snprintf(tzname, sizeof(tzname), "<+%s>-%s",
347 offsetstr, offsetstr);
348
349 return pg_tzset(tzname);
350}
351
352
353/*
354 * Initialize timezone library
355 *
356 * This is called before GUC variable initialization begins. Its purpose
357 * is to ensure that log_timezone has a valid value before any logging GUC
358 * variables could become set to values that require elog.c to provide
359 * timestamps (e.g., log_line_prefix). We may as well initialize
360 * session_timestamp to something valid, too.
361 */
362void
363pg_timezone_initialize(void)
364{
365 /*
366 * We may not yet know where PGSHAREDIR is (in particular this is true in
367 * an EXEC_BACKEND subprocess). So use "GMT", which pg_tzset forces to be
368 * interpreted without reference to the filesystem. This corresponds to
369 * the bootstrap default for these variables in guc.c, although in
370 * principle it could be different.
371 */
372 session_timezone = pg_tzset("GMT");
373 log_timezone = session_timezone;
374}
375
376
377/*
378 * Functions to enumerate available timezones
379 *
380 * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
381 * structure, so the data is only valid up to the next call.
382 *
383 * All data is allocated using palloc in the current context.
384 */
385#define MAX_TZDIR_DEPTH 10
386
387struct pg_tzenum
388{
389 int baselen;
390 int depth;
391 DIR *dirdesc[MAX_TZDIR_DEPTH];
392 char *dirname[MAX_TZDIR_DEPTH];
393 struct pg_tz tz;
394};
395
396/* typedef pg_tzenum is declared in pgtime.h */
397
398pg_tzenum *
399pg_tzenumerate_start(void)
400{
401 pg_tzenum *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum));
402 char *startdir = pstrdup(pg_TZDIR());
403
404 ret->baselen = strlen(startdir) + 1;
405 ret->depth = 0;
406 ret->dirname[0] = startdir;
407 ret->dirdesc[0] = AllocateDir(startdir);
408 if (!ret->dirdesc[0])
409 ereport(ERROR,
410 (errcode_for_file_access(),
411 errmsg("could not open directory \"%s\": %m", startdir)));
412 return ret;
413}
414
415void
416pg_tzenumerate_end(pg_tzenum *dir)
417{
418 while (dir->depth >= 0)
419 {
420 FreeDir(dir->dirdesc[dir->depth]);
421 pfree(dir->dirname[dir->depth]);
422 dir->depth--;
423 }
424 pfree(dir);
425}
426
427pg_tz *
428pg_tzenumerate_next(pg_tzenum *dir)
429{
430 while (dir->depth >= 0)
431 {
432 struct dirent *direntry;
433 char fullname[MAXPGPATH * 2];
434 struct stat statbuf;
435
436 direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
437
438 if (!direntry)
439 {
440 /* End of this directory */
441 FreeDir(dir->dirdesc[dir->depth]);
442 pfree(dir->dirname[dir->depth]);
443 dir->depth--;
444 continue;
445 }
446
447 if (direntry->d_name[0] == '.')
448 continue;
449
450 snprintf(fullname, sizeof(fullname), "%s/%s",
451 dir->dirname[dir->depth], direntry->d_name);
452 if (stat(fullname, &statbuf) != 0)
453 ereport(ERROR,
454 (errcode_for_file_access(),
455 errmsg("could not stat \"%s\": %m", fullname)));
456
457 if (S_ISDIR(statbuf.st_mode))
458 {
459 /* Step into the subdirectory */
460 if (dir->depth >= MAX_TZDIR_DEPTH - 1)
461 ereport(ERROR,
462 (errmsg_internal("timezone directory stack overflow")));
463 dir->depth++;
464 dir->dirname[dir->depth] = pstrdup(fullname);
465 dir->dirdesc[dir->depth] = AllocateDir(fullname);
466 if (!dir->dirdesc[dir->depth])
467 ereport(ERROR,
468 (errcode_for_file_access(),
469 errmsg("could not open directory \"%s\": %m",
470 fullname)));
471
472 /* Start over reading in the new directory */
473 continue;
474 }
475
476 /*
477 * Load this timezone using tzload() not pg_tzset(), so we don't fill
478 * the cache. Also, don't ask for the canonical spelling: we already
479 * know it, and pg_open_tzfile's way of finding it out is pretty
480 * inefficient.
481 */
482 if (tzload(fullname + dir->baselen, NULL, &dir->tz.state, true) != 0)
483 {
484 /* Zone could not be loaded, ignore it */
485 continue;
486 }
487
488 if (!pg_tz_acceptable(&dir->tz))
489 {
490 /* Ignore leap-second zones */
491 continue;
492 }
493
494 /* OK, return the canonical zone name spelling. */
495 strlcpy(dir->tz.TZname, fullname + dir->baselen,
496 sizeof(dir->tz.TZname));
497
498 /* Timezone loaded OK. */
499 return &dir->tz;
500 }
501
502 /* Nothing more found */
503 return NULL;
504}
505