]> granicus.if.org Git - postgresql/blob - src/backend/utils/misc/tzparser.c
Update copyright for 2014
[postgresql] / src / backend / utils / misc / tzparser.c
1 /*-------------------------------------------------------------------------
2  *
3  * tzparser.c
4  *        Functions for parsing timezone offset files
5  *
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.
12  *
13  *
14  * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
15  * Portions Copyright (c) 1994, Regents of the University of California
16  *
17  * IDENTIFICATION
18  *        src/backend/utils/misc/tzparser.c
19  *
20  *-------------------------------------------------------------------------
21  */
22
23 #include "postgres.h"
24
25 #include <ctype.h>
26
27 #include "miscadmin.h"
28 #include "storage/fd.h"
29 #include "utils/guc.h"
30 #include "utils/memutils.h"
31 #include "utils/tzparser.h"
32
33
34 #define WHITESPACE " \t\n\r"
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
57          * datetime.c)
58          */
59         if (strlen(tzentry->abbrev) > TOKMAXLEN)
60         {
61                 GUC_check_errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d",
62                                                  tzentry->abbrev, TOKMAXLEN,
63                                                  tzentry->filename, tzentry->lineno);
64                 return false;
65         }
66         if (tzentry->offset % 900 != 0)
67         {
68                 GUC_check_errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line %d",
69                                                  tzentry->offset,
70                                                  tzentry->filename, tzentry->lineno);
71                 return false;
72         }
73
74         /*
75          * Sanity-check the offset: shouldn't exceed 14 hours
76          */
77         if (tzentry->offset > 14 * 60 * 60 ||
78                 tzentry->offset < -14 * 60 * 60)
79         {
80                 GUC_check_errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d",
81                                                  tzentry->offset,
82                                                  tzentry->filename, tzentry->lineno);
83                 return false;
84         }
85
86         /*
87          * Convert abbrev to lowercase (must match datetime.c's conversion)
88          */
89         for (p = (unsigned char *) tzentry->abbrev; *p; p++)
90                 *p = pg_tolower(*p);
91
92         return true;
93 }
94
95 /*
96  * Attempt to parse the line as a timezone abbrev spec (name, offset, dst)
97  *
98  * Returns TRUE if OK, else false; data is stored in *tzentry
99  */
100 static bool
101 splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry)
102 {
103         char       *abbrev;
104         char       *offset;
105         char       *offset_endptr;
106         char       *remain;
107         char       *is_dst;
108
109         tzentry->lineno = lineno;
110         tzentry->filename = filename;
111
112         abbrev = strtok(line, WHITESPACE);
113         if (!abbrev)
114         {
115                 GUC_check_errmsg("missing time zone abbreviation in time zone file \"%s\", line %d",
116                                                  filename, lineno);
117                 return false;
118         }
119         tzentry->abbrev = abbrev;
120
121         offset = strtok(NULL, WHITESPACE);
122         if (!offset)
123         {
124                 GUC_check_errmsg("missing time zone offset in time zone file \"%s\", line %d",
125                                                  filename, lineno);
126                 return false;
127         }
128         tzentry->offset = strtol(offset, &offset_endptr, 10);
129         if (offset_endptr == offset || *offset_endptr != '\0')
130         {
131                 GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d",
132                                                  filename, lineno);
133                 return false;
134         }
135
136         is_dst = strtok(NULL, WHITESPACE);
137         if (is_dst && pg_strcasecmp(is_dst, "D") == 0)
138         {
139                 tzentry->is_dst = true;
140                 remain = strtok(NULL, WHITESPACE);
141         }
142         else
143         {
144                 /* there was no 'D' dst specifier */
145                 tzentry->is_dst = false;
146                 remain = is_dst;
147         }
148
149         if (!remain)                            /* no more non-whitespace chars */
150                 return true;
151
152         if (remain[0] != '#')           /* must be a comment */
153         {
154                 GUC_check_errmsg("invalid syntax in time zone file \"%s\", line %d",
155                                                  filename, lineno);
156                 return false;
157         }
158         return true;
159 }
160
161 /*
162  * Insert entry into sorted array
163  *
164  * *base: base address of array (changeable if must enlarge array)
165  * *arraysize: allocated length of array (changeable if must enlarge array)
166  * n: current number of valid elements in array
167  * entry: new data to insert
168  * override: TRUE if OK to override
169  *
170  * Returns the new array length (new value for n), or -1 if error
171  */
172 static int
173 addToArray(tzEntry **base, int *arraysize, int n,
174                    tzEntry *entry, bool override)
175 {
176         tzEntry    *arrayptr;
177         int                     low;
178         int                     high;
179
180         /*
181          * Search the array for a duplicate; as a useful side effect, the array is
182          * maintained in sorted order.  We use strcmp() to ensure we match the
183          * sort order datetime.c expects.
184          */
185         arrayptr = *base;
186         low = 0;
187         high = n - 1;
188         while (low <= high)
189         {
190                 int                     mid = (low + high) >> 1;
191                 tzEntry    *midptr = arrayptr + mid;
192                 int                     cmp;
193
194                 cmp = strcmp(entry->abbrev, midptr->abbrev);
195                 if (cmp < 0)
196                         high = mid - 1;
197                 else if (cmp > 0)
198                         low = mid + 1;
199                 else
200                 {
201                         /*
202                          * Found a duplicate entry; complain unless it's the same.
203                          */
204                         if (midptr->offset == entry->offset &&
205                                 midptr->is_dst == entry->is_dst)
206                         {
207                                 /* return unchanged array */
208                                 return n;
209                         }
210                         if (override)
211                         {
212                                 /* same abbrev but something is different, override */
213                                 midptr->offset = entry->offset;
214                                 midptr->is_dst = entry->is_dst;
215                                 return n;
216                         }
217                         /* same abbrev but something is different, complain */
218                         GUC_check_errmsg("time zone abbreviation \"%s\" is multiply defined",
219                                                          entry->abbrev);
220                         GUC_check_errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.",
221                                                                 midptr->filename, midptr->lineno,
222                                                                 entry->filename, entry->lineno);
223                         return -1;
224                 }
225         }
226
227         /*
228          * No match, insert at position "low".
229          */
230         if (n >= *arraysize)
231         {
232                 *arraysize *= 2;
233                 *base = (tzEntry *) repalloc(*base, *arraysize * sizeof(tzEntry));
234         }
235
236         arrayptr = *base + low;
237
238         memmove(arrayptr + 1, arrayptr, (n - low) * sizeof(tzEntry));
239
240         memcpy(arrayptr, entry, sizeof(tzEntry));
241
242         /* Must dup the abbrev to ensure it survives */
243         arrayptr->abbrev = pstrdup(entry->abbrev);
244
245         return n + 1;
246 }
247
248 /*
249  * Parse a single timezone abbrev file --- can recurse to handle @INCLUDE
250  *
251  * filename: user-specified file name (does not include path)
252  * depth: current recursion depth
253  * *base: array for results (changeable if must enlarge array)
254  * *arraysize: allocated length of array (changeable if must enlarge array)
255  * n: current number of valid elements in array
256  *
257  * Returns the new array length (new value for n), or -1 if error
258  */
259 static int
260 ParseTzFile(const char *filename, int depth,
261                         tzEntry **base, int *arraysize, int n)
262 {
263         char            share_path[MAXPGPATH];
264         char            file_path[MAXPGPATH];
265         FILE       *tzFile;
266         char            tzbuf[1024];
267         char       *line;
268         tzEntry         tzentry;
269         int                     lineno = 0;
270         bool            override = false;
271         const char *p;
272
273         /*
274          * We enforce that the filename is all alpha characters.  This may be
275          * overly restrictive, but we don't want to allow access to anything
276          * outside the timezonesets directory, so for instance '/' *must* be
277          * rejected.
278          */
279         for (p = filename; *p; p++)
280         {
281                 if (!isalpha((unsigned char) *p))
282                 {
283                         /* at level 0, just use guc.c's regular "invalid value" message */
284                         if (depth > 0)
285                                 GUC_check_errmsg("invalid time zone file name \"%s\"",
286                                                                  filename);
287                         return -1;
288                 }
289         }
290
291         /*
292          * The maximal recursion depth is a pretty arbitrary setting. It is hard
293          * to imagine that someone needs more than 3 levels so stick with this
294          * conservative setting until someone complains.
295          */
296         if (depth > 3)
297         {
298                 GUC_check_errmsg("time zone file recursion limit exceeded in file \"%s\"",
299                                                  filename);
300                 return -1;
301         }
302
303         get_share_path(my_exec_path, share_path);
304         snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s",
305                          share_path, filename);
306         tzFile = AllocateFile(file_path, "r");
307         if (!tzFile)
308         {
309                 /*
310                  * Check to see if the problem is not the filename but the directory.
311                  * This is worth troubling over because if the installation share/
312                  * directory is missing or unreadable, this is likely to be the first
313                  * place we notice a problem during postmaster startup.
314                  */
315                 int                     save_errno = errno;
316                 DIR                *tzdir;
317
318                 snprintf(file_path, sizeof(file_path), "%s/timezonesets",
319                                  share_path);
320                 tzdir = AllocateDir(file_path);
321                 if (tzdir == NULL)
322                 {
323                         GUC_check_errmsg("could not open directory \"%s\": %m",
324                                                          file_path);
325                         GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.",
326                                                           my_exec_path);
327                         return -1;
328                 }
329                 FreeDir(tzdir);
330                 errno = save_errno;
331
332                 /*
333                  * otherwise, if file doesn't exist and it's level 0, guc.c's
334                  * complaint is enough
335                  */
336                 if (errno != ENOENT || depth > 0)
337                         GUC_check_errmsg("could not read time zone file \"%s\": %m",
338                                                          filename);
339
340                 return -1;
341         }
342
343         while (!feof(tzFile))
344         {
345                 lineno++;
346                 if (fgets(tzbuf, sizeof(tzbuf), tzFile) == NULL)
347                 {
348                         if (ferror(tzFile))
349                         {
350                                 GUC_check_errmsg("could not read time zone file \"%s\": %m",
351                                                                  filename);
352                                 return -1;
353                         }
354                         /* else we're at EOF after all */
355                         break;
356                 }
357                 if (strlen(tzbuf) == sizeof(tzbuf) - 1)
358                 {
359                         /* the line is too long for tzbuf */
360                         GUC_check_errmsg("line is too long in time zone file \"%s\", line %d",
361                                                          filename, lineno);
362                         return -1;
363                 }
364
365                 /* skip over whitespace */
366                 line = tzbuf;
367                 while (*line && isspace((unsigned char) *line))
368                         line++;
369
370                 if (*line == '\0')              /* empty line */
371                         continue;
372                 if (*line == '#')               /* comment line */
373                         continue;
374
375                 if (pg_strncasecmp(line, "@INCLUDE", strlen("@INCLUDE")) == 0)
376                 {
377                         /* pstrdup so we can use filename in result data structure */
378                         char       *includeFile = pstrdup(line + strlen("@INCLUDE"));
379
380                         includeFile = strtok(includeFile, WHITESPACE);
381                         if (!includeFile || !*includeFile)
382                         {
383                                 GUC_check_errmsg("@INCLUDE without file name 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  * On success, return a filled-in TimeZoneAbbrevTable, which must have been
418  * malloc'd not palloc'd.  On failure, return NULL, using GUC_check_errmsg
419  * and friends to give details of the problem.
420  */
421 TimeZoneAbbrevTable *
422 load_tzoffsets(const char *filename)
423 {
424         TimeZoneAbbrevTable *result = NULL;
425         MemoryContext tmpContext;
426         MemoryContext oldContext;
427         tzEntry    *array;
428         int                     arraysize;
429         int                     n;
430
431         /*
432          * Create a temp memory context to work in.  This makes it easy to clean
433          * up afterwards.
434          */
435         tmpContext = AllocSetContextCreate(CurrentMemoryContext,
436                                                                            "TZParserMemory",
437                                                                            ALLOCSET_SMALL_MINSIZE,
438                                                                            ALLOCSET_SMALL_INITSIZE,
439                                                                            ALLOCSET_SMALL_MAXSIZE);
440         oldContext = MemoryContextSwitchTo(tmpContext);
441
442         /* Initialize array at a reasonable size */
443         arraysize = 128;
444         array = (tzEntry *) palloc(arraysize * sizeof(tzEntry));
445
446         /* Parse the file(s) */
447         n = ParseTzFile(filename, 0, &array, &arraysize, 0);
448
449         /* If no errors so far, allocate result and let datetime.c convert data */
450         if (n >= 0)
451         {
452                 result = malloc(offsetof(TimeZoneAbbrevTable, abbrevs) +
453                                                 n * sizeof(datetkn));
454                 if (!result)
455                         GUC_check_errmsg("out of memory");
456                 else
457                         ConvertTimeZoneAbbrevs(result, array, n);
458         }
459
460         /* Clean up */
461         MemoryContextSwitchTo(oldContext);
462         MemoryContextDelete(tmpContext);
463
464         return result;
465 }