PostgreSQL Source Code  git master
tzparser.c File Reference
#include "postgres.h"
#include <ctype.h>
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/tzparser.h"
Include dependency graph for tzparser.c:

Go to the source code of this file.

Macros

#define WHITESPACE   " \t\n\r"
 

Functions

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)
 
static int ParseTzFile (const char *filename, int depth, tzEntry **base, int *arraysize, int n)
 
TimeZoneAbbrevTableload_tzoffsets (const char *filename)
 

Macro Definition Documentation

◆ WHITESPACE

#define WHITESPACE   " \t\n\r"

Definition at line 34 of file tzparser.c.

Referenced by ParseTzFile(), and splitTzLine().

Function Documentation

◆ addToArray()

static int addToArray ( tzEntry **  base,
int *  arraysize,
int  n,
tzEntry entry,
bool  override 
)
static

Definition at line 187 of file tzparser.c.

References tzEntry::abbrev, cmp(), tzEntry::filename, GUC_check_errdetail, GUC_check_errmsg, tzEntry::is_dst, tzEntry::lineno, memmove, tzEntry::offset, repalloc(), and tzEntry::zone.

Referenced by ParseTzFile().

189 {
190  tzEntry *arrayptr;
191  int low;
192  int high;
193 
194  /*
195  * Search the array for a duplicate; as a useful side effect, the array is
196  * maintained in sorted order. We use strcmp() to ensure we match the
197  * sort order datetime.c expects.
198  */
199  arrayptr = *base;
200  low = 0;
201  high = n - 1;
202  while (low <= high)
203  {
204  int mid = (low + high) >> 1;
205  tzEntry *midptr = arrayptr + mid;
206  int cmp;
207 
208  cmp = strcmp(entry->abbrev, midptr->abbrev);
209  if (cmp < 0)
210  high = mid - 1;
211  else if (cmp > 0)
212  low = mid + 1;
213  else
214  {
215  /*
216  * Found a duplicate entry; complain unless it's the same.
217  */
218  if ((midptr->zone == NULL && entry->zone == NULL &&
219  midptr->offset == entry->offset &&
220  midptr->is_dst == entry->is_dst) ||
221  (midptr->zone != NULL && entry->zone != NULL &&
222  strcmp(midptr->zone, entry->zone) == 0))
223  {
224  /* return unchanged array */
225  return n;
226  }
227  if (override)
228  {
229  /* same abbrev but something is different, override */
230  midptr->zone = entry->zone;
231  midptr->offset = entry->offset;
232  midptr->is_dst = entry->is_dst;
233  return n;
234  }
235  /* same abbrev but something is different, complain */
236  GUC_check_errmsg("time zone abbreviation \"%s\" is multiply defined",
237  entry->abbrev);
238  GUC_check_errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.",
239  midptr->filename, midptr->lineno,
240  entry->filename, entry->lineno);
241  return -1;
242  }
243  }
244 
245  /*
246  * No match, insert at position "low".
247  */
248  if (n >= *arraysize)
249  {
250  *arraysize *= 2;
251  *base = (tzEntry *) repalloc(*base, *arraysize * sizeof(tzEntry));
252  }
253 
254  arrayptr = *base + low;
255 
256  memmove(arrayptr + 1, arrayptr, (n - low) * sizeof(tzEntry));
257 
258  memcpy(arrayptr, entry, sizeof(tzEntry));
259 
260  return n + 1;
261 }
#define GUC_check_errdetail
Definition: guc.h:409
int offset
Definition: tzparser.h:29
#define GUC_check_errmsg
Definition: guc.h:405
char * zone
Definition: tzparser.h:27
int lineno
Definition: tzparser.h:32
bool is_dst
Definition: tzparser.h:30
const char * filename
Definition: tzparser.h:33
#define memmove(d, s, c)
Definition: c.h:1238
char * abbrev
Definition: tzparser.h:26
void * repalloc(void *pointer, Size size)
Definition: mcxt.c:1069
static int cmp(const chr *x, const chr *y, size_t len)
Definition: regc_locale.c:742

◆ load_tzoffsets()

TimeZoneAbbrevTable* load_tzoffsets ( const char *  filename)

Definition at line 437 of file tzparser.c.

References ALLOCSET_SMALL_SIZES, AllocSetContextCreate, ConvertTimeZoneAbbrevs(), CurrentMemoryContext, GUC_check_errmsg, MemoryContextDelete(), MemoryContextSwitchTo(), palloc(), and ParseTzFile().

Referenced by check_timezone_abbreviations().

438 {
439  TimeZoneAbbrevTable *result = NULL;
440  MemoryContext tmpContext;
441  MemoryContext oldContext;
442  tzEntry *array;
443  int arraysize;
444  int n;
445 
446  /*
447  * Create a temp memory context to work in. This makes it easy to clean
448  * up afterwards.
449  */
451  "TZParserMemory",
453  oldContext = MemoryContextSwitchTo(tmpContext);
454 
455  /* Initialize array at a reasonable size */
456  arraysize = 128;
457  array = (tzEntry *) palloc(arraysize * sizeof(tzEntry));
458 
459  /* Parse the file(s) */
460  n = ParseTzFile(filename, 0, &array, &arraysize, 0);
461 
462  /* If no errors so far, let datetime.c allocate memory & convert format */
463  if (n >= 0)
464  {
465  result = ConvertTimeZoneAbbrevs(array, n);
466  if (!result)
467  GUC_check_errmsg("out of memory");
468  }
469 
470  /* Clean up */
471  MemoryContextSwitchTo(oldContext);
472  MemoryContextDelete(tmpContext);
473 
474  return result;
475 }
void MemoryContextDelete(MemoryContext context)
Definition: mcxt.c:211
#define AllocSetContextCreate
Definition: memutils.h:170
#define ALLOCSET_SMALL_SIZES
Definition: memutils.h:202
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:109
#define GUC_check_errmsg
Definition: guc.h:405
static int ParseTzFile(const char *filename, int depth, tzEntry **base, int *arraysize, int n)
Definition: tzparser.c:275
MemoryContext CurrentMemoryContext
Definition: mcxt.c:38
static char * filename
Definition: pg_dumpall.c:91
void * palloc(Size size)
Definition: mcxt.c:949
TimeZoneAbbrevTable * ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
Definition: datetime.c:4500

◆ ParseTzFile()

static int ParseTzFile ( const char *  filename,
int  depth,
tzEntry **  base,
int *  arraysize,
int  n 
)
static

Definition at line 275 of file tzparser.c.

References addToArray(), AllocateDir(), AllocateFile(), FreeDir(), FreeFile(), get_share_path(), GUC_check_errhint, GUC_check_errmsg, MAXPGPATH, my_exec_path, pg_strncasecmp(), pstrdup(), share_path, snprintf, splitTzLine(), validateTzEntry(), and WHITESPACE.

Referenced by load_tzoffsets().

277 {
278  char share_path[MAXPGPATH];
279  char file_path[MAXPGPATH];
280  FILE *tzFile;
281  char tzbuf[1024];
282  char *line;
283  tzEntry tzentry;
284  int lineno = 0;
285  bool override = false;
286  const char *p;
287 
288  /*
289  * We enforce that the filename is all alpha characters. This may be
290  * overly restrictive, but we don't want to allow access to anything
291  * outside the timezonesets directory, so for instance '/' *must* be
292  * rejected.
293  */
294  for (p = filename; *p; p++)
295  {
296  if (!isalpha((unsigned char) *p))
297  {
298  /* at level 0, just use guc.c's regular "invalid value" message */
299  if (depth > 0)
300  GUC_check_errmsg("invalid time zone file name \"%s\"",
301  filename);
302  return -1;
303  }
304  }
305 
306  /*
307  * The maximal recursion depth is a pretty arbitrary setting. It is hard
308  * to imagine that someone needs more than 3 levels so stick with this
309  * conservative setting until someone complains.
310  */
311  if (depth > 3)
312  {
313  GUC_check_errmsg("time zone file recursion limit exceeded in file \"%s\"",
314  filename);
315  return -1;
316  }
317 
318  get_share_path(my_exec_path, share_path);
319  snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s",
320  share_path, filename);
321  tzFile = AllocateFile(file_path, "r");
322  if (!tzFile)
323  {
324  /*
325  * Check to see if the problem is not the filename but the directory.
326  * This is worth troubling over because if the installation share/
327  * directory is missing or unreadable, this is likely to be the first
328  * place we notice a problem during postmaster startup.
329  */
330  int save_errno = errno;
331  DIR *tzdir;
332 
333  snprintf(file_path, sizeof(file_path), "%s/timezonesets",
334  share_path);
335  tzdir = AllocateDir(file_path);
336  if (tzdir == NULL)
337  {
338  GUC_check_errmsg("could not open directory \"%s\": %m",
339  file_path);
340  GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.",
341  my_exec_path);
342  return -1;
343  }
344  FreeDir(tzdir);
345  errno = save_errno;
346 
347  /*
348  * otherwise, if file doesn't exist and it's level 0, guc.c's
349  * complaint is enough
350  */
351  if (errno != ENOENT || depth > 0)
352  GUC_check_errmsg("could not read time zone file \"%s\": %m",
353  filename);
354 
355  return -1;
356  }
357 
358  while (!feof(tzFile))
359  {
360  lineno++;
361  if (fgets(tzbuf, sizeof(tzbuf), tzFile) == NULL)
362  {
363  if (ferror(tzFile))
364  {
365  GUC_check_errmsg("could not read time zone file \"%s\": %m",
366  filename);
367  return -1;
368  }
369  /* else we're at EOF after all */
370  break;
371  }
372  if (strlen(tzbuf) == sizeof(tzbuf) - 1)
373  {
374  /* the line is too long for tzbuf */
375  GUC_check_errmsg("line is too long in time zone file \"%s\", line %d",
376  filename, lineno);
377  return -1;
378  }
379 
380  /* skip over whitespace */
381  line = tzbuf;
382  while (*line && isspace((unsigned char) *line))
383  line++;
384 
385  if (*line == '\0') /* empty line */
386  continue;
387  if (*line == '#') /* comment line */
388  continue;
389 
390  if (pg_strncasecmp(line, "@INCLUDE", strlen("@INCLUDE")) == 0)
391  {
392  /* pstrdup so we can use filename in result data structure */
393  char *includeFile = pstrdup(line + strlen("@INCLUDE"));
394 
395  includeFile = strtok(includeFile, WHITESPACE);
396  if (!includeFile || !*includeFile)
397  {
398  GUC_check_errmsg("@INCLUDE without file name in time zone file \"%s\", line %d",
399  filename, lineno);
400  return -1;
401  }
402  n = ParseTzFile(includeFile, depth + 1,
403  base, arraysize, n);
404  if (n < 0)
405  return -1;
406  continue;
407  }
408 
409  if (pg_strncasecmp(line, "@OVERRIDE", strlen("@OVERRIDE")) == 0)
410  {
411  override = true;
412  continue;
413  }
414 
415  if (!splitTzLine(filename, lineno, line, &tzentry))
416  return -1;
417  if (!validateTzEntry(&tzentry))
418  return -1;
419  n = addToArray(base, arraysize, n, &tzentry, override);
420  if (n < 0)
421  return -1;
422  }
423 
424  FreeFile(tzFile);
425 
426  return n;
427 }
char * pstrdup(const char *in)
Definition: mcxt.c:1186
#define GUC_check_errmsg
Definition: guc.h:405
static char * share_path
Definition: initdb.c:120
static bool validateTzEntry(tzEntry *tzentry)
Definition: tzparser.c:51
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
Definition: pgstrcasecmp.c:69
Definition: dirent.c:25
static int addToArray(tzEntry **base, int *arraysize, int n, tzEntry *entry, bool override)
Definition: tzparser.c:187
#define MAXPGPATH
static int ParseTzFile(const char *filename, int depth, tzEntry **base, int *arraysize, int n)
Definition: tzparser.c:275
FILE * AllocateFile(const char *name, const char *mode)
Definition: fd.c:2205
DIR * AllocateDir(const char *dirname)
Definition: fd.c:2466
char my_exec_path[MAXPGPATH]
Definition: globals.c:72
int FreeFile(FILE *file)
Definition: fd.c:2404
static char * filename
Definition: pg_dumpall.c:91
#define GUC_check_errhint
Definition: guc.h:413
#define WHITESPACE
Definition: tzparser.c:34
static bool splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry)
Definition: tzparser.c:97
void get_share_path(const char *my_exec_path, char *ret_path)
Definition: path.c:704
#define snprintf
Definition: port.h:192
int FreeDir(DIR *dir)
Definition: fd.c:2584

◆ splitTzLine()

static bool splitTzLine ( const char *  filename,
int  lineno,
char *  line,
tzEntry tzentry 
)
static

Definition at line 97 of file tzparser.c.

References tzEntry::abbrev, tzEntry::filename, filename, GUC_check_errmsg, tzEntry::is_dst, tzEntry::lineno, tzEntry::offset, pg_strcasecmp(), pstrdup(), WHITESPACE, and tzEntry::zone.

Referenced by ParseTzFile().

98 {
99  char *abbrev;
100  char *offset;
101  char *offset_endptr;
102  char *remain;
103  char *is_dst;
104 
105  tzentry->lineno = lineno;
106  tzentry->filename = filename;
107 
108  abbrev = strtok(line, WHITESPACE);
109  if (!abbrev)
110  {
111  GUC_check_errmsg("missing time zone abbreviation in time zone file \"%s\", line %d",
112  filename, lineno);
113  return false;
114  }
115  tzentry->abbrev = pstrdup(abbrev);
116 
117  offset = strtok(NULL, WHITESPACE);
118  if (!offset)
119  {
120  GUC_check_errmsg("missing time zone offset in time zone file \"%s\", line %d",
121  filename, lineno);
122  return false;
123  }
124 
125  /* We assume zone names don't begin with a digit or sign */
126  if (isdigit((unsigned char) *offset) || *offset == '+' || *offset == '-')
127  {
128  tzentry->zone = NULL;
129  tzentry->offset = strtol(offset, &offset_endptr, 10);
130  if (offset_endptr == offset || *offset_endptr != '\0')
131  {
132  GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d",
133  filename, lineno);
134  return false;
135  }
136 
137  is_dst = strtok(NULL, WHITESPACE);
138  if (is_dst && pg_strcasecmp(is_dst, "D") == 0)
139  {
140  tzentry->is_dst = true;
141  remain = strtok(NULL, WHITESPACE);
142  }
143  else
144  {
145  /* there was no 'D' dst specifier */
146  tzentry->is_dst = false;
147  remain = is_dst;
148  }
149  }
150  else
151  {
152  /*
153  * Assume entry is a zone name. We do not try to validate it by
154  * looking up the zone, because that would force loading of a lot of
155  * zones that probably will never be used in the current session.
156  */
157  tzentry->zone = pstrdup(offset);
158  tzentry->offset = 0;
159  tzentry->is_dst = false;
160  remain = strtok(NULL, WHITESPACE);
161  }
162 
163  if (!remain) /* no more non-whitespace chars */
164  return true;
165 
166  if (remain[0] != '#') /* must be a comment */
167  {
168  GUC_check_errmsg("invalid syntax in time zone file \"%s\", line %d",
169  filename, lineno);
170  return false;
171  }
172  return true;
173 }
char * pstrdup(const char *in)
Definition: mcxt.c:1186
int offset
Definition: tzparser.h:29
#define GUC_check_errmsg
Definition: guc.h:405
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
char * zone
Definition: tzparser.h:27
int lineno
Definition: tzparser.h:32
bool is_dst
Definition: tzparser.h:30
const char * filename
Definition: tzparser.h:33
char * abbrev
Definition: tzparser.h:26
static char * filename
Definition: pg_dumpall.c:91
#define WHITESPACE
Definition: tzparser.c:34

◆ validateTzEntry()

static bool validateTzEntry ( tzEntry tzentry)
static

Definition at line 51 of file tzparser.c.

References tzEntry::abbrev, tzEntry::filename, GUC_check_errmsg, tzEntry::lineno, tzEntry::offset, pg_tolower(), and TOKMAXLEN.

Referenced by ParseTzFile().

52 {
53  unsigned char *p;
54 
55  /*
56  * Check restrictions imposed by datetktbl storage format (see datetime.c)
57  */
58  if (strlen(tzentry->abbrev) > TOKMAXLEN)
59  {
60  GUC_check_errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d",
61  tzentry->abbrev, TOKMAXLEN,
62  tzentry->filename, tzentry->lineno);
63  return false;
64  }
65 
66  /*
67  * Sanity-check the offset: shouldn't exceed 14 hours
68  */
69  if (tzentry->offset > 14 * 60 * 60 ||
70  tzentry->offset < -14 * 60 * 60)
71  {
72  GUC_check_errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d",
73  tzentry->offset,
74  tzentry->filename, tzentry->lineno);
75  return false;
76  }
77 
78  /*
79  * Convert abbrev to lowercase (must match datetime.c's conversion)
80  */
81  for (p = (unsigned char *) tzentry->abbrev; *p; p++)
82  *p = pg_tolower(*p);
83 
84  return true;
85 }
unsigned char pg_tolower(unsigned char ch)
Definition: pgstrcasecmp.c:122
int offset
Definition: tzparser.h:29
#define GUC_check_errmsg
Definition: guc.h:405
int lineno
Definition: tzparser.h:32
const char * filename
Definition: tzparser.h:33
char * abbrev
Definition: tzparser.h:26
#define TOKMAXLEN
Definition: datetime.h:205