]> granicus.if.org Git - postgresql/blob - src/backend/utils/misc/tzparser.c
Adjust initialization sequence for timezone_abbreviations so that
[postgresql] / src / backend / utils / misc / tzparser.c
1 /*-------------------------------------------------------------------------
2  *
3  * tzparser.c
4  *        Functions for parsing timezone offset files
5  *
6  * Note: we generally should not throw any errors in this file, but instead
7  * try to return an error code.  This is not completely bulletproof at
8  * present --- in particular out-of-memory will throw an error.  Could
9  * probably fix with PG_TRY if necessary.
10  *
11  *
12  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
13  * Portions Copyright (c) 1994, Regents of the University of California
14  *
15  * IDENTIFICATION
16  *        $PostgreSQL: pgsql/src/backend/utils/misc/tzparser.c,v 1.1 2006/07/25 03:51:21 tgl Exp $
17  *
18  *-------------------------------------------------------------------------
19  */
20
21 #include "postgres.h"
22
23 #include <ctype.h>
24
25 #include "miscadmin.h"
26 #include "storage/fd.h"
27 #include "utils/datetime.h"
28 #include "utils/memutils.h"
29 #include "utils/tzparser.h"
30
31
32 #define WHITESPACE " \t\n\r"
33
34 static int      tz_elevel;                      /* to avoid passing this around a lot */
35
36 static bool validateTzEntry(tzEntry *tzentry);
37 static bool splitTzLine(const char *filename, int lineno,
38                                                 char *line, tzEntry *tzentry);
39 static int      addToArray(tzEntry **base, int *arraysize, int n,
40                                            tzEntry *entry, bool override);
41 static int      ParseTzFile(const char *filename, int depth,
42                                                 tzEntry **base, int *arraysize, int n);
43
44
45 /*
46  * Apply additional validation checks to a tzEntry
47  *
48  * Returns TRUE if OK, else false
49  */
50 static bool
51 validateTzEntry(tzEntry *tzentry)
52 {
53         unsigned char *p;
54
55         /*
56          * Check restrictions imposed by datetkntbl storage format (see datetime.c)
57          */
58         if (strlen(tzentry->abbrev) > TOKMAXLEN)
59         {
60                 ereport(tz_elevel,
61                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
62                                  errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d",
63                                                 tzentry->abbrev, TOKMAXLEN,
64                                                 tzentry->filename, tzentry->lineno)));
65                 return false;
66         }
67         if (tzentry->offset % 900 != 0)
68         {
69                 ereport(tz_elevel,
70                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
71                                  errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line %d",
72                                                 tzentry->offset,
73                                                 tzentry->filename, tzentry->lineno)));
74                 return false;
75         }
76
77         /*
78          * Sanity-check the offset: shouldn't exceed 14 hours
79          */
80         if (tzentry->offset > 14*60*60 ||
81                 tzentry->offset < -14*60*60)
82         {
83                 ereport(tz_elevel,
84                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
85                                  errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d",
86                                                 tzentry->offset,
87                                                 tzentry->filename, tzentry->lineno)));
88                 return false;
89         }
90
91         /*
92          * Convert abbrev to lowercase (must match datetime.c's conversion)
93          */
94         for (p = (unsigned char *) tzentry->abbrev; *p; p++)
95                 *p = pg_tolower(*p);
96
97         return true;
98 }
99
100 /*
101  * Attempt to parse the line as a timezone abbrev spec (name, offset, dst)
102  *
103  * Returns TRUE if OK, else false; data is stored in *tzentry
104  */
105 static bool
106 splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry)
107 {
108         char    *abbrev;
109         char    *offset;
110         char    *offset_endptr;
111         char    *remain;
112         char    *is_dst;
113
114         tzentry->lineno = lineno;
115         tzentry->filename = filename;
116
117         abbrev = strtok(line, WHITESPACE);
118         if (!abbrev)
119         {
120                 ereport(tz_elevel,
121                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
122                                  errmsg("missing time zone abbreviation in time zone file \"%s\", line %d",
123                                                 filename, lineno)));
124                 return false;
125         }
126         tzentry->abbrev = abbrev;
127
128         offset = strtok(NULL, WHITESPACE);
129         if (!offset)
130         {
131                 ereport(tz_elevel,
132                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
133                                  errmsg("missing time zone offset in time zone file \"%s\", line %d",
134                                                 filename, lineno)));
135                 return false;
136         }
137         tzentry->offset = strtol(offset, &offset_endptr, 10);
138         if (offset_endptr == offset || *offset_endptr != '\0')
139         {
140                 ereport(tz_elevel,
141                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
142                                  errmsg("invalid number for time zone offset in time zone file \"%s\", line %d",
143                                                 filename, lineno)));
144                 return false;
145         }
146
147         is_dst = strtok(NULL, WHITESPACE);
148         if (is_dst && pg_strcasecmp(is_dst, "D") == 0)
149         {
150                 tzentry->is_dst = true;
151                 remain = strtok(NULL, WHITESPACE);
152         }
153         else
154         {
155                 /* there was no 'D' dst specifier */
156                 tzentry->is_dst = false;
157                 remain = is_dst;
158         }
159
160         if (!remain)            /* no more non-whitespace chars */
161                 return true;
162
163         if (remain[0] != '#')           /* must be a comment */
164         {
165                 ereport(tz_elevel,
166                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
167                                  errmsg("invalid syntax in time zone file \"%s\", line %d",
168                                                 filename, lineno)));
169                 return false;
170         }
171         return true;
172 }
173
174 /*
175  * Insert entry into sorted array
176  *
177  * *base: base address of array (changeable if must enlarge array)
178  * *arraysize: allocated length of array (changeable if must enlarge array)
179  * n: current number of valid elements in array
180  * entry: new data to insert
181  * override: TRUE if OK to override
182  *
183  * Returns the new array length (new value for n), or -1 if error
184  */
185 static int
186 addToArray(tzEntry **base, int *arraysize, int n,
187                    tzEntry *entry, bool override)
188 {
189         tzEntry* arrayptr;
190         int                     low;
191         int                     high;
192
193         /*
194          * Search the array for a duplicate; as a useful side effect, the array
195          * is maintained in sorted order.  We use strcmp() to ensure we match
196          * the sort order datetime.c expects.
197          */
198         arrayptr = *base;
199         low = 0;
200         high = n-1;
201         while (low <= high)
202         {
203                 int             mid = (low + high) >> 1;
204                 tzEntry *midptr = arrayptr + mid;
205                 int             cmp;
206
207                 cmp = strcmp(entry->abbrev, midptr->abbrev);
208                 if (cmp < 0)
209                         high = mid - 1;
210                 else if (cmp > 0)
211                         low = mid + 1;
212                 else
213                 {
214                         /*
215                          * Found a duplicate entry; complain unless it's the same.
216                          */
217                         if (midptr->offset == entry->offset     &&
218                                 midptr->is_dst == entry->is_dst)
219                         {
220                                 /* return unchanged array */
221                                 return n;
222                         }
223                         if (override)
224                         {
225                                 /* same abbrev but something is different, override */
226                                 midptr->offset = entry->offset;
227                                 midptr->is_dst = entry->is_dst;
228                                 return n;
229                         }
230                         /* same abbrev but something is different, complain */
231                         ereport(tz_elevel,
232                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
233                                          errmsg("time zone abbreviation \"%s\" is multiply defined",
234                                                         entry->abbrev),
235                                          errdetail("Time zone file \"%s\", line %d conflicts with file \"%s\", line %d.",
236                                                            midptr->filename, midptr->lineno,
237                                                            entry->filename, entry->lineno)));
238                         return -1;
239                 }
240         }
241
242         /*
243          * No match, insert at position "low".
244          */
245         if (n >= *arraysize)
246         {
247                 *arraysize *= 2;
248                 *base = (tzEntry *) repalloc(*base, *arraysize * sizeof(tzEntry));
249         }
250
251         arrayptr = *base + low;
252
253         memmove(arrayptr + 1, arrayptr, (n - low) * sizeof(tzEntry));
254
255         memcpy(arrayptr, entry, sizeof(tzEntry));
256
257         /* Must dup the abbrev to ensure it survives */
258         arrayptr->abbrev = pstrdup(entry->abbrev);
259
260         return n+1;
261 }
262
263 /*
264  * Parse a single timezone abbrev file --- can recurse to handle @INCLUDE
265  *
266  * filename: user-specified file name (does not include path)
267  * depth: current recursion depth
268  * *base: array for results (changeable if must enlarge array)
269  * *arraysize: allocated length of array (changeable if must enlarge array)
270  * n: current number of valid elements in array
271  *
272  * Returns the new array length (new value for n), or -1 if error
273  */
274 static int
275 ParseTzFile(const char *filename, int depth,
276                         tzEntry **base, int *arraysize, int n)
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, we need no ereport since guc.c will say enough */
299                         if (depth > 0)
300                                 ereport(tz_elevel,
301                                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
302                           errmsg("invalid time zone file name \"%s\"",
303                                                                  filename)));
304                         return -1;
305                 }
306         }
307
308         /*
309          * The maximal recursion depth is a pretty arbitrary setting.
310          * It is hard to imagine that someone needs more than 3 levels so stick
311          * with this conservative setting until someone complains.
312          */
313         if (depth > 3)
314         {
315                 ereport(tz_elevel,
316                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
317                                  errmsg("time zone file recursion limit exceeded in file \"%s\"",
318                                                 filename)));
319                 return -1;
320         }
321
322         get_share_path(my_exec_path, share_path);
323         snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s",
324                          share_path, filename);
325         tzFile = AllocateFile(file_path, "r");
326         if (!tzFile)
327         {
328                 /* at level 0, if file doesn't exist, guc.c's complaint is enough */
329                 if (errno != ENOENT || depth > 0)
330                         ereport(tz_elevel,
331                                         (errcode_for_file_access(),
332                                          errmsg("could not read time zone file \"%s\": %m",
333                                                         filename)));
334                 return -1;
335         }
336
337         while (!feof(tzFile))
338         {
339                 lineno++;
340                 if (fgets(tzbuf, sizeof(tzbuf), tzFile) == NULL)
341                 {
342                         if (ferror(tzFile))
343                         {
344                                 ereport(tz_elevel,
345                                                 (errcode_for_file_access(),
346                                                  errmsg("could not read time zone file \"%s\": %m",
347                                                                 filename)));
348                                 return -1;
349                         }
350                         /* else we're at EOF after all */
351                         break;
352                 }
353                 if (strlen(tzbuf) == sizeof(tzbuf)-1) 
354                 {
355                         /* the line is too long for tzbuf */
356                         ereport(tz_elevel,
357                                         (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
358                                          errmsg("line is too long in time zone file \"%s\", line %d",
359                                                         filename, lineno)));
360                         return -1;
361                 }
362
363                 /* skip over whitespace */
364                 line = tzbuf;
365                 while (*line && isspace((unsigned char) *line))
366                         line++;
367
368                 if (*line == '\0')                              /* empty line */
369                         continue;
370                 if (*line == '#')                               /* comment line */
371                         continue;
372
373                 if (pg_strncasecmp(line, "@INCLUDE", strlen("@INCLUDE")) == 0)
374                 {
375                         /* pstrdup so we can use filename in result data structure */
376                         char* includeFile = pstrdup(line + strlen("@INCLUDE"));
377
378                         includeFile = strtok(includeFile, WHITESPACE);
379                         if (!includeFile || !*includeFile)
380                         {
381                                 ereport(tz_elevel,
382                                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
383                                                  errmsg("@INCLUDE without filename in time zone file \"%s\", line %d",
384                                                                 filename, lineno)));
385                                 return -1;
386                         }
387                         n = ParseTzFile(includeFile, depth + 1,
388                                                         base, arraysize, n);
389                         if (n < 0)
390                                 return -1;
391                         continue;
392                 }
393
394                 if (pg_strncasecmp(line, "@OVERRIDE", strlen("@OVERRIDE")) == 0)
395                 {
396                         override = true;
397                         continue;
398                 }
399
400                 if (!splitTzLine(filename, lineno, line, &tzentry))
401                         return -1;
402                 if (!validateTzEntry(&tzentry))
403                         return -1;
404                 n = addToArray(base, arraysize, n, &tzentry, override);
405                 if (n < 0)
406                         return -1;
407         }
408
409         FreeFile(tzFile);
410
411         return n;
412 }
413
414 /*
415  * load_tzoffsets --- read and parse the specified timezone offset file
416  *
417  * filename: name specified by user
418  * doit: whether to actually apply the new values, or just check
419  * elevel: elog reporting level (will be less than ERROR)
420  *
421  * Returns TRUE if OK, FALSE if not; should avoid erroring out
422  */
423 bool
424 load_tzoffsets(const char *filename, bool doit, int elevel)
425 {
426         MemoryContext tmpContext;
427         MemoryContext oldContext;
428         tzEntry    *array;
429         int                     arraysize;
430         int                     n;
431
432         tz_elevel = elevel;
433
434         /*
435          * Create a temp memory context to work in.  This makes it easy to
436          * clean up afterwards.
437          */
438         tmpContext = AllocSetContextCreate(CurrentMemoryContext,
439                                                                            "TZParserMemory",
440                                                                            ALLOCSET_SMALL_MINSIZE,
441                                                                            ALLOCSET_SMALL_INITSIZE,
442                                                                            ALLOCSET_SMALL_MAXSIZE);
443         oldContext = MemoryContextSwitchTo(tmpContext);
444
445         /* Initialize array at a reasonable size */
446         arraysize = 128;
447         array = (tzEntry *) palloc(arraysize * sizeof(tzEntry));
448
449         /* Parse the file(s) */
450         n = ParseTzFile(filename, 0, &array, &arraysize, 0);
451
452         /* If no errors and we should apply the result, pass it to datetime.c */
453         if (n >= 0 && doit)
454                 InstallTimeZoneAbbrevs(array, n);
455
456         /* Clean up */
457         MemoryContextSwitchTo(oldContext);
458         MemoryContextDelete(tmpContext);
459
460         return (n >= 0);
461 }