1/*-------------------------------------------------------------------------
4 * Functions for parsing timezone offset files
6 * Note: this code is invoked from the check_hook for the GUC variable
7 * timezone_abbreviations. Therefore, it should report problems using
8 * GUC_check_errmsg() and related functions, and try to avoid throwing
9 * elog(ERROR). This is not completely bulletproof at present --- in
10 * particular out-of-memory will throw an error. Could probably fix with
11 * PG_TRY if necessary.
14 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
15 * Portions Copyright (c) 1994, Regents of the University of California
18 * src/backend/utils/misc/tzparser.c
20 *-------------------------------------------------------------------------
35 #define WHITESPACE " \t\n\r"
43 tzEntry **base,
int *arraysize,
int n);
47 * Apply additional validation checks to a tzEntry
49 * Returns true if OK, else false
57 * Check restrictions imposed by datetktbl storage format (see datetime.c)
61 GUC_check_errmsg(
"time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d",
68 * Sanity-check the offset: shouldn't exceed 14 hours
73 GUC_check_errmsg(
"time zone offset %d is out of range in time zone file \"%s\", line %d",
80 * Convert abbrev to lowercase (must match datetime.c's conversion)
82 for (p = (
unsigned char *) tzentry->
abbrev; *p; p++)
89 * Attempt to parse the line as a timezone abbrev spec
95 * Returns true if OK, else false; data is stored in *tzentry
113 GUC_check_errmsg(
"missing time zone abbreviation in time zone file \"%s\", line %d",
122 GUC_check_errmsg(
"missing time zone offset in time zone file \"%s\", line %d",
127 /* We assume zone names don't begin with a digit or sign */
128 if (isdigit((
unsigned char) *offset) || *offset ==
'+' || *offset ==
'-')
130 tzentry->
zone = NULL;
131 tzentry->
offset = strtol(offset, &offset_endptr, 10);
132 if (offset_endptr == offset || *offset_endptr !=
'0円')
134 GUC_check_errmsg(
"invalid number for time zone offset in time zone file \"%s\", line %d",
147 /* there was no 'D' dst specifier */
155 * Assume entry is a zone name. We do not try to validate it by
156 * looking up the zone, because that would force loading of a lot of
157 * zones that probably will never be used in the current session.
165 if (!remain)
/* no more non-whitespace chars */
168 if (remain[0] !=
'#')
/* must be a comment */
178 * Insert entry into sorted array
180 * *base: base address of array (changeable if must enlarge array)
181 * *arraysize: allocated length of array (changeable if must enlarge array)
182 * n: current number of valid elements in array
183 * entry: new data to insert
184 * override: true if OK to override
186 * Returns the new array length (new value for n), or -1 if error
197 * Search the array for a duplicate; as a useful side effect, the array is
198 * maintained in sorted order. We use strcmp() to ensure we match the
199 * sort order datetime.c expects.
206 int mid = (low + high) >> 1;
207 tzEntry *midptr = arrayptr + mid;
218 * Found a duplicate entry; complain unless it's the same.
220 if ((midptr->
zone == NULL && entry->
zone == NULL &&
223 (midptr->
zone != NULL && entry->
zone != NULL &&
224 strcmp(midptr->
zone, entry->
zone) == 0))
226 /* return unchanged array */
231 /* same abbrev but something is different, override */
237 /* same abbrev but something is different, complain */
240 GUC_check_errdetail(
"Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.",
248 * No match, insert at position "low".
256 arrayptr = *base + low;
258 memmove(arrayptr + 1, arrayptr, (n - low) *
sizeof(
tzEntry));
260 memcpy(arrayptr, entry,
sizeof(
tzEntry));
266 * Parse a single timezone abbrev file --- can recurse to handle @INCLUDE
268 * filename: user-specified file name (does not include path)
269 * depth: current recursion depth
270 * *base: array for results (changeable if must enlarge array)
271 * *arraysize: allocated length of array (changeable if must enlarge array)
272 * n: current number of valid elements in array
274 * Returns the new array length (new value for n), or -1 if error
278 tzEntry **base,
int *arraysize,
int n)
287 bool override =
false;
291 * We enforce that the filename is all alpha characters. This may be
292 * overly restrictive, but we don't want to allow access to anything
293 * outside the timezonesets directory, so for instance '/' *must* be
298 if (!isalpha((
unsigned char) *p))
300 /* at level 0, just use guc.c's regular "invalid value" message */
309 * The maximal recursion depth is a pretty arbitrary setting. It is hard
310 * to imagine that someone needs more than 3 levels so stick with this
311 * conservative setting until someone complains.
321 snprintf(file_path,
sizeof(file_path),
"%s/timezonesets/%s",
327 * Check to see if the problem is not the filename but the directory.
328 * This is worth troubling over because if the installation share/
329 * directory is missing or unreadable, this is likely to be the first
330 * place we notice a problem during postmaster startup.
332 int save_errno = errno;
335 snprintf(file_path,
sizeof(file_path),
"%s/timezonesets",
342 GUC_check_errhint(
"This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.",
350 * otherwise, if file doesn't exist and it's level 0, guc.c's
351 * complaint is enough
353 if (errno != ENOENT || depth > 0)
360 while (!feof(tzFile))
363 if (fgets(tzbuf,
sizeof(tzbuf), tzFile) == NULL)
372 /* else we're at EOF after all */
375 if (strlen(tzbuf) ==
sizeof(tzbuf) - 1)
377 /* the line is too long for tzbuf */
384 /* skip over whitespace */
386 while (*line && isspace((
unsigned char) *line))
389 if (*line ==
'0円')
/* empty line */
391 if (*line ==
'#')
/* comment line */
396 /* pstrdup so we can use filename in result data structure */
397 char *includeFile =
pstrdup(line + strlen(
"@INCLUDE"));
401 if (!includeFile || !*includeFile)
403 GUC_check_errmsg(
"@INCLUDE without file name in time zone file \"%s\", line %d",
431 n =
addToArray(base, arraysize, n, &tzentry,
override);
442 * load_tzoffsets --- read and parse the specified timezone offset file
444 * On success, return a filled-in TimeZoneAbbrevTable, which must have been
445 * guc_malloc'd not palloc'd. On failure, return NULL, using GUC_check_errmsg
446 * and friends to give details of the problem.
459 * Create a temp memory context to work in. This makes it easy to clean
467 /* Initialize array at a reasonable size */
471 /* Parse the file(s) */
474 /* If no errors so far, let datetime.c allocate memory & convert format */
TimeZoneAbbrevTable * ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
DIR * AllocateDir(const char *dirname)
FILE * AllocateFile(const char *name, const char *mode)
char my_exec_path[MAXPGPATH]
#define GUC_check_errdetail
#define GUC_check_errhint
char * pstrdup(const char *in)
void * repalloc(void *pointer, Size size)
MemoryContext CurrentMemoryContext
void MemoryContextDelete(MemoryContext context)
#define AllocSetContextCreate
#define ALLOCSET_SMALL_SIZES
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
void get_share_path(const char *my_exec_path, char *ret_path)
int pg_strcasecmp(const char *s1, const char *s2)
unsigned char pg_tolower(unsigned char ch)
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
static int cmp(const chr *x, const chr *y, size_t len)
static bool validateTzEntry(tzEntry *tzentry)
static bool splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry)
static int addToArray(tzEntry **base, int *arraysize, int n, tzEntry *entry, bool override)
TimeZoneAbbrevTable * load_tzoffsets(const char *filename)
static int ParseTzFile(const char *filename, int depth, tzEntry **base, int *arraysize, int n)