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