1 /*-------------------------------------------------------------------------
2 *
3 * pgtz.c
4 * Timezone Library Integration Functions
5 *
6 * Portions Copyright (c) 1996-2016, 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 *
pg_TZDIR(void)43 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 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 */
75 int
pg_open_tzfile(const char * name,char * canonname)76 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 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 */
150 static bool
scan_directory_ci(const char * dirname,const char * fname,int fnamelen,char * canonname,int canonnamelen)151 scan_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 if (!dirdesc)
160 {
161 ereport(LOG,
162 (errcode_for_file_access(),
163 errmsg("could not open directory \"%s\": %m", dirname)));
164 return false;
165 }
166
167 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 if (direntry->d_name[0] == '.')
174 continue;
175
176 if (strlen(direntry->d_name) == fnamelen &&
177 pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
178 {
179 /* Found our match */
180 strlcpy(canonname, direntry->d_name, canonnamelen);
181 found = true;
182 break;
183 }
184 }
185
186 FreeDir(dirdesc);
187
188 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
init_timezone_hashtable(void)209 init_timezone_hashtable(void)
210 {
211 HASHCTL hash_ctl;
212
213 MemSet(&hash_ctl, 0, sizeof(hash_ctl));
214
215 hash_ctl.keysize = TZ_STRLEN_MAX + 1;
216 hash_ctl.entrysize = sizeof(pg_tz_cache);
217
218 timezone_cache = hash_create("Timezones",
219 4,
220 &hash_ctl,
221 HASH_ELEM);
222 if (!timezone_cache)
223 return false;
224
225 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 *
pg_tzset(const char * name)243 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 if (strlen(name) > TZ_STRLEN_MAX)
252 return NULL; /* not going to fit */
253
254 if (!timezone_cache)
255 if (!init_timezone_hashtable())
256 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 p = uppername;
265 while (*name)
266 *p++ = pg_toupper((unsigned char) *name++);
267 *p = '\0';
268
269 tzp = (pg_tz_cache *) hash_search(timezone_cache,
270 uppername,
271 HASH_FIND,
272 NULL);
273 if (tzp)
274 {
275 /* Timezone found in cache, nothing more to do */
276 return &tzp->tz;
277 }
278
279 /*
280 * "GMT" is always sent to tzparse(), as per discussion above.
281 */
282 if (strcmp(uppername, "GMT") == 0)
283 {
284 if (!tzparse(uppername, &tzstate, true))
285 {
286 /* This really, really should not happen ... */
287 elog(ERROR, "could not initialize GMT time zone");
288 }
289 /* Use uppercase name as canonical */
290 strcpy(canonname, uppername);
291 }
292 else if (tzload(uppername, canonname, &tzstate, true) != 0)
293 {
294 if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false))
295 {
296 /* Unknown timezone. Fail our call instead of loading GMT! */
297 return NULL;
298 }
299 /* For POSIX timezone specs, use uppercase name as canonical */
300 strcpy(canonname, uppername);
301 }
302
303 /* Save timezone in the cache */
304 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 strcpy(tzp->tz.TZname, canonname);
311 memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
312
313 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 *
pg_tzset_offset(long gmtoffset)329 pg_tzset_offset(long gmtoffset)
330 {
331 long absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset;
332 char offsetstr[64];
333 char tzname[128];
334
335 snprintf(offsetstr, sizeof(offsetstr),
336 "%02ld", absoffset / SECS_PER_HOUR);
337 absoffset %= SECS_PER_HOUR;
338 if (absoffset != 0)
339 {
340 snprintf(offsetstr + strlen(offsetstr),
341 sizeof(offsetstr) - strlen(offsetstr),
342 ":%02ld", absoffset / SECS_PER_MINUTE);
343 absoffset %= SECS_PER_MINUTE;
344 if (absoffset != 0)
345 snprintf(offsetstr + strlen(offsetstr),
346 sizeof(offsetstr) - strlen(offsetstr),
347 ":%02ld", absoffset);
348 }
349 if (gmtoffset > 0)
350 snprintf(tzname, sizeof(tzname), "<-%s>+%s",
351 offsetstr, offsetstr);
352 else
353 snprintf(tzname, sizeof(tzname), "<+%s>-%s",
354 offsetstr, offsetstr);
355
356 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_timezone to something valid, too.
368 */
369 void
pg_timezone_initialize(void)370 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 session_timezone = pg_tzset("GMT");
380 log_timezone = session_timezone;
381 }
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 *
pg_tzenumerate_start(void)406 pg_tzenumerate_start(void)
407 {
408 pg_tzenum *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum));
409 char *startdir = pstrdup(pg_TZDIR());
410
411 ret->baselen = strlen(startdir) + 1;
412 ret->depth = 0;
413 ret->dirname[0] = startdir;
414 ret->dirdesc[0] = AllocateDir(startdir);
415 if (!ret->dirdesc[0])
416 ereport(ERROR,
417 (errcode_for_file_access(),
418 errmsg("could not open directory \"%s\": %m", startdir)));
419 return ret;
420 }
421
422 void
pg_tzenumerate_end(pg_tzenum * dir)423 pg_tzenumerate_end(pg_tzenum *dir)
424 {
425 while (dir->depth >= 0)
426 {
427 FreeDir(dir->dirdesc[dir->depth]);
428 pfree(dir->dirname[dir->depth]);
429 dir->depth--;
430 }
431 pfree(dir);
432 }
433
434 pg_tz *
pg_tzenumerate_next(pg_tzenum * dir)435 pg_tzenumerate_next(pg_tzenum *dir)
436 {
437 while (dir->depth >= 0)
438 {
439 struct dirent *direntry;
440 char fullname[MAXPGPATH * 2];
441 struct stat statbuf;
442
443 direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
444
445 if (!direntry)
446 {
447 /* End of this directory */
448 FreeDir(dir->dirdesc[dir->depth]);
449 pfree(dir->dirname[dir->depth]);
450 dir->depth--;
451 continue;
452 }
453
454 if (direntry->d_name[0] == '.')
455 continue;
456
457 snprintf(fullname, sizeof(fullname), "%s/%s",
458 dir->dirname[dir->depth], direntry->d_name);
459 if (stat(fullname, &statbuf) != 0)
460 ereport(ERROR,
461 (errcode_for_file_access(),
462 errmsg("could not stat \"%s\": %m", fullname)));
463
464 if (S_ISDIR(statbuf.st_mode))
465 {
466 /* Step into the subdirectory */
467 if (dir->depth >= MAX_TZDIR_DEPTH - 1)
468 ereport(ERROR,
469 (errmsg_internal("timezone directory stack overflow")));
470 dir->depth++;
471 dir->dirname[dir->depth] = pstrdup(fullname);
472 dir->dirdesc[dir->depth] = AllocateDir(fullname);
473 if (!dir->dirdesc[dir->depth])
474 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 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 if (tzload(fullname + dir->baselen, NULL, &dir->tz.state, true) != 0)
490 {
491 /* Zone could not be loaded, ignore it */
492 continue;
493 }
494
495 if (!pg_tz_acceptable(&dir->tz))
496 {
497 /* Ignore leap-second zones */
498 continue;
499 }
500
501 /* OK, return the canonical zone name spelling. */
502 strlcpy(dir->tz.TZname, fullname + dir->baselen,
503 sizeof(dir->tz.TZname));
504
505 /* Timezone loaded OK. */
506 return &dir->tz;
507 }
508
509 /* Nothing more found */
510 return NULL;
511 }
512