1 /*-------------------------------------------------------------------------
4 * Timezone Library Integration Functions
6 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
11 *-------------------------------------------------------------------------
20 #include "datatype/timestamp.h"
21 #include "miscadmin.h"
23 #include "storage/fd.h"
24 #include "utils/hsearch.h"
27 /* Current session timezone (controlled by TimeZone GUC) */
28 pg_tz
*session_timezone
= NULL
;
30 /* Current log timezone (controlled by log_timezone GUC) */
31 pg_tz
*log_timezone
= NULL
;
34 static bool scan_directory_ci(const char *dirname
,
35 const char *fname
, int fnamelen
,
36 char *canonname
, int canonnamelen
);
40 * Return full pathname of timezone data directory
46 /* normal case: timezone stuff is under our share dir */
47 static bool done_tzdir
= false;
48 static char tzdir
[MAXPGPATH
];
53 get_share_path(my_exec_path
, tzdir
);
54 strlcpy(tzdir
+ strlen(tzdir
), "/timezone", MAXPGPATH
- strlen(tzdir
));
59 /* we're configured to use system's timezone database */
66 * Given a timezone name, open() the timezone data file. Return the
67 * file descriptor if successful, -1 if not.
69 * The input name is searched for case-insensitively (we assume that the
70 * timezone database does not contain case-equivalent names).
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!).
76 pg_open_tzfile(const char *name
, char *canonname
)
79 char fullname
[MAXPGPATH
];
83 /* Initialize fullname with base name of tzdata directory */
84 strlcpy(fullname
, pg_TZDIR(), sizeof(fullname
));
85 orignamelen
= fullnamelen
= strlen(fullname
);
87 if (fullnamelen
+ 1 + strlen(name
) >= MAXPGPATH
)
88 return -1; /* not gonna fit */
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.
97 if (canonname
== NULL
)
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);
107 /* If that didn't work, fall through to do it the hard way */
108 fullname
[fullnamelen
] = '\0';
112 * Loop to split the given name into directory levels; for each level,
113 * search using scan_directory_ci().
118 const char *slashptr
;
121 slashptr
= strchr(fname
, '/');
123 fnamelen
= slashptr
- fname
;
125 fnamelen
= strlen(fname
);
126 if (!scan_directory_ci(fullname
, fname
, fnamelen
,
127 fullname
+ fullnamelen
+ 1,
128 MAXPGPATH
- fullnamelen
- 1))
130 fullname
[fullnamelen
++] = '/';
131 fullnamelen
+= strlen(fullname
+ fullnamelen
);
133 fname
= slashptr
+ 1;
139 strlcpy(canonname
, fullname
+ orignamelen
+ 1, TZ_STRLEN_MAX
+ 1);
141 return open(fullname
, O_RDONLY
| PG_BINARY
, 0);
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.
151 scan_directory_ci(const char *dirname
, const char *fname
, int fnamelen
,
152 char *canonname
, int canonnamelen
)
156 struct dirent
*direntry
;
158 dirdesc
= AllocateDir(dirname
);
160 while ((direntry
= ReadDirExtended(dirdesc
, dirname
, LOG
)) != NULL
)
163 * Ignore . and .., plus any other "hidden" files. This is a security
164 * measure to prevent access to files outside the timezone directory.
166 if (direntry
->d_name
[0] == '.')
169 if (strlen(direntry
->d_name
) == fnamelen
&&
170 pg_strncasecmp(direntry
->d_name
, fname
, fnamelen
) == 0)
172 /* Found our match */
173 strlcpy(canonname
, direntry
->d_name
, canonnamelen
);
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.
193 /* tznameupper contains the all-upper-case name of the timezone */
194 char tznameupper
[TZ_STRLEN_MAX
+ 1];
198 static HTAB
*timezone_cache
= NULL
;
202 init_timezone_hashtable(void)
206 hash_ctl
.keysize
= TZ_STRLEN_MAX
+ 1;
207 hash_ctl
.entrysize
= sizeof(pg_tz_cache
);
209 timezone_cache
= hash_create("Timezones",
212 HASH_ELEM
| HASH_STRINGS
);
220 * Load a timezone from file or from cache.
221 * Does not verify that the timezone is acceptable!
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.
234 pg_tzset(const char *name
)
237 struct state tzstate
;
238 char uppername
[TZ_STRLEN_MAX
+ 1];
239 char canonname
[TZ_STRLEN_MAX
+ 1];
242 if (strlen(name
) > TZ_STRLEN_MAX
)
243 return NULL
; /* not going to fit */
246 if (!init_timezone_hashtable())
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.)
257 *p
++ = pg_toupper((unsigned char) *name
++);
260 tzp
= (pg_tz_cache
*) hash_search(timezone_cache
,
266 /* Timezone found in cache, nothing more to do */
271 * "GMT" is always sent to tzparse(), as per discussion above.
273 if (strcmp(uppername
, "GMT") == 0)
275 if (!tzparse(uppername
, &tzstate
, true))
277 /* This really, really should not happen ... */
278 elog(ERROR
, "could not initialize GMT time zone");
280 /* Use uppercase name as canonical */
281 strcpy(canonname
, uppername
);
283 else if (tzload(uppername
, canonname
, &tzstate
, true) != 0)
285 if (uppername
[0] == ':' || !tzparse(uppername
, &tzstate
, false))
287 /* Unknown timezone. Fail our call instead of loading GMT! */
290 /* For POSIX timezone specs, use uppercase name as canonical */
291 strcpy(canonname
, uppername
);
294 /* Save timezone in the cache */
295 tzp
= (pg_tz_cache
*) hash_search(timezone_cache
,
300 /* hash_search already copied uppername into the hash key */
301 strcpy(tzp
->tz
.TZname
, canonname
);
302 memcpy(&tzp
->tz
.state
, &tzstate
, sizeof(tzstate
));
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().
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.
316 * Caution: this can fail (return NULL) if the specified offset is outside
317 * the range allowed by the zic library.
320 pg_tzset_offset(long gmtoffset
)
322 long absoffset
= (gmtoffset
< 0) ? -gmtoffset
: gmtoffset
;
326 snprintf(offsetstr
, sizeof(offsetstr
),
327 "%02ld", absoffset
/ SECS_PER_HOUR
);
328 absoffset
%= SECS_PER_HOUR
;
331 snprintf(offsetstr
+ strlen(offsetstr
),
332 sizeof(offsetstr
) - strlen(offsetstr
),
333 ":%02ld", absoffset
/ SECS_PER_MINUTE
);
334 absoffset
%= SECS_PER_MINUTE
;
336 snprintf(offsetstr
+ strlen(offsetstr
),
337 sizeof(offsetstr
) - strlen(offsetstr
),
338 ":%02ld", absoffset
);
341 snprintf(tzname
, sizeof(tzname
), "<-%s>+%s",
342 offsetstr
, offsetstr
);
344 snprintf(tzname
, sizeof(tzname
), "<+%s>-%s",
345 offsetstr
, offsetstr
);
347 return pg_tzset(tzname
);
352 * Initialize timezone library
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.
361 pg_timezone_initialize(void)
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.c, although in
368 * principle it could be different.
370 session_timezone
= pg_tzset("GMT");
371 log_timezone
= session_timezone
;
376 * Functions to enumerate available timezones
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.
381 * All data is allocated using palloc in the current context.
383 #define MAX_TZDIR_DEPTH 10
389 DIR *dirdesc
[MAX_TZDIR_DEPTH
];
390 char *dirname
[MAX_TZDIR_DEPTH
];
394 /* typedef pg_tzenum is declared in pgtime.h */
397 pg_tzenumerate_start(void)
399 pg_tzenum
*ret
= (pg_tzenum
*) palloc0(sizeof(pg_tzenum
));
400 char *startdir
= pstrdup(pg_TZDIR());
402 ret
->baselen
= strlen(startdir
) + 1;
404 ret
->dirname
[0] = startdir
;
405 ret
->dirdesc
[0] = AllocateDir(startdir
);
406 if (!ret
->dirdesc
[0])
408 (errcode_for_file_access(),
409 errmsg("could not open directory \"%s\": %m", startdir
)));
414 pg_tzenumerate_end(pg_tzenum
*dir
)
416 while (dir
->depth
>= 0)
418 FreeDir(dir
->dirdesc
[dir
->depth
]);
419 pfree(dir
->dirname
[dir
->depth
]);
426 pg_tzenumerate_next(pg_tzenum
*dir
)
428 while (dir
->depth
>= 0)
430 struct dirent
*direntry
;
431 char fullname
[MAXPGPATH
* 2];
434 direntry
= ReadDir(dir
->dirdesc
[dir
->depth
], dir
->dirname
[dir
->depth
]);
438 /* End of this directory */
439 FreeDir(dir
->dirdesc
[dir
->depth
]);
440 pfree(dir
->dirname
[dir
->depth
]);
445 if (direntry
->d_name
[0] == '.')
448 snprintf(fullname
, sizeof(fullname
), "%s/%s",
449 dir
->dirname
[dir
->depth
], direntry
->d_name
);
450 if (stat(fullname
, &statbuf
) != 0)
452 (errcode_for_file_access(),
453 errmsg("could not stat \"%s\": %m", fullname
)));
455 if (S_ISDIR(statbuf
.st_mode
))
457 /* Step into the subdirectory */
458 if (dir
->depth
>= MAX_TZDIR_DEPTH
- 1)
460 (errmsg_internal("timezone directory stack overflow")));
462 dir
->dirname
[dir
->depth
] = pstrdup(fullname
);
463 dir
->dirdesc
[dir
->depth
] = AllocateDir(fullname
);
464 if (!dir
->dirdesc
[dir
->depth
])
466 (errcode_for_file_access(),
467 errmsg("could not open directory \"%s\": %m",
470 /* Start over reading in the new directory */
475 * Load this timezone using tzload() not pg_tzset(), so we don't fill
476 * the cache. Also, don't ask for the canonical spelling: we already
477 * know it, and pg_open_tzfile's way of finding it out is pretty
480 if (tzload(fullname
+ dir
->baselen
, NULL
, &dir
->tz
.state
, true) != 0)
482 /* Zone could not be loaded, ignore it */
486 if (!pg_tz_acceptable(&dir
->tz
))
488 /* Ignore leap-second zones */
492 /* OK, return the canonical zone name spelling. */
493 strlcpy(dir
->tz
.TZname
, fullname
+ dir
->baselen
,
494 sizeof(dir
->tz
.TZname
));
496 /* Timezone loaded OK. */
500 /* Nothing more found */