OSDN Git Service

Add code to test for unknown timezone names (following some ideas from
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 18 May 2003 01:06:26 +0000 (01:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 18 May 2003 01:06:26 +0000 (01:06 +0000)
Ross Reedstrom, a couple months back) and to detect timezones that are
using leap-second timekeeping.  The unknown-zone-name test is pretty
heuristic and ugly, but it seems better than the old behavior of just
switching to GMT given a bad name.  Also make DecodePosixTimezone() a
tad more robust.

src/backend/commands/variable.c
src/backend/utils/adt/datetime.c
src/include/utils/datetime.h

index 25291d3..aa8d9d3 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.75 2003/04/27 17:31:25 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.76 2003/05/18 01:06:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -235,7 +235,147 @@ show_datestyle(void)
 /*
  * Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
  */
-static char tzbuf[64];
+#define TZBUF_LEN      64
+
+static char tzbuf[TZBUF_LEN];
+
+/*
+ * First time through, we remember the original environment TZ value, if any.
+ */
+static bool have_saved_tz = false;
+static char orig_tzbuf[TZBUF_LEN];
+
+/*
+ * Convenience subroutine for assigning the value of TZ
+ */
+static void
+set_tz(const char *tz)
+{
+       strcpy(tzbuf, "TZ=");
+       strncpy(tzbuf + 3, tz, sizeof(tzbuf) - 4);
+       if (putenv(tzbuf) != 0)         /* shouldn't happen? */
+               elog(LOG, "Unable to set TZ environment variable");
+       tzset();
+}
+
+/*
+ * Remove any value of TZ we have established
+ *
+ * Note: this leaves us with *no* value of TZ in the environment, and
+ * is therefore only appropriate for reverting to that state, not for
+ * reverting to a state where TZ was set to something else.
+ */
+static void
+clear_tz(void)
+{
+       /*
+        * unsetenv() works fine, but is BSD, not POSIX, and is not
+        * available under Solaris, among others. Apparently putenv()
+        * called as below clears the process-specific environment
+        * variables.  Other reasonable arguments to putenv() (e.g.
+        * "TZ=", "TZ", "") result in a core dump (under Linux
+        * anyway). - thomas 1998-01-26
+        */
+       if (tzbuf[0] == 'T')
+       {
+               strcpy(tzbuf, "=");
+               if (putenv(tzbuf) != 0)
+                       elog(LOG, "Unable to clear TZ environment variable");
+               tzset();
+       }
+}
+
+/*
+ * Check whether tzset() succeeded
+ *
+ * Unfortunately, tzset doesn't offer any well-defined way to detect that the
+ * value of TZ was bad.  Often it will just select UTC (GMT) as the effective
+ * timezone.  We use the following heuristics:
+ *
+ * If tzname[1] is a nonempty string, *or* the global timezone variable is
+ * not zero, then tzset must have recognized the TZ value as something
+ * different from UTC.  Return true.
+ *
+ * Otherwise, check to see if the TZ name is a known spelling of "UTC"
+ * (ie, appears in our internal tables as a timezone equivalent to UTC).
+ * If so, accept it.
+ *
+ * This will reject nonstandard spellings of UTC unless tzset() chose to
+ * set tzname[1] as well as tzname[0].  The glibc version of tzset() will
+ * do so, but on other systems we may be tightening the spec a little.
+ *
+ * Another problem is that on some platforms (eg HPUX), if tzset thinks the
+ * input is bogus then it will adopt the system default timezone, which we
+ * really can't tell is not the intended translation of the input.
+ *
+ * Still, it beats failing to detect bad TZ names at all, and a silent
+ * failure mode of adopting the system-wide default is much better than
+ * a silent failure mode of adopting UTC.
+ *
+ * NB: this must NOT elog(ERROR).  The caller must get control back so that
+ * it can restore the old value of TZ if we don't like the new one.
+ */
+static bool
+tzset_succeeded(const char *tz)
+{
+       char            tztmp[TZBUF_LEN];
+       char       *cp;
+       int                     tzval;
+
+       /*
+        * Check first set of heuristics to say that tzset definitely worked.
+        */
+       if (tzname[1] && tzname[1][0] != '\0')
+               return true;
+       if (TIMEZONE_GLOBAL != 0)
+               return true;
+
+       /*
+        * Check for known spellings of "UTC".  Note we must downcase the input
+        * before passing it to DecodePosixTimezone().
+        */
+       StrNCpy(tztmp, tz, sizeof(tztmp));
+       for (cp = tztmp; *cp; cp++)
+               *cp = tolower((unsigned char) *cp);
+       if (DecodePosixTimezone(tztmp, &tzval) == 0)
+               if (tzval == 0)
+                       return true;
+
+       return false;
+}
+
+/*
+ * Check whether timezone is acceptable.
+ *
+ * What we are doing here is checking for leap-second-aware timekeeping.
+ * We need to reject such TZ settings because they'll wreak havoc with our
+ * date/time arithmetic.
+ *
+ * NB: this must NOT elog(ERROR).  The caller must get control back so that
+ * it can restore the old value of TZ if we don't like the new one.
+ */
+static bool
+tz_acceptable(void)
+{
+       struct tm       tt;
+       time_t          time2000;
+
+       /*
+        * To detect leap-second timekeeping, compute the time_t value for
+        * local midnight, 2000-01-01.  Insist that this be a multiple of 60;
+        * any partial-minute offset has to be due to leap seconds.
+        */
+       MemSet(&tt, 0, sizeof(tt));
+       tt.tm_year = 100;
+       tt.tm_mon = 0;
+       tt.tm_mday = 1;
+       tt.tm_isdst = -1;
+       time2000 = mktime(&tt);
+       if ((time2000 % 60) != 0)
+               return false;
+
+       return true;
+}
 
 /*
  * assign_timezone: GUC assign_hook for timezone
@@ -248,6 +388,21 @@ assign_timezone(const char *value, bool doit, bool interactive)
        double          hours;
 
        /*
+        * On first call, see if there is a TZ in the original environment.
+        * Save that value permanently.
+        */
+       if (!have_saved_tz)
+       {
+               char   *orig_tz = getenv("TZ");
+
+               if (orig_tz)
+                       StrNCpy(orig_tzbuf, orig_tz, sizeof(orig_tzbuf));
+               else
+                       orig_tzbuf[0] = '\0';
+               have_saved_tz = true;
+       }
+
+       /*
         * Check for INTERVAL 'foo'
         */
        if (strncasecmp(value, "interval", 8) == 0)
@@ -313,25 +468,36 @@ assign_timezone(const char *value, bool doit, bool interactive)
                else if (strcasecmp(value, "UNKNOWN") == 0)
                {
                        /*
-                        * Clear any TZ value we may have established.
-                        *
-                        * unsetenv() works fine, but is BSD, not POSIX, and is not
-                        * available under Solaris, among others. Apparently putenv()
-                        * called as below clears the process-specific environment
-                        * variables.  Other reasonable arguments to putenv() (e.g.
-                        * "TZ=", "TZ", "") result in a core dump (under Linux
-                        * anyway). - thomas 1998-01-26
+                        * UNKNOWN is the value shown as the "default" for TimeZone
+                        * in guc.c.  We interpret it as meaning the original TZ
+                        * inherited from the environment.  Note that if there is an
+                        * original TZ setting, we will return that rather than UNKNOWN
+                        * as the canonical spelling.
                         */
                        if (doit)
                        {
-                               if (tzbuf[0] == 'T')
+                               bool    ok;
+
+                               /* Revert to original setting of TZ, whatever it was */
+                               if (orig_tzbuf[0])
                                {
-                                       strcpy(tzbuf, "=");
-                                       if (putenv(tzbuf) != 0)
-                                               elog(ERROR, "Unable to clear TZ environment variable");
-                                       tzset();
+                                       set_tz(orig_tzbuf);
+                                       ok = tzset_succeeded(orig_tzbuf) && tz_acceptable();
+                               }
+                               else
+                               {
+                                       clear_tz();
+                                       ok = tz_acceptable();
+                               }
+
+                               if (ok)
+                                       HasCTZSet = false;
+                               else
+                               {
+                                       /* Bogus, so force UTC (equivalent to INTERVAL 0) */
+                                       CTimeZone = 0;
+                                       HasCTZSet = true;
                                }
-                               HasCTZSet = false;
                        }
                }
                else
@@ -339,19 +505,58 @@ assign_timezone(const char *value, bool doit, bool interactive)
                        /*
                         * Otherwise assume it is a timezone name.
                         *
-                        * XXX unfortunately we have no reasonable way to check whether a
-                        * timezone name is good, so we have to just assume that it
-                        * is.
+                        * We have to actually apply the change before we can have any
+                        * hope of checking it.  So, save the old value in case we have
+                        * to back out.  Note that it's possible the old setting is in
+                        * tzbuf, so we'd better copy it.
                         */
-                       if (doit)
+                       char    save_tzbuf[TZBUF_LEN];
+                       char   *save_tz;
+                       bool    known,
+                                       acceptable;
+
+                       save_tz = getenv("TZ");
+                       if (save_tz)
+                               StrNCpy(save_tzbuf, save_tz, sizeof(save_tzbuf));
+
+                       set_tz(value);
+
+                       known = tzset_succeeded(value);
+                       acceptable = tz_acceptable();
+
+                       if (doit && known && acceptable)
                        {
-                               strcpy(tzbuf, "TZ=");
-                               strncat(tzbuf, value, sizeof(tzbuf) - 4);
-                               if (putenv(tzbuf) != 0) /* shouldn't happen? */
-                                       elog(LOG, "assign_timezone: putenv failed");
-                               tzset();
+                               /* Keep the changed TZ */
                                HasCTZSet = false;
                        }
+                       else
+                       {
+                               /*
+                                * Revert to prior TZ setting; note we haven't changed
+                                * HasCTZSet in this path, so if we were previously using
+                                * a fixed offset, we still are.
+                                */
+                               if (save_tz)
+                                       set_tz(save_tzbuf);
+                               else
+                                       clear_tz();
+                               /* Complain if it was bad */
+                               if (!known)
+                               {
+                                       elog(interactive ? ERROR : LOG,
+                                                "unrecognized timezone name \"%s\"",
+                                                value);
+                                       return NULL;
+                               }
+                               if (!acceptable)
+                               {
+                                       elog(interactive ? ERROR : LOG,
+                                                "timezone \"%s\" appears to use leap seconds"
+                                                "\n\tPostgreSQL does not support leap seconds",
+                                                value);
+                                       return NULL;
+                               }
+                       }
                }
        }
 
@@ -369,10 +574,7 @@ assign_timezone(const char *value, bool doit, bool interactive)
                return NULL;
 
        if (HasCTZSet)
-       {
-               snprintf(result, sizeof(tzbuf), "%.5f",
-                                (double) CTimeZone / 3600.0);
-       }
+               snprintf(result, sizeof(tzbuf), "%.5f", (double) CTimeZone / 3600.0);
        else if (tzbuf[0] == 'T')
                strcpy(result, tzbuf + 3);
        else
index abcdf13..31b42d4 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.104 2003/05/04 04:30:15 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.105 2003/05/18 01:06:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,7 +36,6 @@ static int DecodeTime(char *str, int fmask, int *tmask,
 static int     DecodeTimezone(char *str, int *tzp);
 static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
 static int     DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
-static int     DecodePosixTimezone(char *str, int *val);
 static void TrimTrailingZeros(char *str);
 
 
@@ -942,8 +941,6 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                return -1;
 
                                        val = strtol(field[i], &cp, 10);
-                                       if (*cp != '-')
-                                               return -1;
 
                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
                                        /* Get the time zone from the end of the string */
@@ -2555,6 +2552,10 @@ DecodeNumberField(int len, char *str, int fmask,
 /* DecodeTimezone()
  * Interpret string as a numeric timezone.
  *
+ * Return 0 if okay (and set *tzp), nonzero if not okay.
+ *
+ * NB: this must *not* elog on failure; see commands/variable.c.
+ *
  * Note: we allow timezone offsets up to 13:59.  There are places that
  * use +1300 summer time.
  */
@@ -2567,7 +2568,10 @@ DecodeTimezone(char *str, int *tzp)
        char       *cp;
        int                     len;
 
-       /* assume leading character is "+" or "-" */
+       /* leading character must be "+" or "-" */
+       if (*str != '+' && *str != '-')
+               return -1;
+
        hr = strtol((str + 1), &cp, 10);
 
        /* explicit delimiter? */
@@ -2589,6 +2593,7 @@ DecodeTimezone(char *str, int *tzp)
                min = 0;
 
        tz = (hr * 60 + min) * 60;
+
        if (*str == '-')
                tz = -tz;
 
@@ -2601,9 +2606,14 @@ DecodeTimezone(char *str, int *tzp)
  * Interpret string as a POSIX-compatible timezone:
  *     PST-hh:mm
  *     PST+h
+ *     PST
  * - thomas 2000-03-15
+ *
+ * Return 0 if okay (and set *tzp), nonzero if not okay.
+ *
+ * NB: this must *not* elog on failure; see commands/variable.c.
  */
-static int
+int
 DecodePosixTimezone(char *str, int *tzp)
 {
        int                     val,
@@ -2612,13 +2622,21 @@ DecodePosixTimezone(char *str, int *tzp)
        char       *cp;
        char            delim;
 
+       /* advance over name part */
        cp = str;
-       while ((*cp != '\0') && isalpha((unsigned char) *cp))
+       while (*cp && isalpha((unsigned char) *cp))
                cp++;
 
-       if (DecodeTimezone(cp, &tz) != 0)
-               return -1;
+       /* decode offset, if present */
+       if (*cp)
+       {
+               if (DecodeTimezone(cp, &tz) != 0)
+                       return -1;
+       }
+       else
+               tz = 0;
 
+       /* decode name part.  We must temporarily scribble on the input! */
        delim = *cp;
        *cp = '\0';
        type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val);
@@ -2641,8 +2659,12 @@ DecodePosixTimezone(char *str, int *tzp)
 
 /* DecodeSpecial()
  * Decode text string using lookup table.
+ *
  * Implement a cache lookup since it is likely that dates
  *     will be related in format.
+ *
+ * NB: this must *not* elog on failure;
+ * see commands/variable.c.
  */
 int
 DecodeSpecial(int field, char *lowtoken, int *val)
index 1f43ffb..7623095 100644 (file)
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: datetime.h,v 1.38 2003/04/18 01:03:42 momjian Exp $
+ * $Id: datetime.h,v 1.39 2003/05/18 01:06:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -290,6 +290,8 @@ extern bool ClearDateCache(bool newval, bool doit, bool interactive);
 
 extern int     j2day(int jd);
 
+extern int     DecodePosixTimezone(char *str, int *tzp);
+
 extern bool CheckDateTokenTables(void);
 
 #endif   /* DATETIME_H */