LCOV - code coverage report
Current view: top level - src/timezone - pgtz.c (source / functions) Hit Total Coverage
Test: PostgreSQL Lines: 132 151 87.4 %
Date: 2017-09-29 13:40:31 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.11