PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
pgtz.c
Go to the documentation of this file.
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) */
29
30/* Current log timezone (controlled by log_timezone GUC) */
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 *
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
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];
197
198static HTAB *timezone_cache = NULL;
199
200
201static bool
203{
204 HASHCTL hash_ctl;
205
206 hash_ctl.keysize = TZ_STRLEN_MAX + 1;
207 hash_ctl.entrysize = sizeof(pg_tz_cache);
208
209 timezone_cache = hash_create("Timezones",
210 4,
211 &hash_ctl,
213 if (!timezone_cache)
214 return false;
215
216 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 */
233pg_tz *
234pg_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 if (strlen(tzname) > TZ_STRLEN_MAX)
243 return NULL; /* not going to fit */
244
245 if (!timezone_cache)
247 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 p = uppername;
256 while (*tzname)
257 *p++ = pg_toupper((unsigned char) *tzname++);
258 *p = '\0';
259
261 uppername,
262 HASH_FIND,
263 NULL);
264 if (tzp)
265 {
266 /* Timezone found in cache, nothing more to do */
267 return &tzp->tz;
268 }
269
270 /*
271 * "GMT" is always sent to tzparse(), as per discussion above.
272 */
273 if (strcmp(uppername, "GMT") == 0)
274 {
275 if (!tzparse(uppername, &tzstate, true))
276 {
277 /* This really, really should not happen ... */
278 elog(ERROR, "could not initialize GMT time zone");
279 }
280 /* Use uppercase name as canonical */
281 strcpy(canonname, uppername);
282 }
283 else if (tzload(uppername, canonname, &tzstate, true) != 0)
284 {
285 if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false))
286 {
287 /* Unknown timezone. Fail our call instead of loading GMT! */
288 return NULL;
289 }
290 /* For POSIX timezone specs, use uppercase name as canonical */
291 strcpy(canonname, uppername);
292 }
293
294 /* Save timezone in the cache */
296 uppername,
298 NULL);
299
300 /* hash_search already copied uppername into the hash key */
301 strcpy(tzp->tz.TZname, canonname);
302 memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
303
304 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 */
319pg_tz *
320pg_tzset_offset(long gmtoffset)
321{
322 long absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset;
323 char offsetstr[64];
324 char tzname[128];
325
326 snprintf(offsetstr, sizeof(offsetstr),
327 "%02ld", absoffset / SECS_PER_HOUR);
328 absoffset %= SECS_PER_HOUR;
329 if (absoffset != 0)
330 {
331 snprintf(offsetstr + strlen(offsetstr),
332 sizeof(offsetstr) - strlen(offsetstr),
333 ":%02ld", absoffset / SECS_PER_MINUTE);
334 absoffset %= SECS_PER_MINUTE;
335 if (absoffset != 0)
336 snprintf(offsetstr + strlen(offsetstr),
337 sizeof(offsetstr) - strlen(offsetstr),
338 ":%02ld", absoffset);
339 }
340 if (gmtoffset > 0)
341 snprintf(tzname, sizeof(tzname), "<-%s>+%s",
342 offsetstr, offsetstr);
343 else
344 snprintf(tzname, sizeof(tzname), "<+%s>-%s",
345 offsetstr, offsetstr);
346
347 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 */
360void
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 session_timezone = pg_tzset("GMT");
372}
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
386{
388 int depth;
391 struct pg_tz tz;
392};
393
394/* typedef pg_tzenum is declared in pgtime.h */
395
396pg_tzenum *
398{
399 pg_tzenum *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum));
400 char *startdir = pstrdup(pg_TZDIR());
401
402 ret->baselen = strlen(startdir) + 1;
403 ret->depth = 0;
404 ret->dirname[0] = startdir;
405 ret->dirdesc[0] = AllocateDir(startdir);
406 if (!ret->dirdesc[0])
409 errmsg("could not open directory \"%s\": %m", startdir)));
410 return ret;
411}
412
413void
415{
416 while (dir->depth >= 0)
417 {
418 FreeDir(dir->dirdesc[dir->depth]);
419 pfree(dir->dirname[dir->depth]);
420 dir->depth--;
421 }
422 pfree(dir);
423}
424
425pg_tz *
427{
428 while (dir->depth >= 0)
429 {
430 struct dirent *direntry;
431 char fullname[MAXPGPATH * 2];
432
433 direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
434
435 if (!direntry)
436 {
437 /* End of this directory */
438 FreeDir(dir->dirdesc[dir->depth]);
439 pfree(dir->dirname[dir->depth]);
440 dir->depth--;
441 continue;
442 }
443
444 if (direntry->d_name[0] == '.')
445 continue;
446
447 snprintf(fullname, sizeof(fullname), "%s/%s",
448 dir->dirname[dir->depth], direntry->d_name);
449
450 if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
451 {
452 /* Step into the subdirectory */
453 if (dir->depth >= MAX_TZDIR_DEPTH - 1)
455 (errmsg_internal("timezone directory stack overflow")));
456 dir->depth++;
457 dir->dirname[dir->depth] = pstrdup(fullname);
458 dir->dirdesc[dir->depth] = AllocateDir(fullname);
459 if (!dir->dirdesc[dir->depth])
462 errmsg("could not open directory \"%s\": %m",
463 fullname)));
464
465 /* Start over reading in the new directory */
466 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 if (tzload(fullname + dir->baselen, NULL, &dir->tz.state, true) != 0)
476 {
477 /* Zone could not be loaded, ignore it */
478 continue;
479 }
480
481 if (!pg_tz_acceptable(&dir->tz))
482 {
483 /* Ignore leap-second zones */
484 continue;
485 }
486
487 /* OK, return the canonical zone name spelling. */
488 strlcpy(dir->tz.TZname, fullname + dir->baselen,
489 sizeof(dir->tz.TZname));
490
491 /* Timezone loaded OK. */
492 return &dir->tz;
493 }
494
495 /* Nothing more found */
496 return NULL;
497}
#define PG_BINARY
Definition: c.h:1227
#define SECS_PER_HOUR
Definition: timestamp.h:127
#define SECS_PER_MINUTE
Definition: timestamp.h:128
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:955
HTAB * hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:352
int errmsg_internal(const char *fmt,...)
Definition: elog.c:1157
int errcode_for_file_access(void)
Definition: elog.c:876
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define LOG
Definition: elog.h:31
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define ereport(elevel,...)
Definition: elog.h:149
int FreeDir(DIR *dir)
Definition: fd.c:2983
struct dirent * ReadDirExtended(DIR *dir, const char *dirname, int elevel)
Definition: fd.c:2946
DIR * AllocateDir(const char *dirname)
Definition: fd.c:2865
struct dirent * ReadDir(DIR *dir, const char *dirname)
Definition: fd.c:2931
PGFileType get_dirent_type(const char *path, const struct dirent *de, bool look_through_symlinks, int elevel)
Definition: file_utils.c:526
@ PGFILETYPE_DIR
Definition: file_utils.h:23
char my_exec_path[MAXPGPATH]
Definition: globals.c:80
#define HASH_STRINGS
Definition: hsearch.h:96
@ HASH_FIND
Definition: hsearch.h:113
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_ELEM
Definition: hsearch.h:95
int tzload(const char *name, char *canonname, struct state *sp, bool doextend)
Definition: localtime.c:586
bool tzparse(const char *name, struct state *sp, bool lastditch)
Definition: localtime.c:936
char * pstrdup(const char *in)
Definition: mcxt.c:1696
void pfree(void *pointer)
Definition: mcxt.c:1521
void * palloc0(Size size)
Definition: mcxt.c:1347
#define MAXPGPATH
bool pg_tz_acceptable(pg_tz *tz)
Definition: localtime.c:1890
#define TZ_STRLEN_MAX
Definition: pgtime.h:54
pg_tz * pg_tzset_offset(long gmtoffset)
Definition: pgtz.c:320
static HTAB * timezone_cache
Definition: pgtz.c:198
pg_tz * pg_tzenumerate_next(pg_tzenum *dir)
Definition: pgtz.c:426
pg_tz * log_timezone
Definition: pgtz.c:31
void pg_timezone_initialize(void)
Definition: pgtz.c:361
pg_tz * pg_tzset(const char *tzname)
Definition: pgtz.c:234
#define MAX_TZDIR_DEPTH
Definition: pgtz.c:383
static const char * pg_TZDIR(void)
Definition: pgtz.c:43
int pg_open_tzfile(const char *name, char *canonname)
Definition: pgtz.c:76
static bool scan_directory_ci(const char *dirname, const char *fname, int fnamelen, char *canonname, int canonnamelen)
Definition: pgtz.c:151
pg_tz * session_timezone
Definition: pgtz.c:28
static bool init_timezone_hashtable(void)
Definition: pgtz.c:202
void pg_tzenumerate_end(pg_tzenum *dir)
Definition: pgtz.c:414
pg_tzenum * pg_tzenumerate_start(void)
Definition: pgtz.c:397
void get_share_path(const char *my_exec_path, char *ret_path)
Definition: path.c:825
unsigned char pg_toupper(unsigned char ch)
Definition: pgstrcasecmp.c:105
#define snprintf
Definition: port.h:238
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:45
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
Definition: pgstrcasecmp.c:69
Definition: dirent.c:26
Size keysize
Definition: hsearch.h:75
Size entrysize
Definition: hsearch.h:76
Definition: dynahash.c:220
Definition: dirent.h:10
char d_name[MAX_PATH]
Definition: dirent.h:15
pg_tz tz
Definition: pgtz.c:195
Definition: pgtz.h:66
char TZname[TZ_STRLEN_MAX+1]
Definition: pgtz.h:68
struct state state
Definition: pgtz.h:69
int baselen
Definition: pgtz.c:387
char * dirname[MAX_TZDIR_DEPTH]
Definition: pgtz.c:390
struct pg_tz tz
Definition: pgtz.c:391
int depth
Definition: pgtz.c:388
DIR * dirdesc[MAX_TZDIR_DEPTH]
Definition: pgtz.c:389
Definition: regguts.h:323
const char * name