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));