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