9
9
*
10
10
*
11
11
* 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 $
13
13
*
14
14
*-------------------------------------------------------------------------
15
15
*/
@@ -235,7 +235,147 @@ show_datestyle(void)
235
235
/*
236
236
* Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
237
237
*/
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 ((unsigned char ) * 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
+ }
239
379
240
380
/*
241
381
* assign_timezone: GUC assign_hook for timezone
@@ -247,6 +387,21 @@ assign_timezone(const char *value, bool doit, bool interactive)
247
387
char * endptr ;
248
388
double hours ;
249
389
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
+
250
405
/*
251
406
* Check for INTERVAL 'foo'
252
407
*/
@@ -313,45 +468,95 @@ assign_timezone(const char *value, bool doit, bool interactive)
313
468
else if (strcasecmp (value , "UNKNOWN" ) == 0 )
314
469
{
315
470
/*
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.
324
476
*/
325
477
if (doit )
326
478
{
327
- if (tzbuf [0 ] == 'T' )
479
+ bool ok ;
480
+
481
+ /* Revert to original setting of TZ, whatever it was */
482
+ if (orig_tzbuf [0 ])
328
483
{
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;
333
500
}
334
- HasCTZSet = false;
335
501
}
336
502
}
337
503
else
338
504
{
339
505
/*
340
506
* Otherwise assume it is a timezone name.
341
507
*
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.
345
512
*/
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 )
347
528
{
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 */
353
530
HasCTZSet = false;
354
531
}
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
+ }
355
560
}
356
561
}
357
562
@@ -369,10 +574,7 @@ assign_timezone(const char *value, bool doit, bool interactive)
369
574
return NULL ;
370
575
371
576
if (HasCTZSet )
372
- {
373
- snprintf (result , sizeof (tzbuf ), "%.5f" ,
374
- (double ) CTimeZone / 3600.0 );
375
- }
577
+ snprintf (result , sizeof (tzbuf ), "%.5f" , (double ) CTimeZone / 3600.0 );
376
578
else if (tzbuf [0 ] == 'T' )
377
579
strcpy (result , tzbuf + 3 );
378
580
else
0 commit comments