99 *
1010 *
1111 * IDENTIFICATION
12- * $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.75 2003/04/27 17:31 :25 tgl Exp $
12+ * $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.76 2003/05/18 01:06 :25 tgl Exp $
1313 *
1414 *-------------------------------------------------------------------------
1515 */
@@ -235,7 +235,147 @@ show_datestyle(void)
235235/*
236236 * Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
237237 */
238- static char tzbuf [64 ];
238+ #define TZBUF_LEN 64
239+
240+ static char tzbuf [TZBUF_LEN ];
241+
242+ /*
243+ * First time through, we remember the original environment TZ value, if any.
244+ */
245+ static bool have_saved_tz = false;
246+ static char orig_tzbuf [TZBUF_LEN ];
247+
248+ /*
249+ * Convenience subroutine for assigning the value of TZ
250+ */
251+ static void
252+ set_tz (const char * tz )
253+ {
254+ strcpy (tzbuf ,"TZ=" );
255+ strncpy (tzbuf + 3 ,tz ,sizeof (tzbuf )- 4 );
256+ if (putenv (tzbuf )!= 0 )/* shouldn't happen? */
257+ elog (LOG ,"Unable to set TZ environment variable" );
258+ tzset ();
259+ }
260+
261+ /*
262+ * Remove any value of TZ we have established
263+ *
264+ * Note: this leaves us with *no* value of TZ in the environment, and
265+ * is therefore only appropriate for reverting to that state, not for
266+ * reverting to a state where TZ was set to something else.
267+ */
268+ static void
269+ clear_tz (void )
270+ {
271+ /*
272+ * unsetenv() works fine, but is BSD, not POSIX, and is not
273+ * available under Solaris, among others. Apparently putenv()
274+ * called as below clears the process-specific environment
275+ * variables. Other reasonable arguments to putenv() (e.g.
276+ * "TZ=", "TZ", "") result in a core dump (under Linux
277+ * anyway). - thomas 1998-01-26
278+ */
279+ if (tzbuf [0 ]== 'T' )
280+ {
281+ strcpy (tzbuf ,"=" );
282+ if (putenv (tzbuf )!= 0 )
283+ elog (LOG ,"Unable to clear TZ environment variable" );
284+ tzset ();
285+ }
286+ }
287+
288+ /*
289+ * Check whether tzset() succeeded
290+ *
291+ * Unfortunately, tzset doesn't offer any well-defined way to detect that the
292+ * value of TZ was bad. Often it will just select UTC (GMT) as the effective
293+ * timezone. We use the following heuristics:
294+ *
295+ * If tzname[1] is a nonempty string, *or* the global timezone variable is
296+ * not zero, then tzset must have recognized the TZ value as something
297+ * different from UTC. Return true.
298+ *
299+ * Otherwise, check to see if the TZ name is a known spelling of "UTC"
300+ * (ie, appears in our internal tables as a timezone equivalent to UTC).
301+ * If so, accept it.
302+ *
303+ * This will reject nonstandard spellings of UTC unless tzset() chose to
304+ * set tzname[1] as well as tzname[0]. The glibc version of tzset() will
305+ * do so, but on other systems we may be tightening the spec a little.
306+ *
307+ * Another problem is that on some platforms (eg HPUX), if tzset thinks the
308+ * input is bogus then it will adopt the system default timezone, which we
309+ * really can't tell is not the intended translation of the input.
310+ *
311+ * Still, it beats failing to detect bad TZ names at all, and a silent
312+ * failure mode of adopting the system-wide default is much better than
313+ * a silent failure mode of adopting UTC.
314+ *
315+ * NB: this must NOT elog(ERROR). The caller must get control back so that
316+ * it can restore the old value of TZ if we don't like the new one.
317+ */
318+ static bool
319+ tzset_succeeded (const char * tz )
320+ {
321+ char tztmp [TZBUF_LEN ];
322+ char * cp ;
323+ int tzval ;
324+
325+ /*
326+ * Check first set of heuristics to say that tzset definitely worked.
327+ */
328+ if (tzname [1 ]&& tzname [1 ][0 ]!= '\0' )
329+ return true;
330+ if (TIMEZONE_GLOBAL != 0 )
331+ return true;
332+
333+ /*
334+ * Check for known spellings of "UTC". Note we must downcase the input
335+ * before passing it to DecodePosixTimezone().
336+ */
337+ StrNCpy (tztmp ,tz ,sizeof (tztmp ));
338+ for (cp = tztmp ;* cp ;cp ++ )
339+ * cp = tolower ((unsignedchar )* cp );
340+ if (DecodePosixTimezone (tztmp ,& tzval )== 0 )
341+ if (tzval == 0 )
342+ return true;
343+
344+ return false;
345+ }
346+
347+ /*
348+ * Check whether timezone is acceptable.
349+ *
350+ * What we are doing here is checking for leap-second-aware timekeeping.
351+ * We need to reject such TZ settings because they'll wreak havoc with our
352+ * date/time arithmetic.
353+ *
354+ * NB: this must NOT elog(ERROR). The caller must get control back so that
355+ * it can restore the old value of TZ if we don't like the new one.
356+ */
357+ static bool
358+ tz_acceptable (void )
359+ {
360+ struct tm tt ;
361+ time_t time2000 ;
362+
363+ /*
364+ * To detect leap-second timekeeping, compute the time_t value for
365+ * local midnight, 2000-01-01. Insist that this be a multiple of 60;
366+ * any partial-minute offset has to be due to leap seconds.
367+ */
368+ MemSet (& tt ,0 ,sizeof (tt ));
369+ tt .tm_year = 100 ;
370+ tt .tm_mon = 0 ;
371+ tt .tm_mday = 1 ;
372+ tt .tm_isdst = -1 ;
373+ time2000 = mktime (& tt );
374+ if ((time2000 %60 )!= 0 )
375+ return false;
376+
377+ return true;
378+ }
239379
240380/*
241381 * assign_timezone: GUC assign_hook for timezone
@@ -247,6 +387,21 @@ assign_timezone(const char *value, bool doit, bool interactive)
247387char * endptr ;
248388double hours ;
249389
390+ /*
391+ * On first call, see if there is a TZ in the original environment.
392+ * Save that value permanently.
393+ */
394+ if (!have_saved_tz )
395+ {
396+ char * orig_tz = getenv ("TZ" );
397+
398+ if (orig_tz )
399+ StrNCpy (orig_tzbuf ,orig_tz ,sizeof (orig_tzbuf ));
400+ else
401+ orig_tzbuf [0 ]= '\0' ;
402+ have_saved_tz = true;
403+ }
404+
250405/*
251406 * Check for INTERVAL 'foo'
252407 */
@@ -313,45 +468,95 @@ assign_timezone(const char *value, bool doit, bool interactive)
313468else if (strcasecmp (value ,"UNKNOWN" )== 0 )
314469{
315470/*
316- * Clear any TZ value we may have established.
317- *
318- * unsetenv() works fine, but is BSD, not POSIX, and is not
319- * available under Solaris, among others. Apparently putenv()
320- * called as below clears the process-specific environment
321- * variables. Other reasonable arguments to putenv() (e.g.
322- * "TZ=", "TZ", "") result in a core dump (under Linux
323- * anyway). - thomas 1998-01-26
471+ * UNKNOWN is the value shown as the "default" for TimeZone
472+ * in guc.c. We interpret it as meaning the original TZ
473+ * inherited from the environment. Note that if there is an
474+ * original TZ setting, we will return that rather than UNKNOWN
475+ * as the canonical spelling.
324476 */
325477if (doit )
326478{
327- if (tzbuf [0 ]== 'T' )
479+ bool ok ;
480+
481+ /* Revert to original setting of TZ, whatever it was */
482+ if (orig_tzbuf [0 ])
328483{
329- strcpy (tzbuf ,"=" );
330- if (putenv (tzbuf )!= 0 )
331- elog (ERROR ,"Unable to clear TZ environment variable" );
332- tzset ();
484+ set_tz (orig_tzbuf );
485+ ok = tzset_succeeded (orig_tzbuf )&& tz_acceptable ();
486+ }
487+ else
488+ {
489+ clear_tz ();
490+ ok = tz_acceptable ();
491+ }
492+
493+ if (ok )
494+ HasCTZSet = false;
495+ else
496+ {
497+ /* Bogus, so force UTC (equivalent to INTERVAL 0) */
498+ CTimeZone = 0 ;
499+ HasCTZSet = true;
333500}
334- HasCTZSet = false;
335501}
336502}
337503else
338504{
339505/*
340506 * Otherwise assume it is a timezone name.
341507 *
342- * XXX unfortunately we have no reasonable way to check whether a
343- * timezone name is good, so we have to just assume that it
344- * is.
508+ * We have to actually apply the change before we can have any
509+ * hope of checking it. So, save the old value in case we have
510+ * to back out. Note that it's possible the old setting is in
511+ * tzbuf, so we'd better copy it.
345512 */
346- if (doit )
513+ char save_tzbuf [TZBUF_LEN ];
514+ char * save_tz ;
515+ bool known ,
516+ acceptable ;
517+
518+ save_tz = getenv ("TZ" );
519+ if (save_tz )
520+ StrNCpy (save_tzbuf ,save_tz ,sizeof (save_tzbuf ));
521+
522+ set_tz (value );
523+
524+ known = tzset_succeeded (value );
525+ acceptable = tz_acceptable ();
526+
527+ if (doit && known && acceptable )
347528{
348- strcpy (tzbuf ,"TZ=" );
349- strncat (tzbuf ,value ,sizeof (tzbuf )- 4 );
350- if (putenv (tzbuf )!= 0 )/* shouldn't happen? */
351- elog (LOG ,"assign_timezone: putenv failed" );
352- tzset ();
529+ /* Keep the changed TZ */
353530HasCTZSet = false;
354531}
532+ else
533+ {
534+ /*
535+ * Revert to prior TZ setting; note we haven't changed
536+ * HasCTZSet in this path, so if we were previously using
537+ * a fixed offset, we still are.
538+ */
539+ if (save_tz )
540+ set_tz (save_tzbuf );
541+ else
542+ clear_tz ();
543+ /* Complain if it was bad */
544+ if (!known )
545+ {
546+ elog (interactive ?ERROR :LOG ,
547+ "unrecognized timezone name \"%s\"" ,
548+ value );
549+ return NULL ;
550+ }
551+ if (!acceptable )
552+ {
553+ elog (interactive ?ERROR :LOG ,
554+ "timezone \"%s\" appears to use leap seconds"
555+ "\n\tPostgreSQL does not support leap seconds" ,
556+ value );
557+ return NULL ;
558+ }
559+ }
355560}
356561}
357562
@@ -369,10 +574,7 @@ assign_timezone(const char *value, bool doit, bool interactive)
369574return NULL ;
370575
371576if (HasCTZSet )
372- {
373- snprintf (result ,sizeof (tzbuf ),"%.5f" ,
374- (double )CTimeZone /3600.0 );
375- }
577+ snprintf (result ,sizeof (tzbuf ),"%.5f" , (double )CTimeZone /3600.0 );
376578else if (tzbuf [0 ]== 'T' )
377579strcpy (result ,tzbuf + 3 );
378580else