]> granicus.if.org Git - postgresql/blob - src/timezone/pgtz.c
Make init_spin_delay() C89 compliant and change stuck spinlock reporting.
[postgresql] / src / timezone / pgtz.c
1 /*-------------------------------------------------------------------------
2  *
3  * pgtz.c
4  *        Timezone Library Integration Functions
5  *
6  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
7  *
8  * IDENTIFICATION
9  *        src/timezone/pgtz.c
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14
15 #include <ctype.h>
16 #include <fcntl.h>
17 #include <sys/stat.h>
18 #include <time.h>
19
20 #include "miscadmin.h"
21 #include "pgtz.h"
22 #include "storage/fd.h"
23 #include "utils/hsearch.h"
24
25
26 /* Current session timezone (controlled by TimeZone GUC) */
27 pg_tz      *session_timezone = NULL;
28
29 /* Current log timezone (controlled by log_timezone GUC) */
30 pg_tz      *log_timezone = NULL;
31
32
33 static bool scan_directory_ci(const char *dirname,
34                                   const char *fname, int fnamelen,
35                                   char *canonname, int canonnamelen);
36
37
38 /*
39  * Return full pathname of timezone data directory
40  */
41 static const char *
42 pg_TZDIR(void)
43 {
44 #ifndef SYSTEMTZDIR
45         /* normal case: timezone stuff is under our share dir */
46         static bool done_tzdir = false;
47         static char tzdir[MAXPGPATH];
48
49         if (done_tzdir)
50                 return tzdir;
51
52         get_share_path(my_exec_path, tzdir);
53         strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
54
55         done_tzdir = true;
56         return tzdir;
57 #else
58         /* we're configured to use system's timezone database */
59         return SYSTEMTZDIR;
60 #endif
61 }
62
63
64 /*
65  * Given a timezone name, open() the timezone data file.  Return the
66  * file descriptor if successful, -1 if not.
67  *
68  * The input name is searched for case-insensitively (we assume that the
69  * timezone database does not contain case-equivalent names).
70  *
71  * If "canonname" is not NULL, then on success the canonical spelling of the
72  * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
73  */
74 int
75 pg_open_tzfile(const char *name, char *canonname)
76 {
77         const char *fname;
78         char            fullname[MAXPGPATH];
79         int                     fullnamelen;
80         int                     orignamelen;
81
82         /*
83          * Loop to split the given name into directory levels; for each level,
84          * search using scan_directory_ci().
85          */
86         strlcpy(fullname, pg_TZDIR(), sizeof(fullname));
87         orignamelen = fullnamelen = strlen(fullname);
88         fname = name;
89         for (;;)
90         {
91                 const char *slashptr;
92                 int                     fnamelen;
93
94                 slashptr = strchr(fname, '/');
95                 if (slashptr)
96                         fnamelen = slashptr - fname;
97                 else
98                         fnamelen = strlen(fname);
99                 if (fullnamelen + 1 + fnamelen >= MAXPGPATH)
100                         return -1;                      /* not gonna fit */
101                 if (!scan_directory_ci(fullname, fname, fnamelen,
102                                                            fullname + fullnamelen + 1,
103                                                            MAXPGPATH - fullnamelen - 1))
104                         return -1;
105                 fullname[fullnamelen++] = '/';
106                 fullnamelen += strlen(fullname + fullnamelen);
107                 if (slashptr)
108                         fname = slashptr + 1;
109                 else
110                         break;
111         }
112
113         if (canonname)
114                 strlcpy(canonname, fullname + orignamelen + 1, TZ_STRLEN_MAX + 1);
115
116         return open(fullname, O_RDONLY | PG_BINARY, 0);
117 }
118
119
120 /*
121  * Scan specified directory for a case-insensitive match to fname
122  * (of length fnamelen --- fname may not be null terminated!).  If found,
123  * copy the actual filename into canonname and return true.
124  */
125 static bool
126 scan_directory_ci(const char *dirname, const char *fname, int fnamelen,
127                                   char *canonname, int canonnamelen)
128 {
129         bool            found = false;
130         DIR                *dirdesc;
131         struct dirent *direntry;
132
133         dirdesc = AllocateDir(dirname);
134         if (!dirdesc)
135         {
136                 ereport(LOG,
137                                 (errcode_for_file_access(),
138                                  errmsg("could not open directory \"%s\": %m", dirname)));
139                 return false;
140         }
141
142         while ((direntry = ReadDir(dirdesc, dirname)) != NULL)
143         {
144                 /*
145                  * Ignore . and .., plus any other "hidden" files.  This is a security
146                  * measure to prevent access to files outside the timezone directory.
147                  */
148                 if (direntry->d_name[0] == '.')
149                         continue;
150
151                 if (strlen(direntry->d_name) == fnamelen &&
152                         pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
153                 {
154                         /* Found our match */
155                         strlcpy(canonname, direntry->d_name, canonnamelen);
156                         found = true;
157                         break;
158                 }
159         }
160
161         FreeDir(dirdesc);
162
163         return found;
164 }
165
166
167 /*
168  * We keep loaded timezones in a hashtable so we don't have to
169  * load and parse the TZ definition file every time one is selected.
170  * Because we want timezone names to be found case-insensitively,
171  * the hash key is the uppercased name of the zone.
172  */
173 typedef struct
174 {
175         /* tznameupper contains the all-upper-case name of the timezone */
176         char            tznameupper[TZ_STRLEN_MAX + 1];
177         pg_tz           tz;
178 } pg_tz_cache;
179
180 static HTAB *timezone_cache = NULL;
181
182
183 static bool
184 init_timezone_hashtable(void)
185 {
186         HASHCTL         hash_ctl;
187
188         MemSet(&hash_ctl, 0, sizeof(hash_ctl));
189
190         hash_ctl.keysize = TZ_STRLEN_MAX + 1;
191         hash_ctl.entrysize = sizeof(pg_tz_cache);
192
193         timezone_cache = hash_create("Timezones",
194                                                                  4,
195                                                                  &hash_ctl,
196                                                                  HASH_ELEM);
197         if (!timezone_cache)
198                 return false;
199
200         return true;
201 }
202
203 /*
204  * Load a timezone from file or from cache.
205  * Does not verify that the timezone is acceptable!
206  *
207  * "GMT" is always interpreted as the tzparse() definition, without attempting
208  * to load a definition from the filesystem.  This has a number of benefits:
209  * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
210  * the bootstrap default timezone setting doesn't work (as could happen if
211  * the OS attempts to supply a leap-second-aware version of "GMT").
212  * 2. Because we aren't accessing the filesystem, we can safely initialize
213  * the "GMT" zone definition before my_exec_path is known.
214  * 3. It's quick enough that we don't waste much time when the bootstrap
215  * default timezone setting is later overridden from postgresql.conf.
216  */
217 pg_tz *
218 pg_tzset(const char *name)
219 {
220         pg_tz_cache *tzp;
221         struct state tzstate;
222         char            uppername[TZ_STRLEN_MAX + 1];
223         char            canonname[TZ_STRLEN_MAX + 1];
224         char       *p;
225
226         if (strlen(name) > TZ_STRLEN_MAX)
227                 return NULL;                    /* not going to fit */
228
229         if (!timezone_cache)
230                 if (!init_timezone_hashtable())
231                         return NULL;
232
233         /*
234          * Upcase the given name to perform a case-insensitive hashtable search.
235          * (We could alternatively downcase it, but we prefer upcase so that we
236          * can get consistently upcased results from tzparse() in case the name is
237          * a POSIX-style timezone spec.)
238          */
239         p = uppername;
240         while (*name)
241                 *p++ = pg_toupper((unsigned char) *name++);
242         *p = '\0';
243
244         tzp = (pg_tz_cache *) hash_search(timezone_cache,
245                                                                           uppername,
246                                                                           HASH_FIND,
247                                                                           NULL);
248         if (tzp)
249         {
250                 /* Timezone found in cache, nothing more to do */
251                 return &tzp->tz;
252         }
253
254         /*
255          * "GMT" is always sent to tzparse(), as per discussion above.
256          */
257         if (strcmp(uppername, "GMT") == 0)
258         {
259                 if (!tzparse(uppername, &tzstate, true))
260                 {
261                         /* This really, really should not happen ... */
262                         elog(ERROR, "could not initialize GMT time zone");
263                 }
264                 /* Use uppercase name as canonical */
265                 strcpy(canonname, uppername);
266         }
267         else if (tzload(uppername, canonname, &tzstate, true) != 0)
268         {
269                 if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false))
270                 {
271                         /* Unknown timezone. Fail our call instead of loading GMT! */
272                         return NULL;
273                 }
274                 /* For POSIX timezone specs, use uppercase name as canonical */
275                 strcpy(canonname, uppername);
276         }
277
278         /* Save timezone in the cache */
279         tzp = (pg_tz_cache *) hash_search(timezone_cache,
280                                                                           uppername,
281                                                                           HASH_ENTER,
282                                                                           NULL);
283
284         /* hash_search already copied uppername into the hash key */
285         strcpy(tzp->tz.TZname, canonname);
286         memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
287
288         return &tzp->tz;
289 }
290
291 /*
292  * Load a fixed-GMT-offset timezone.
293  * This is used for SQL-spec SET TIME ZONE INTERVAL 'foo' cases.
294  * It's otherwise equivalent to pg_tzset().
295  *
296  * The GMT offset is specified in seconds, positive values meaning west of
297  * Greenwich (ie, POSIX not ISO sign convention).  However, we use ISO
298  * sign convention in the displayable abbreviation for the zone.
299  *
300  * Caution: this can fail (return NULL) if the specified offset is outside
301  * the range allowed by the zic library.
302  */
303 pg_tz *
304 pg_tzset_offset(long gmtoffset)
305 {
306         long            absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset;
307         char            offsetstr[64];
308         char            tzname[128];
309
310         snprintf(offsetstr, sizeof(offsetstr),
311                          "%02ld", absoffset / SECSPERHOUR);
312         absoffset %= SECSPERHOUR;
313         if (absoffset != 0)
314         {
315                 snprintf(offsetstr + strlen(offsetstr),
316                                  sizeof(offsetstr) - strlen(offsetstr),
317                                  ":%02ld", absoffset / SECSPERMIN);
318                 absoffset %= SECSPERMIN;
319                 if (absoffset != 0)
320                         snprintf(offsetstr + strlen(offsetstr),
321                                          sizeof(offsetstr) - strlen(offsetstr),
322                                          ":%02ld", absoffset);
323         }
324         if (gmtoffset > 0)
325                 snprintf(tzname, sizeof(tzname), "<-%s>+%s",
326                                  offsetstr, offsetstr);
327         else
328                 snprintf(tzname, sizeof(tzname), "<+%s>-%s",
329                                  offsetstr, offsetstr);
330
331         return pg_tzset(tzname);
332 }
333
334
335 /*
336  * Initialize timezone library
337  *
338  * This is called before GUC variable initialization begins.  Its purpose
339  * is to ensure that log_timezone has a valid value before any logging GUC
340  * variables could become set to values that require elog.c to provide
341  * timestamps (e.g., log_line_prefix).  We may as well initialize
342  * session_timestamp to something valid, too.
343  */
344 void
345 pg_timezone_initialize(void)
346 {
347         /*
348          * We may not yet know where PGSHAREDIR is (in particular this is true in
349          * an EXEC_BACKEND subprocess).  So use "GMT", which pg_tzset forces to be
350          * interpreted without reference to the filesystem.  This corresponds to
351          * the bootstrap default for these variables in guc.c, although in
352          * principle it could be different.
353          */
354         session_timezone = pg_tzset("GMT");
355         log_timezone = session_timezone;
356 }
357
358
359 /*
360  * Functions to enumerate available timezones
361  *
362  * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
363  * structure, so the data is only valid up to the next call.
364  *
365  * All data is allocated using palloc in the current context.
366  */
367 #define MAX_TZDIR_DEPTH 10
368
369 struct pg_tzenum
370 {
371         int                     baselen;
372         int                     depth;
373         DIR                *dirdesc[MAX_TZDIR_DEPTH];
374         char       *dirname[MAX_TZDIR_DEPTH];
375         struct pg_tz tz;
376 };
377
378 /* typedef pg_tzenum is declared in pgtime.h */
379
380 pg_tzenum *
381 pg_tzenumerate_start(void)
382 {
383         pg_tzenum  *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum));
384         char       *startdir = pstrdup(pg_TZDIR());
385
386         ret->baselen = strlen(startdir) + 1;
387         ret->depth = 0;
388         ret->dirname[0] = startdir;
389         ret->dirdesc[0] = AllocateDir(startdir);
390         if (!ret->dirdesc[0])
391                 ereport(ERROR,
392                                 (errcode_for_file_access(),
393                                  errmsg("could not open directory \"%s\": %m", startdir)));
394         return ret;
395 }
396
397 void
398 pg_tzenumerate_end(pg_tzenum *dir)
399 {
400         while (dir->depth >= 0)
401         {
402                 FreeDir(dir->dirdesc[dir->depth]);
403                 pfree(dir->dirname[dir->depth]);
404                 dir->depth--;
405         }
406         pfree(dir);
407 }
408
409 pg_tz *
410 pg_tzenumerate_next(pg_tzenum *dir)
411 {
412         while (dir->depth >= 0)
413         {
414                 struct dirent *direntry;
415                 char            fullname[MAXPGPATH];
416                 struct stat statbuf;
417
418                 direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
419
420                 if (!direntry)
421                 {
422                         /* End of this directory */
423                         FreeDir(dir->dirdesc[dir->depth]);
424                         pfree(dir->dirname[dir->depth]);
425                         dir->depth--;
426                         continue;
427                 }
428
429                 if (direntry->d_name[0] == '.')
430                         continue;
431
432                 snprintf(fullname, MAXPGPATH, "%s/%s",
433                                  dir->dirname[dir->depth], direntry->d_name);
434                 if (stat(fullname, &statbuf) != 0)
435                         ereport(ERROR,
436                                         (errcode_for_file_access(),
437                                          errmsg("could not stat \"%s\": %m", fullname)));
438
439                 if (S_ISDIR(statbuf.st_mode))
440                 {
441                         /* Step into the subdirectory */
442                         if (dir->depth >= MAX_TZDIR_DEPTH - 1)
443                                 ereport(ERROR,
444                                          (errmsg_internal("timezone directory stack overflow")));
445                         dir->depth++;
446                         dir->dirname[dir->depth] = pstrdup(fullname);
447                         dir->dirdesc[dir->depth] = AllocateDir(fullname);
448                         if (!dir->dirdesc[dir->depth])
449                                 ereport(ERROR,
450                                                 (errcode_for_file_access(),
451                                                  errmsg("could not open directory \"%s\": %m",
452                                                                 fullname)));
453
454                         /* Start over reading in the new directory */
455                         continue;
456                 }
457
458                 /*
459                  * Load this timezone using tzload() not pg_tzset(), so we don't fill
460                  * the cache
461                  */
462                 if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state,
463                                    true) != 0)
464                 {
465                         /* Zone could not be loaded, ignore it */
466                         continue;
467                 }
468
469                 if (!pg_tz_acceptable(&dir->tz))
470                 {
471                         /* Ignore leap-second zones */
472                         continue;
473                 }
474
475                 /* Timezone loaded OK. */
476                 return &dir->tz;
477         }
478
479         /* Nothing more found */
480         return NULL;
481 }