Header And Logo

PostgreSQL
| The world's most advanced open source database.

pgtz.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * pgtz.c
00004  *    Timezone Library Integration Functions
00005  *
00006  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
00007  *
00008  * IDENTIFICATION
00009  *    src/timezone/pgtz.c
00010  *
00011  *-------------------------------------------------------------------------
00012  */
00013 #include "postgres.h"
00014 
00015 #include <ctype.h>
00016 #include <fcntl.h>
00017 #include <sys/stat.h>
00018 #include <time.h>
00019 
00020 #include "miscadmin.h"
00021 #include "pgtz.h"
00022 #include "storage/fd.h"
00023 #include "utils/hsearch.h"
00024 
00025 
00026 /* Current session timezone (controlled by TimeZone GUC) */
00027 pg_tz      *session_timezone = NULL;
00028 
00029 /* Current log timezone (controlled by log_timezone GUC) */
00030 pg_tz      *log_timezone = NULL;
00031 
00032 
00033 static bool scan_directory_ci(const char *dirname,
00034                   const char *fname, int fnamelen,
00035                   char *canonname, int canonnamelen);
00036 
00037 
00038 /*
00039  * Return full pathname of timezone data directory
00040  */
00041 static const char *
00042 pg_TZDIR(void)
00043 {
00044 #ifndef SYSTEMTZDIR
00045     /* normal case: timezone stuff is under our share dir */
00046     static bool done_tzdir = false;
00047     static char tzdir[MAXPGPATH];
00048 
00049     if (done_tzdir)
00050         return tzdir;
00051 
00052     get_share_path(my_exec_path, tzdir);
00053     strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
00054 
00055     done_tzdir = true;
00056     return tzdir;
00057 #else
00058     /* we're configured to use system's timezone database */
00059     return SYSTEMTZDIR;
00060 #endif
00061 }
00062 
00063 
00064 /*
00065  * Given a timezone name, open() the timezone data file.  Return the
00066  * file descriptor if successful, -1 if not.
00067  *
00068  * The input name is searched for case-insensitively (we assume that the
00069  * timezone database does not contain case-equivalent names).
00070  *
00071  * If "canonname" is not NULL, then on success the canonical spelling of the
00072  * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
00073  */
00074 int
00075 pg_open_tzfile(const char *name, char *canonname)
00076 {
00077     const char *fname;
00078     char        fullname[MAXPGPATH];
00079     int         fullnamelen;
00080     int         orignamelen;
00081 
00082     /*
00083      * Loop to split the given name into directory levels; for each level,
00084      * search using scan_directory_ci().
00085      */
00086     strcpy(fullname, pg_TZDIR());
00087     orignamelen = fullnamelen = strlen(fullname);
00088     fname = name;
00089     for (;;)
00090     {
00091         const char *slashptr;
00092         int         fnamelen;
00093 
00094         slashptr = strchr(fname, '/');
00095         if (slashptr)
00096             fnamelen = slashptr - fname;
00097         else
00098             fnamelen = strlen(fname);
00099         if (fullnamelen + 1 + fnamelen >= MAXPGPATH)
00100             return -1;          /* not gonna fit */
00101         if (!scan_directory_ci(fullname, fname, fnamelen,
00102                                fullname + fullnamelen + 1,
00103                                MAXPGPATH - fullnamelen - 1))
00104             return -1;
00105         fullname[fullnamelen++] = '/';
00106         fullnamelen += strlen(fullname + fullnamelen);
00107         if (slashptr)
00108             fname = slashptr + 1;
00109         else
00110             break;
00111     }
00112 
00113     if (canonname)
00114         strlcpy(canonname, fullname + orignamelen + 1, TZ_STRLEN_MAX + 1);
00115 
00116     return open(fullname, O_RDONLY | PG_BINARY, 0);
00117 }
00118 
00119 
00120 /*
00121  * Scan specified directory for a case-insensitive match to fname
00122  * (of length fnamelen --- fname may not be null terminated!).  If found,
00123  * copy the actual filename into canonname and return true.
00124  */
00125 static bool
00126 scan_directory_ci(const char *dirname, const char *fname, int fnamelen,
00127                   char *canonname, int canonnamelen)
00128 {
00129     bool        found = false;
00130     DIR        *dirdesc;
00131     struct dirent *direntry;
00132 
00133     dirdesc = AllocateDir(dirname);
00134     if (!dirdesc)
00135     {
00136         ereport(LOG,
00137                 (errcode_for_file_access(),
00138                  errmsg("could not open directory \"%s\": %m", dirname)));
00139         return false;
00140     }
00141 
00142     while ((direntry = ReadDir(dirdesc, dirname)) != NULL)
00143     {
00144         /*
00145          * Ignore . and .., plus any other "hidden" files.  This is a security
00146          * measure to prevent access to files outside the timezone directory.
00147          */
00148         if (direntry->d_name[0] == '.')
00149             continue;
00150 
00151         if (strlen(direntry->d_name) == fnamelen &&
00152             pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
00153         {
00154             /* Found our match */
00155             strlcpy(canonname, direntry->d_name, canonnamelen);
00156             found = true;
00157             break;
00158         }
00159     }
00160 
00161     FreeDir(dirdesc);
00162 
00163     return found;
00164 }
00165 
00166 
00167 /*
00168  * We keep loaded timezones in a hashtable so we don't have to
00169  * load and parse the TZ definition file every time one is selected.
00170  * Because we want timezone names to be found case-insensitively,
00171  * the hash key is the uppercased name of the zone.
00172  */
00173 typedef struct
00174 {
00175     /* tznameupper contains the all-upper-case name of the timezone */
00176     char        tznameupper[TZ_STRLEN_MAX + 1];
00177     pg_tz       tz;
00178 } pg_tz_cache;
00179 
00180 static HTAB *timezone_cache = NULL;
00181 
00182 
00183 static bool
00184 init_timezone_hashtable(void)
00185 {
00186     HASHCTL     hash_ctl;
00187 
00188     MemSet(&hash_ctl, 0, sizeof(hash_ctl));
00189 
00190     hash_ctl.keysize = TZ_STRLEN_MAX + 1;
00191     hash_ctl.entrysize = sizeof(pg_tz_cache);
00192 
00193     timezone_cache = hash_create("Timezones",
00194                                  4,
00195                                  &hash_ctl,
00196                                  HASH_ELEM);
00197     if (!timezone_cache)
00198         return false;
00199 
00200     return true;
00201 }
00202 
00203 /*
00204  * Load a timezone from file or from cache.
00205  * Does not verify that the timezone is acceptable!
00206  *
00207  * "GMT" is always interpreted as the tzparse() definition, without attempting
00208  * to load a definition from the filesystem.  This has a number of benefits:
00209  * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
00210  * the bootstrap default timezone setting doesn't work (as could happen if
00211  * the OS attempts to supply a leap-second-aware version of "GMT").
00212  * 2. Because we aren't accessing the filesystem, we can safely initialize
00213  * the "GMT" zone definition before my_exec_path is known.
00214  * 3. It's quick enough that we don't waste much time when the bootstrap
00215  * default timezone setting is later overridden from postgresql.conf.
00216  */
00217 pg_tz *
00218 pg_tzset(const char *name)
00219 {
00220     pg_tz_cache *tzp;
00221     struct state tzstate;
00222     char        uppername[TZ_STRLEN_MAX + 1];
00223     char        canonname[TZ_STRLEN_MAX + 1];
00224     char       *p;
00225 
00226     if (strlen(name) > TZ_STRLEN_MAX)
00227         return NULL;            /* not going to fit */
00228 
00229     if (!timezone_cache)
00230         if (!init_timezone_hashtable())
00231             return NULL;
00232 
00233     /*
00234      * Upcase the given name to perform a case-insensitive hashtable search.
00235      * (We could alternatively downcase it, but we prefer upcase so that we
00236      * can get consistently upcased results from tzparse() in case the name is
00237      * a POSIX-style timezone spec.)
00238      */
00239     p = uppername;
00240     while (*name)
00241         *p++ = pg_toupper((unsigned char) *name++);
00242     *p = '\0';
00243 
00244     tzp = (pg_tz_cache *) hash_search(timezone_cache,
00245                                       uppername,
00246                                       HASH_FIND,
00247                                       NULL);
00248     if (tzp)
00249     {
00250         /* Timezone found in cache, nothing more to do */
00251         return &tzp->tz;
00252     }
00253 
00254     /*
00255      * "GMT" is always sent to tzparse(), as per discussion above.
00256      */
00257     if (strcmp(uppername, "GMT") == 0)
00258     {
00259         if (tzparse(uppername, &tzstate, TRUE) != 0)
00260         {
00261             /* This really, really should not happen ... */
00262             elog(ERROR, "could not initialize GMT time zone");
00263         }
00264         /* Use uppercase name as canonical */
00265         strcpy(canonname, uppername);
00266     }
00267     else if (tzload(uppername, canonname, &tzstate, TRUE) != 0)
00268     {
00269         if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0)
00270         {
00271             /* Unknown timezone. Fail our call instead of loading GMT! */
00272             return NULL;
00273         }
00274         /* For POSIX timezone specs, use uppercase name as canonical */
00275         strcpy(canonname, uppername);
00276     }
00277 
00278     /* Save timezone in the cache */
00279     tzp = (pg_tz_cache *) hash_search(timezone_cache,
00280                                       uppername,
00281                                       HASH_ENTER,
00282                                       NULL);
00283 
00284     /* hash_search already copied uppername into the hash key */
00285     strcpy(tzp->tz.TZname, canonname);
00286     memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
00287 
00288     return &tzp->tz;
00289 }
00290 
00291 
00292 /*
00293  * Initialize timezone library
00294  *
00295  * This is called before GUC variable initialization begins.  Its purpose
00296  * is to ensure that log_timezone has a valid value before any logging GUC
00297  * variables could become set to values that require elog.c to provide
00298  * timestamps (e.g., log_line_prefix).  We may as well initialize
00299  * session_timestamp to something valid, too.
00300  */
00301 void
00302 pg_timezone_initialize(void)
00303 {
00304     /*
00305      * We may not yet know where PGSHAREDIR is (in particular this is true in
00306      * an EXEC_BACKEND subprocess).  So use "GMT", which pg_tzset forces to be
00307      * interpreted without reference to the filesystem.  This corresponds to
00308      * the bootstrap default for these variables in guc.c, although in
00309      * principle it could be different.
00310      */
00311     session_timezone = pg_tzset("GMT");
00312     log_timezone = session_timezone;
00313 }
00314 
00315 
00316 /*
00317  * Functions to enumerate available timezones
00318  *
00319  * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
00320  * structure, so the data is only valid up to the next call.
00321  *
00322  * All data is allocated using palloc in the current context.
00323  */
00324 #define MAX_TZDIR_DEPTH 10
00325 
00326 struct pg_tzenum
00327 {
00328     int         baselen;
00329     int         depth;
00330     DIR        *dirdesc[MAX_TZDIR_DEPTH];
00331     char       *dirname[MAX_TZDIR_DEPTH];
00332     struct pg_tz tz;
00333 };
00334 
00335 /* typedef pg_tzenum is declared in pgtime.h */
00336 
00337 pg_tzenum *
00338 pg_tzenumerate_start(void)
00339 {
00340     pg_tzenum  *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum));
00341     char       *startdir = pstrdup(pg_TZDIR());
00342 
00343     ret->baselen = strlen(startdir) + 1;
00344     ret->depth = 0;
00345     ret->dirname[0] = startdir;
00346     ret->dirdesc[0] = AllocateDir(startdir);
00347     if (!ret->dirdesc[0])
00348         ereport(ERROR,
00349                 (errcode_for_file_access(),
00350                  errmsg("could not open directory \"%s\": %m", startdir)));
00351     return ret;
00352 }
00353 
00354 void
00355 pg_tzenumerate_end(pg_tzenum *dir)
00356 {
00357     while (dir->depth >= 0)
00358     {
00359         FreeDir(dir->dirdesc[dir->depth]);
00360         pfree(dir->dirname[dir->depth]);
00361         dir->depth--;
00362     }
00363     pfree(dir);
00364 }
00365 
00366 pg_tz *
00367 pg_tzenumerate_next(pg_tzenum *dir)
00368 {
00369     while (dir->depth >= 0)
00370     {
00371         struct dirent *direntry;
00372         char        fullname[MAXPGPATH];
00373         struct stat statbuf;
00374 
00375         direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
00376 
00377         if (!direntry)
00378         {
00379             /* End of this directory */
00380             FreeDir(dir->dirdesc[dir->depth]);
00381             pfree(dir->dirname[dir->depth]);
00382             dir->depth--;
00383             continue;
00384         }
00385 
00386         if (direntry->d_name[0] == '.')
00387             continue;
00388 
00389         snprintf(fullname, MAXPGPATH, "%s/%s",
00390                  dir->dirname[dir->depth], direntry->d_name);
00391         if (stat(fullname, &statbuf) != 0)
00392             ereport(ERROR,
00393                     (errcode_for_file_access(),
00394                      errmsg("could not stat \"%s\": %m", fullname)));
00395 
00396         if (S_ISDIR(statbuf.st_mode))
00397         {
00398             /* Step into the subdirectory */
00399             if (dir->depth >= MAX_TZDIR_DEPTH - 1)
00400                 ereport(ERROR,
00401                      (errmsg_internal("timezone directory stack overflow")));
00402             dir->depth++;
00403             dir->dirname[dir->depth] = pstrdup(fullname);
00404             dir->dirdesc[dir->depth] = AllocateDir(fullname);
00405             if (!dir->dirdesc[dir->depth])
00406                 ereport(ERROR,
00407                         (errcode_for_file_access(),
00408                          errmsg("could not open directory \"%s\": %m",
00409                                 fullname)));
00410 
00411             /* Start over reading in the new directory */
00412             continue;
00413         }
00414 
00415         /*
00416          * Load this timezone using tzload() not pg_tzset(), so we don't fill
00417          * the cache
00418          */
00419         if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state,
00420                    TRUE) != 0)
00421         {
00422             /* Zone could not be loaded, ignore it */
00423             continue;
00424         }
00425 
00426         if (!pg_tz_acceptable(&dir->tz))
00427         {
00428             /* Ignore leap-second zones */
00429             continue;
00430         }
00431 
00432         /* Timezone loaded OK. */
00433         return &dir->tz;
00434     }
00435 
00436     /* Nothing more found */
00437     return NULL;
00438 }