Since the switch to 64-bit time_t, our tar(1) can not archive files
which timestamp are before 1970.

Assuming these files:
$ ls -l *gz
-rw-r--r--  1 miod  dmg   34827 Jul  1  1904 comandr.mod.gz
-rw-r--r--  1 miod  dmg  119280 Dec  3  1969 gbusa.mod.gz

tar will fail to archive them:
$ tar cf - *gz
tar: Ustar header field is too small for gbusa.mod.gz
tar: Ustar header field is too small for comandr.mod.gz

Since the ustar file format stores the timestamp a width-limited octal
value, which is expected to match a signed 32-bit value, it makes sense
to handle the ustar timestamp as a 32-bit value (and switch to a
different tar flavour by default at some point before 2038).

The following diff allows me to archive these files correctly.

Index: tar.c
===================================================================
RCS file: /cvs/src/bin/pax/tar.c,v
retrieving revision 1.49
diff -u -p -r1.49 tar.c
--- tar.c       21 Nov 2013 15:54:45 -0000      1.49
+++ tar.c       9 Dec 2013 20:04:40 -0000
@@ -52,9 +52,11 @@
  * Routines for reading, writing and header identify of various versions of tar
  */
 
+static time_t asc_time(char *, int, int);
 static size_t expandname(char *, size_t, char **, const char *, size_t);
 static u_long tar_chksm(char *, int);
 static char *name_split(char *, int);
+static int time_oct(time_t, char *, int, int);
 static int ul_oct(u_long, char *, int, int);
 static int uqd_oct(u_quad_t, char *, int, int);
 #ifndef SMALL
@@ -144,6 +146,43 @@ tar_trail(ARCHD *ignore, char *buf, int 
 }
 
 /*
+ * asc_time()
+ *     convert hex/octal character string into a time_t. We do not have to
+ *     check for overflow! (the headers in all supported formats are not large
+ *     enough to create an overflow).
+ *     NOTE: strings passed to us are NOT TERMINATED.
+ * Return:
+ *     time_t value
+ */
+
+static time_t
+asc_time(char *str, int len, int base)
+{
+       u_quad_t q;
+
+       q = asc_uqd(str, len, base);
+       return (time_t)(int32_t)q;
+}
+
+/*
+ * time_oct()
+ *     convert a time_t to an octal string. This is actually a wrapper
+ *     over either ul_oct(), if the time_t fits in a signed 32-bit value,
+ *     or uqd_oct().
+ * Return:
+ *     0 if the number fit into the string, -1 otherwise
+ */
+
+static int
+time_oct(time_t val, char *str, int len, int term)
+{
+       if (val == (int32_t)val)
+               return ul_oct((u_long)(int32_t)val, str, len, term);
+       else
+               return uqd_oct((u_quad_t)val, str, len, term);
+}
+
+/*
  * ul_oct()
  *     convert an unsigned long to an octal string. many oddball field
  *     termination characters are used by the various versions of tar in the
@@ -378,7 +417,6 @@ int
 tar_rd(ARCHD *arcn, char *buf)
 {
        HD_TAR *hd;
-       u_quad_t val;
        char *pt;
 
        /*
@@ -409,11 +447,7 @@ tar_rd(ARCHD *arcn, char *buf)
 #else
        arcn->sb.st_size = (off_t)asc_uqd(hd->size, sizeof(hd->size), OCT);
 #endif
-       val = asc_uqd(hd->mtime, sizeof(hd->mtime), OCT);
-       if ((time_t)val < 0 || (time_t)val != val)
-               arcn->sb.st_mtime = INT_MAX;                    /* XXX 2038 */
-       else
-               arcn->sb.st_mtime = val;
+       arcn->sb.st_mtime = asc_time(hd->mtime, sizeof(hd->mtime), OCT);
        arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
 
        /*
@@ -638,7 +672,7 @@ tar_wr(ARCHD *arcn)
        if (ul_oct((u_long)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 0) ||
            ul_oct((u_long)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 0) ||
            ul_oct((u_long)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 0) ||
-           uqd_oct(arcn->sb.st_mtime, hd->mtime, sizeof(hd->mtime), 1))
+           time_oct(arcn->sb.st_mtime, hd->mtime, sizeof(hd->mtime), 1))
                goto out;
 
        /*
@@ -747,7 +781,6 @@ ustar_rd(ARCHD *arcn, char *buf)
        int cnt = 0;
        dev_t devmajor;
        dev_t devminor;
-       u_quad_t val;
 
        /*
         * we only get proper sized buffers
@@ -810,11 +843,7 @@ ustar_rd(ARCHD *arcn, char *buf)
 #else
        arcn->sb.st_size = (off_t)asc_uqd(hd->size, sizeof(hd->size), OCT);
 #endif
-       val = asc_uqd(hd->mtime, sizeof(hd->mtime), OCT);
-       if ((time_t)val < 0 || (time_t)val != val)
-               arcn->sb.st_mtime = INT_MAX;                    /* XXX 2038 */
-       else
-               arcn->sb.st_mtime = val;
+       arcn->sb.st_mtime = asc_time(hd->mtime, sizeof(hd->mtime), OCT);
        arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
 
        /*
@@ -1094,7 +1123,7 @@ ustar_wr(ARCHD *arcn)
                        goto out;
        }
        if (ul_oct((u_long)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 3) ||
-           uqd_oct(arcn->sb.st_mtime, hd->mtime, sizeof(hd->mtime), 3))
+           time_oct(arcn->sb.st_mtime, hd->mtime, sizeof(hd->mtime), 3))
                goto out;
        if (!Nflag) {
                strncpy(hd->uname, name_uid(arcn->sb.st_uid, 0), 
sizeof(hd->uname));

Reply via email to