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