Work around portability issue with newer versions of mktime().
authorTom Lane <[email protected]>
Sun, 13 Jun 2021 18:32:42 +0000 (14:32 -0400)
committerTom Lane <[email protected]>
Sun, 13 Jun 2021 18:32:42 +0000 (14:32 -0400)
Recent glibc versions have made mktime() fail if tm_isdst is
inconsistent with the prevailing timezone; in particular it fails for
tm_isdst = 1 when the zone is UTC.  (This seems wildly inconsistent
with the POSIX-mandated treatment of "incorrect" values for the other
fields of struct tm, so if you ask me it's a bug, but I bet they'll
say it's intentional.)  This has been observed to cause cosmetic
problems when pg_restore'ing an archive created in a different
timezone.

To fix, do mktime() using the field values from the archive, and if
that fails try again with tm_isdst = -1.  This will give a result
that's off by the UTC-offset difference from the original zone, but
that was true before, too.  It's not terribly critical since we don't
do anything with the result except possibly print it.  (Someday we
should flush this entire bit of logic and record a standard-format
timestamp in the archive instead.  That's not okay for a back-patched
bug fix, though.)

Also, guard our only other use of mktime() by having initdb's
build_time_t() set tm_isdst = -1 not 0.  This case could only have
an issue in zones that are DST year-round; but I think some do exist,
or could in future.

Per report from Wells Oliver.  Back-patch to all supported
versions, since any of them might need to run with a newer glibc.

Discussion: https://postgr.es/m/CAOC+FBWDhDHO7G-i1_n_hjRzCnUeFO+H-Czi1y10mFhRWpBrew@mail.gmail.com

src/bin/initdb/findtimezone.c
src/bin/pg_dump/pg_backup_archiver.c

index 47c833fc81b031f8bc0e5e5298015e11effc7d85..a087a764be55f63c9613f077b3befdcf207e3d54 100644 (file)
@@ -195,6 +195,7 @@ build_time_t(int year, int month, int day)
    tm.tm_mday = day;
    tm.tm_mon = month - 1;
    tm.tm_year = year - 1900;
+   tm.tm_isdst = -1;
 
    return mktime(&tm);
 }
index a67e110660f9ae3de20cd0c7fed8aa4ab3160fbc..df052a6c46519f85b2285cec1466f6a50ab1a5c0 100644 (file)
@@ -3615,7 +3615,6 @@ void
 ReadHead(ArchiveHandle *AH)
 {
    int         fmt;
-   struct tm   crtm;
 
    /*
     * If we haven't already read the header, do so.
@@ -3683,6 +3682,8 @@ ReadHead(ArchiveHandle *AH)
 
    if (AH->version >= K_VERS_1_4)
    {
+       struct tm   crtm;
+
        crtm.tm_sec = ReadInt(AH);
        crtm.tm_min = ReadInt(AH);
        crtm.tm_hour = ReadInt(AH);
@@ -3691,12 +3692,33 @@ ReadHead(ArchiveHandle *AH)
        crtm.tm_year = ReadInt(AH);
        crtm.tm_isdst = ReadInt(AH);
 
-       AH->archdbname = ReadStr(AH);
-
+       /*
+        * Newer versions of glibc have mktime() report failure if tm_isdst is
+        * inconsistent with the prevailing timezone, e.g. tm_isdst = 1 when
+        * TZ=UTC.  This is problematic when restoring an archive under a
+        * different timezone setting.  If we get a failure, try again with
+        * tm_isdst set to -1 ("don't know").
+        *
+        * XXX with or without this hack, we reconstruct createDate
+        * incorrectly when the prevailing timezone is different from
+        * pg_dump's.  Next time we bump the archive version, we should flush
+        * this representation and store a plain seconds-since-the-Epoch
+        * timestamp instead.
+        */
        AH->createDate = mktime(&crtm);
-
        if (AH->createDate == (time_t) -1)
-           write_msg(modulename, "WARNING: invalid creation date in header\n");
+       {
+           crtm.tm_isdst = -1;
+           AH->createDate = mktime(&crtm);
+           if (AH->createDate == (time_t) -1)
+               write_msg(modulename,
+                         "WARNING: invalid creation date in header\n");
+       }
+   }
+
+   if (AH->version >= K_VERS_1_4)
+   {
+       AH->archdbname = ReadStr(AH);
    }
 
    if (AH->version >= K_VERS_1_10)