Fix some problems with negative and out-of-range integers.

Original problem reported for HP-UX LVM v2.2 by Michael White in
<http://lists.gnu.org/archive/html/bug-tar/2012-10/msg00000.html>.
This patch fixes some other gotchas that I noticed.
* gnulib.modules: Add extern-inline.
* src/common.h: Use _GL_INLINE_HEADER_BEGIN, _GL_INLINE_HEADER_END.
(COMMON_INLINE, max, min): New macros.
(represent_uintmax, valid_timespec): New inline functions.
(SYSINT_BUFSIZE): New constant.
(sysinttostr, strtosysint, decode_timespec): New decls.
* src/create.c (start_private_header): Silently bring the time_t
value into range; it is now the caller's responsibility to deal
with any overflow error.  Use uid 0 and gid 0 rather than the
user's uid/gid, since the faked header isn't "owned" by the user
and the uid/gid could in theory be out of range.  Leave major and
minor zeroed.
(FILL): Remove.
(write_gnu_long_link): Let start_private_header zero things out.
* src/create.c (write_gnu_long_link, write_extended):
* src/xheader.c (xheader_write_global):
Use start_time, not current time; no point hammering on the clock.
* src/compare.c (diff_multivol): Check that offset, size are in range.
* src/incremen.c (read_incr_db_01, write_directory_file_entry):
Allow negative time_t, dev_t, and ino_t.
* src/list.c (max): Remove (moved to common.h).
(read_header): Check that size is in range.
(from_header): Return intmax_t, not uintmax_t, to allow negative.
All callers changed.  At compile time, check assumptions about
intmax_t and uintmax_t.  Use bool for booleans.  Avoid overflow
hassles on picky hosts.
(mode_from_header): Last arg is now bool *, not unsigned *.
All callers changed.
(simple_print_header): Do not assume UID, GID fit in 'long'.
* src/list.c (from_header):
* src/xheader.c (out_of_range_header):
Arg is now a plain minimum value, not minus minval converted to
uintmax_t.  All callers changed.
* src/misc.c (COMMON_INLINE): New macro.
(sysinttostr, strtosysint, decode_timespec): New functions.
* src/sparse.c (oldgnu_add_sparse, oldgnu_fixup_header)
(star_fixup_header):
Check for offset overflow.
(decode_num): Clear errno before calling strtoumax.
* src/tar.c (expand_pax_option): Don't discard nanoseconds.
* src/xheader.c (assign_time_option): Allow negative time_t.
(decode_record): Simplify, since out-of-range string is guaranteed
to produce a value exceeding len_max.
(xheader_read): Last arg is off_t, not size_t.
Caller should diagnose negative arg, as needed.
Check that it's in range.
(enum decode_time_status): Remove.
(_decode_time): Remove, folding into decode_time.
(decode_time): Return bool, not enum decode_time_status.
Rely on decode_timespec to do most of the work.
(code_signed_num): New function.
(code_num): Use it.
(decode_signed_num): New function.
(decode_num): Use it.
(gid_coder, gid_decoder, uid_coder, uid_decoder, sparse_map_decoder)
(sparse_map_decoder): Code and decode negative values.
(sparse_map_decoder): Improve check for out-of-range values.
* tests/time01.at: New file.
* tests/Makefile.am (TESTSUITE_AT): Add it.
* tests/testsuite.at: Include it.
This commit is contained in:
Paul Eggert
2012-12-22 20:41:23 -08:00
parent d8ac237663
commit df7b55a8f6
13 changed files with 472 additions and 315 deletions

View File

@@ -12,6 +12,7 @@ configmake
dirname
error
exclude
extern-inline
exitfail
fchmodat
fchownat

View File

@@ -70,6 +70,11 @@
#define LG_8 3
#define LG_64 6
#define LG_256 8
_GL_INLINE_HEADER_BEGIN
#ifndef COMMON_INLINE
# define COMMON_INLINE _GL_INLINE
#endif
/* Information gleaned from the command line. */
@@ -588,6 +593,8 @@ void skip_member (void);
/* Module misc.c. */
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) < (b) ? (b) : (a))
void assign_string (char **dest, const char *src);
int unquote_string (char *str);
char *zap_slashes (char *name);
@@ -600,11 +607,42 @@ namebuf_t namebuf_create (const char *dir);
void namebuf_free (namebuf_t buf);
char *namebuf_name (namebuf_t buf, const char *name);
/* Represent N using a signed integer I such that (uintmax_t) I == N.
With a good optimizing compiler, this is equivalent to (intmax_t) i
and requires zero machine instructions. */
#if ! (UINTMAX_MAX / 2 <= INTMAX_MAX)
# error "represent_uintmax returns intmax_t to represent uintmax_t"
#endif
COMMON_INLINE intmax_t
represent_uintmax (uintmax_t n)
{
if (n <= INTMAX_MAX)
return n;
else
{
/* Avoid signed integer overflow on picky platforms. */
intmax_t nd = n - INTMAX_MIN;
return nd + INTMAX_MIN;
}
}
enum { SYSINT_BUFSIZE =
max (UINTMAX_STRSIZE_BOUND, INT_BUFSIZE_BOUND (intmax_t)) };
char *sysinttostr (uintmax_t, intmax_t, uintmax_t, char buf[SYSINT_BUFSIZE]);
intmax_t strtosysint (char const *, char **, intmax_t, uintmax_t);
void code_ns_fraction (int ns, char *p);
char const *code_timespec (struct timespec ts, char *sbuf);
enum { BILLION = 1000000000, LOG10_BILLION = 9 };
enum { TIMESPEC_STRSIZE_BOUND =
UINTMAX_STRSIZE_BOUND + LOG10_BILLION + sizeof "-." - 1 };
struct timespec decode_timespec (char const *, char **, bool);
/* Return true if T does not represent an out-of-range or invalid value. */
COMMON_INLINE bool
valid_timespec (struct timespec t)
{
return 0 <= t.tv_nsec;
}
bool must_be_dot_or_slash (char const *);
@@ -730,7 +768,7 @@ void xheader_decode (struct tar_stat_info *stat);
void xheader_decode_global (struct xheader *xhdr);
void xheader_store (char const *keyword, struct tar_stat_info *st,
void const *data);
void xheader_read (struct xheader *xhdr, union block *header, size_t size);
void xheader_read (struct xheader *xhdr, union block *header, off_t size);
void xheader_write (char type, char *name, time_t t, struct xheader *xhdr);
void xheader_write_global (struct xheader *xhdr);
void xheader_finish (struct xheader *hdr);
@@ -858,3 +896,5 @@ void finish_deferred_unlinks (void);
/* Module exit.c */
extern void (*fatal_exit_hook) (void);
_GL_INLINE_HEADER_END

View File

@@ -1,7 +1,8 @@
/* Diff files from a tar archive.
Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
2003, 2004, 2005, 2006, 2007, 2009, 2010, 2012 Free Software
Foundation, Inc.
Written by John Gilmore, on 1987-04-30.
@@ -414,7 +415,9 @@ diff_multivol (void)
}
offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
if (stat_data.st_size != current_stat_info.stat.st_size + offset)
if (offset < 0
|| INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
|| stat_data.st_size != current_stat_info.stat.st_size + offset)
{
report_difference (&current_stat_info, _("Size differs"));
skip_member ();

View File

@@ -512,12 +512,11 @@ start_private_header (const char *name, size_t size, time_t t)
tar_name_copy_str (header->header.name, name, NAME_FIELD_SIZE);
OFF_TO_CHARS (size, header->header.size);
TIME_TO_CHARS (t, header->header.mtime);
TIME_TO_CHARS (t < 0 ? 0 : min (t, MAX_OCTAL_VAL (header->header.mtime)),
header->header.mtime);
MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
UID_TO_CHARS (getuid (), header->header.uid);
GID_TO_CHARS (getgid (), header->header.gid);
MAJOR_TO_CHARS (0, header->header.devmajor);
MINOR_TO_CHARS (0, header->header.devminor);
UID_TO_CHARS (0, header->header.uid);
GID_TO_CHARS (0, header->header.gid);
strncpy (header->header.magic, TMAGIC, TMAGLEN);
strncpy (header->header.version, TVERSION, TVERSLEN);
return header;
@@ -535,11 +534,6 @@ write_short_name (struct tar_stat_info *st)
return header;
}
#define FILL(field,byte) do { \
memset(field, byte, sizeof(field)-1); \
(field)[sizeof(field)-1] = 0; \
} while (0)
/* Write a GNUTYPE_LONGLINK or GNUTYPE_LONGNAME block. */
static void
write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
@@ -549,13 +543,7 @@ write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
union block *header;
char *tmpname;
header = start_private_header ("././@LongLink", size, time (NULL));
FILL (header->header.mtime, '0');
FILL (header->header.mode, '0');
FILL (header->header.uid, '0');
FILL (header->header.gid, '0');
FILL (header->header.devmajor, 0);
FILL (header->header.devminor, 0);
header = start_private_header ("././@LongLink", size, start_time.tv_sec);
uid_to_uname (0, &tmpname);
UNAME_TO_CHARS (tmpname, header->header.uname);
free (tmpname);
@@ -712,7 +700,7 @@ write_extended (bool global, struct tar_stat_info *st, union block *old_header)
{
type = XGLTYPE;
p = xheader_ghdr_name ();
time (&t);
t = start_time.tv_sec;
}
else
{

View File

@@ -447,7 +447,7 @@ procdir (const char *name_buffer, struct tar_stat_info *st,
struct stat *stat_data = &st->stat;
bool nfs = NFS_FILE_STAT (*stat_data);
bool perhaps_renamed = false;
if ((directory = find_directory (name_buffer)) != NULL)
{
if (DIR_IS_INITED (directory))
@@ -520,7 +520,7 @@ procdir (const char *name_buffer, struct tar_stat_info *st,
stat_data->st_ino);
directory = note_directory (name_buffer,
get_stat_mtime(stat_data),
get_stat_mtime (stat_data),
stat_data->st_dev,
stat_data->st_ino,
nfs,
@@ -573,7 +573,7 @@ procdir (const char *name_buffer, struct tar_stat_info *st,
}
perhaps_renamed = false;
}
else if (flag & PD_FORCE_CHILDREN)
{
directory->children = PD_CHILDREN(flag);
@@ -946,8 +946,6 @@ read_incr_db_01 (int version, const char *initbuf)
{
int n;
uintmax_t u;
time_t sec;
long int nsec;
char *buf = NULL;
size_t bufsize = 0;
char *ebuf;
@@ -969,21 +967,15 @@ read_incr_db_01 (int version, const char *initbuf)
bufsize = strlen (buf) + 1;
}
sec = TYPE_MINIMUM (time_t);
nsec = -1;
errno = 0;
u = strtoumax (buf, &ebuf, 10);
if (!errno && TYPE_MAXIMUM (time_t) < u)
errno = ERANGE;
if (errno || buf == ebuf)
newer_mtime_option = decode_timespec (buf, &ebuf, false);
if (! valid_timespec (newer_mtime_option))
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option),
lineno,
_("Invalid time stamp")));
else
{
sec = u;
if (version == 1 && *ebuf)
{
char const *buf_ns = ebuf + 1;
@@ -997,20 +989,13 @@ read_incr_db_01 (int version, const char *initbuf)
quotearg_colon (listed_incremental_option),
lineno,
_("Invalid time stamp")));
sec = TYPE_MINIMUM (time_t);
newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
newer_mtime_option.tv_nsec = -1;
}
else
nsec = u;
}
else
{
/* pre-1 incremental format does not contain nanoseconds */
nsec = 0;
newer_mtime_option.tv_nsec = u;
}
}
newer_mtime_option.tv_sec = sec;
newer_mtime_option.tv_nsec = nsec;
while (0 < (n = getline (&buf, &bufsize, listed_incremental_stream)))
{
@@ -1027,20 +1012,12 @@ read_incr_db_01 (int version, const char *initbuf)
if (version == 1)
{
errno = 0;
u = strtoumax (strp, &ebuf, 10);
if (!errno && TYPE_MAXIMUM (time_t) < u)
errno = ERANGE;
if (errno || strp == ebuf || *ebuf != ' ')
{
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid modification time (seconds)")));
sec = (time_t) -1;
}
else
sec = u;
mtime = decode_timespec (strp, &ebuf, false);
strp = ebuf;
if (!valid_timespec (mtime) || *strp != ' ')
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid modification time")));
errno = 0;
u = strtoumax (strp, &ebuf, 10);
@@ -1051,46 +1028,30 @@ read_incr_db_01 (int version, const char *initbuf)
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid modification time (nanoseconds)")));
nsec = -1;
mtime.tv_nsec = -1;
}
else
nsec = u;
mtime.tv_sec = sec;
mtime.tv_nsec = nsec;
mtime.tv_nsec = u;
strp = ebuf;
}
else
memset (&mtime, 0, sizeof mtime);
mtime.tv_sec = mtime.tv_nsec = 0;
errno = 0;
u = strtoumax (strp, &ebuf, 10);
if (!errno && TYPE_MAXIMUM (dev_t) < u)
errno = ERANGE;
if (errno || strp == ebuf || *ebuf != ' ')
{
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid device number")));
dev = (dev_t) -1;
}
else
dev = u;
dev = strtosysint (strp, &ebuf,
TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t));
strp = ebuf;
if (errno || *strp != ' ')
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid device number")));
errno = 0;
u = strtoumax (strp, &ebuf, 10);
if (!errno && TYPE_MAXIMUM (ino_t) < u)
errno = ERANGE;
if (errno || strp == ebuf || *ebuf != ' ')
{
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid inode number")));
ino = (ino_t) -1;
}
else
ino = u;
ino = strtosysint (strp, &ebuf,
TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t));
strp = ebuf;
if (errno || *strp != ' ')
ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno,
_("Invalid inode number")));
strp++;
unquote_string (strp);
@@ -1391,20 +1352,21 @@ write_directory_file_entry (void *entry, void *data)
if (DIR_IS_FOUND (directory))
{
char buf[UINTMAX_STRSIZE_BOUND];
char buf[max (SYSINT_BUFSIZE, INT_BUFSIZE_BOUND (intmax_t))];
char const *s;
s = DIR_IS_NFS (directory) ? "1" : "0";
fwrite (s, 2, 1, fp);
s = (TYPE_SIGNED (time_t)
? imaxtostr (directory->mtime.tv_sec, buf)
: umaxtostr (directory->mtime.tv_sec, buf));
s = sysinttostr (directory->mtime.tv_sec, TYPE_MINIMUM (time_t),
TYPE_MAXIMUM (time_t), buf);
fwrite (s, strlen (s) + 1, 1, fp);
s = umaxtostr (directory->mtime.tv_nsec, buf);
s = imaxtostr (directory->mtime.tv_nsec, buf);
fwrite (s, strlen (s) + 1, 1, fp);
s = umaxtostr (directory->device_number, buf);
s = sysinttostr (directory->device_number,
TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t), buf);
fwrite (s, strlen (s) + 1, 1, fp);
s = umaxtostr (directory->inode_number, buf);
s = sysinttostr (directory->inode_number,
TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t), buf);
fwrite (s, strlen (s) + 1, 1, fp);
fwrite (directory->name, strlen (directory->name) + 1, 1, fp);

View File

@@ -26,8 +26,6 @@
#include "common.h"
#define max(a, b) ((a) < (b) ? (b) : (a))
union block *current_header; /* points to current archive header */
enum archive_format current_format; /* recognized format */
union block *recent_long_name; /* recent long name header and contents */
@@ -47,11 +45,11 @@ static union block *recent_global_header; /* Recent global header block */
static gid_t gid_from_header (const char *buf, size_t size);
static major_t major_from_header (const char *buf, size_t size);
static minor_t minor_from_header (const char *buf, size_t size);
static mode_t mode_from_header (const char *buf, size_t size, unsigned *hbits);
static mode_t mode_from_header (const char *buf, size_t size, bool *hbits);
static time_t time_from_header (const char *buf, size_t size);
static uid_t uid_from_header (const char *buf, size_t size);
static uintmax_t from_header (const char *, size_t, const char *,
uintmax_t, uintmax_t, bool, bool);
static intmax_t from_header (const char *, size_t, const char *,
intmax_t, uintmax_t, bool, bool);
/* Base 64 digits; see Internet RFC 2045 Table 1. */
static char const base_64_digits[64] =
@@ -318,7 +316,7 @@ tar_checksum (union block *header, bool silent)
int unsigned_sum = 0; /* the POSIX one :-) */
int signed_sum = 0; /* the Sun one :-( */
int recorded_sum;
uintmax_t parsed_sum;
int parsed_sum;
char *p;
p = header->buffer;
@@ -343,9 +341,8 @@ tar_checksum (union block *header, bool silent)
parsed_sum = from_header (header->header.chksum,
sizeof header->header.chksum, 0,
(uintmax_t) 0,
(uintmax_t) TYPE_MAXIMUM (int), true, silent);
if (parsed_sum == (uintmax_t) -1)
0, INT_MAX, true, silent);
if (parsed_sum < 0)
return HEADER_FAILURE;
recorded_sum = parsed_sum;
@@ -408,7 +405,11 @@ read_header (union block **return_block, struct tar_stat_info *info,
if (header->header.typeflag == LNKTYPE)
info->stat.st_size = 0; /* links 0 size on tape */
else
info->stat.st_size = OFF_FROM_HEADER (header->header.size);
{
info->stat.st_size = OFF_FROM_HEADER (header->header.size);
if (info->stat.st_size < 0)
return HEADER_FAILURE;
}
if (header->header.typeflag == GNUTYPE_LONGNAME
|| header->header.typeflag == GNUTYPE_LONGLINK
@@ -573,7 +574,7 @@ decode_header (union block *header, struct tar_stat_info *stat_info,
enum archive_format *format_pointer, int do_user_group)
{
enum archive_format format;
unsigned hbits; /* high bits of the file mode. */
bool hbits;
mode_t mode = MODE_FROM_HEADER (header->header.mode, &hbits);
if (strcmp (header->header.magic, TMAGIC) == 0)
@@ -680,20 +681,30 @@ decode_header (union block *header, struct tar_stat_info *stat_info,
/* Convert buffer at WHERE0 of size DIGS from external format to
uintmax_t. DIGS must be positive. If TYPE is nonnull, the data
are of type TYPE. The buffer must represent a value in the range
-MINUS_MINVAL through MAXVAL. If OCTAL_ONLY, allow only octal
intmax_t. DIGS must be positive. If TYPE is nonnull, the data are
of type TYPE. The buffer must represent a value in the range
MINVAL through MAXVAL; if the mathematically correct result V would
be greater than INTMAX_MAX, return a negative integer V such that
(uintmax_t) V yields the correct result. If OCTAL_ONLY, allow only octal
numbers instead of the other GNU extensions. Return -1 on error,
diagnosing the error if TYPE is nonnull and if !SILENT. */
static uintmax_t
#if ! (INTMAX_MAX <= UINTMAX_MAX && - (INTMAX_MIN + 1) <= UINTMAX_MAX)
# error "from_header internally represents intmax_t as uintmax_t + sign"
#endif
#if ! (UINTMAX_MAX / 2 <= INTMAX_MAX)
# error "from_header returns intmax_t to represent uintmax_t"
#endif
static intmax_t
from_header (char const *where0, size_t digs, char const *type,
uintmax_t minus_minval, uintmax_t maxval,
intmax_t minval, uintmax_t maxval,
bool octal_only, bool silent)
{
uintmax_t value;
uintmax_t uminval = minval;
uintmax_t minus_minval = - uminval;
char const *where = where0;
char const *lim = where + digs;
int negative = 0;
bool negative = false;
/* Accommodate buggy tar of unknown vintage, which outputs leading
NUL if the previous field overflows. */
@@ -721,14 +732,14 @@ from_header (char const *where0, size_t digs, char const *type,
if (ISODIGIT (*where))
{
char const *where1 = where;
uintmax_t overflow = 0;
bool overflow = false;
for (;;)
{
value += *where++ - '0';
if (where == lim || ! ISODIGIT (*where))
break;
overflow |= value ^ (value << LG_8 >> LG_8);
overflow |= value != (value << LG_8 >> LG_8);
value <<= LG_8;
}
@@ -752,7 +763,7 @@ from_header (char const *where0, size_t digs, char const *type,
if (where == lim || ! ISODIGIT (*where))
break;
digit = *where - '0';
overflow |= value ^ (value << LG_8 >> LG_8);
overflow |= value != (value << LG_8 >> LG_8);
value <<= LG_8;
}
value++;
@@ -765,7 +776,7 @@ from_header (char const *where0, size_t digs, char const *type,
/* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
_("Archive octal value %.*s is out of %s range; assuming two's complement"),
(int) (where - where1), where1, type));
negative = 1;
negative = true;
}
}
@@ -845,7 +856,7 @@ from_header (char const *where0, size_t digs, char const *type,
return -1;
}
}
negative = signbit;
negative = signbit != 0;
if (negative)
value = -value;
}
@@ -877,7 +888,7 @@ from_header (char const *where0, size_t digs, char const *type,
}
if (value <= (negative ? minus_minval : maxval))
return negative ? -value : value;
return represent_uintmax (negative ? -value : value);
if (type && !silent)
{
@@ -903,8 +914,7 @@ static gid_t
gid_from_header (const char *p, size_t s)
{
return from_header (p, s, "gid_t",
- (uintmax_t) TYPE_MINIMUM (gid_t),
(uintmax_t) TYPE_MAXIMUM (gid_t),
TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t),
false, false);
}
@@ -912,26 +922,26 @@ static major_t
major_from_header (const char *p, size_t s)
{
return from_header (p, s, "major_t",
- (uintmax_t) TYPE_MINIMUM (major_t),
(uintmax_t) TYPE_MAXIMUM (major_t), false, false);
TYPE_MINIMUM (major_t), TYPE_MAXIMUM (major_t),
false, false);
}
static minor_t
minor_from_header (const char *p, size_t s)
{
return from_header (p, s, "minor_t",
- (uintmax_t) TYPE_MINIMUM (minor_t),
(uintmax_t) TYPE_MAXIMUM (minor_t), false, false);
TYPE_MINIMUM (minor_t), TYPE_MAXIMUM (minor_t),
false, false);
}
/* Convert P to the file mode, as understood by tar.
Store unrecognized mode bits (from 10th up) in HBITS. */
Set *HBITS if there are any unrecognized bits. */
static mode_t
mode_from_header (const char *p, size_t s, unsigned *hbits)
mode_from_header (const char *p, size_t s, bool *hbits)
{
unsigned u = from_header (p, s, "mode_t",
- (uintmax_t) TYPE_MINIMUM (mode_t),
TYPE_MAXIMUM (uintmax_t), false, false);
intmax_t u = from_header (p, s, "mode_t",
INTMAX_MIN, UINTMAX_MAX,
false, false);
mode_t mode = ((u & TSUID ? S_ISUID : 0)
| (u & TSGID ? S_ISGID : 0)
| (u & TSVTX ? S_ISVTX : 0)
@@ -944,7 +954,7 @@ mode_from_header (const char *p, size_t s, unsigned *hbits)
| (u & TOREAD ? S_IROTH : 0)
| (u & TOWRITE ? S_IWOTH : 0)
| (u & TOEXEC ? S_IXOTH : 0));
*hbits = mode ^ u;
*hbits = (u & ~07777) != 0;
return mode;
}
@@ -953,31 +963,31 @@ off_from_header (const char *p, size_t s)
{
/* Negative offsets are not allowed in tar files, so invoke
from_header with minimum value 0, not TYPE_MINIMUM (off_t). */
return from_header (p, s, "off_t", (uintmax_t) 0,
(uintmax_t) TYPE_MAXIMUM (off_t), false, false);
return from_header (p, s, "off_t",
0, TYPE_MAXIMUM (off_t),
false, false);
}
static time_t
time_from_header (const char *p, size_t s)
{
return from_header (p, s, "time_t",
- (uintmax_t) TYPE_MINIMUM (time_t),
(uintmax_t) TYPE_MAXIMUM (time_t), false, false);
TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t),
false, false);
}
static uid_t
uid_from_header (const char *p, size_t s)
{
return from_header (p, s, "uid_t",
- (uintmax_t) TYPE_MINIMUM (uid_t),
(uintmax_t) TYPE_MAXIMUM (uid_t), false, false);
TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t),
false, false);
}
uintmax_t
uintmax_from_header (const char *p, size_t s)
{
return from_header (p, s, "uintmax_t", (uintmax_t) 0,
TYPE_MAXIMUM (uintmax_t), false, false);
return from_header (p, s, "uintmax_t", 0, UINTMAX_MAX, false, false);
}
@@ -1073,7 +1083,8 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
char *temp_name;
/* These hold formatted ints. */
char uform[UINTMAX_STRSIZE_BOUND], gform[UINTMAX_STRSIZE_BOUND];
char uform[max (INT_BUFSIZE_BOUND (intmax_t), UINTMAX_STRSIZE_BOUND)];
char gform[sizeof uform];
char *user, *group;
char size[2 * UINTMAX_STRSIZE_BOUND];
/* holds formatted size or major,minor */
@@ -1183,17 +1194,11 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
ids that are too large to fit in a uid_t. */
uintmax_t u = from_header (blk->header.uid,
sizeof blk->header.uid, 0,
(uintmax_t) 0,
(uintmax_t) TYPE_MAXIMUM (uintmax_t),
0, UINTMAX_MAX,
false, false);
if (u != -1)
user = STRINGIFY_BIGINT (u, uform);
else
{
sprintf (uform, "%ld",
(long) UID_FROM_HEADER (blk->header.uid));
user = uform;
}
user = (u != -1
? STRINGIFY_BIGINT (u, uform)
: imaxtostr (UID_FROM_HEADER (blk->header.uid), uform));
}
if (st->gname
@@ -1208,17 +1213,11 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
ids that are too large to fit in a gid_t. */
uintmax_t g = from_header (blk->header.gid,
sizeof blk->header.gid, 0,
(uintmax_t) 0,
(uintmax_t) TYPE_MAXIMUM (uintmax_t),
0, UINTMAX_MAX,
false, false);
if (g != -1)
group = STRINGIFY_BIGINT (g, gform);
else
{
sprintf (gform, "%ld",
(long) GID_FROM_HEADER (blk->header.gid));
group = gform;
}
group = (g != -1
? STRINGIFY_BIGINT (g, gform)
: imaxtostr (GID_FROM_HEADER (blk->header.gid), gform));
}
/* Format the file size or major/minor device numbers. */

View File

@@ -1,7 +1,7 @@
/* Miscellaneous functions, not really specific to GNU tar.
Copyright (C) 1988, 1992, 1994, 1995, 1996, 1997, 1999, 2000, 2001,
2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
2003, 2004, 2005, 2006, 2007, 2009, 2010, 2012 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
@@ -17,6 +17,7 @@
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
#define COMMON_INLINE _GL_EXTERN_INLINE
#include <system.h>
#include <rmt.h>
#include "common.h"
@@ -325,6 +326,76 @@ replace_prefix (char **pname, const char *samp, size_t slen,
/* Handling numbers. */
/* Convert VALUE, which is converted from a system integer type whose
minimum value is MINVAL and maximum MINVAL, to an decimal
integer string. Use the storage in BUF and return a pointer to the
converted string. If VALUE is converted from a negative integer in
the range MINVAL .. -1, represent it with a string representation
of the negative integer, using leading '-'. */
#if ! (INTMAX_MAX <= UINTMAX_MAX / 2)
# error "strtosysint accepts uintmax_t to represent intmax_t"
#endif
char *
sysinttostr (uintmax_t value, intmax_t minval, uintmax_t maxval,
char buf[SYSINT_BUFSIZE])
{
if (value <= maxval)
return umaxtostr (value, buf);
else
{
intmax_t i = value - minval;
return imaxtostr (i + minval, buf);
}
}
/* Convert a prefix of the string ARG to a system integer type whose
minimum value is MINVAL and maximum MAXVAL. If MINVAL is negative,
negative integers MINVAL .. -1 are assumed to be represented using
leading '-' in the usual way. If the represented value exceeds
INTMAX_MAX, return a negative integer V such that (uintmax_t) V
yields the represented value. If ARGLIM is nonnull, store into
*ARGLIM a pointer to the first character after the prefix.
This is the inverse of sysinttostr.
On a normal return, set errno = 0.
On conversion error, return 0 and set errno = EINVAL.
On overflow, return an extreme value and set errno = ERANGE. */
#if ! (INTMAX_MAX <= UINTMAX_MAX)
# error "strtosysint accepts uintmax_t to represent nonnegative intmax_t"
#endif
intmax_t
strtosysint (char const *arg, char **arglim, intmax_t minval, uintmax_t maxval)
{
errno = 0;
if (maxval <= INTMAX_MAX)
{
if (ISDIGIT (arg[*arg == '-']))
{
intmax_t i = strtoimax (arg, arglim, 10);
intmax_t imaxval = maxval;
if (minval <= i && i <= imaxval)
return i;
errno = ERANGE;
return i < minval ? minval : maxval;
}
}
else
{
if (ISDIGIT (*arg))
{
uintmax_t i = strtoumax (arg, arglim, 10);
if (i <= maxval)
return represent_uintmax (i);
errno = ERANGE;
return maxval;
}
}
errno = EINVAL;
return 0;
}
/* Output fraction and trailing digits appropriate for a nanoseconds
count equal to NS, but don't output unnecessary '.' or trailing
zeros. */
@@ -381,6 +452,84 @@ code_timespec (struct timespec t, char sbuf[TIMESPEC_STRSIZE_BOUND])
code_ns_fraction (ns, sbuf + UINTMAX_STRSIZE_BOUND);
return np;
}
struct timespec
decode_timespec (char const *arg, char **arg_lim, bool parse_fraction)
{
time_t s = TYPE_MINIMUM (time_t);
int ns = -1;
char const *p = arg;
bool negative = *arg == '-';
struct timespec r;
if (! ISDIGIT (arg[negative]))
errno = EINVAL;
else
{
errno = 0;
if (negative)
{
intmax_t i = strtoimax (arg, arg_lim, 10);
if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
s = i;
else
errno = ERANGE;
}
else
{
uintmax_t i = strtoumax (arg, arg_lim, 10);
if (i <= TYPE_MAXIMUM (time_t))
s = i;
else
errno = ERANGE;
}
p = *arg_lim;
ns = 0;
if (parse_fraction && *p == '.')
{
int digits = 0;
bool trailing_nonzero = false;
while (ISDIGIT (*++p))
if (digits < LOG10_BILLION)
digits++, ns = 10 * ns + (*p - '0');
else
trailing_nonzero |= *p != '0';
while (digits < LOG10_BILLION)
digits++, ns *= 10;
if (negative)
{
/* Convert "-1.10000000000001" to s == -2, ns == 89999999.
I.e., truncate time stamps towards minus infinity while
converting them to internal form. */
ns += trailing_nonzero;
if (ns != 0)
{
if (s == TYPE_MINIMUM (time_t))
ns = -1;
else
{
s--;
ns = BILLION - ns;
}
}
}
}
if (errno == ERANGE)
ns = -1;
}
*arg_lim = (char *) p;
r.tv_sec = s;
r.tv_nsec = ns;
return r;
}
/* File handling. */

View File

@@ -629,8 +629,8 @@ oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
return add_finish;
sp.offset = OFF_FROM_HEADER (s->offset);
sp.numbytes = OFF_FROM_HEADER (s->numbytes);
if (sp.offset < 0
|| sp.offset + sp.numbytes < 0
if (sp.offset < 0 || sp.numbytes < 0
|| INT_ADD_OVERFLOW (sp.offset, sp.numbytes)
|| file->stat_info->stat.st_size < sp.offset + sp.numbytes
|| file->stat_info->archive_file_size < 0)
return add_fail;
@@ -644,10 +644,10 @@ oldgnu_fixup_header (struct tar_sparse_file *file)
{
/* NOTE! st_size was initialized from the header
which actually contains archived size. The following fixes it */
off_t realsize = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
file->stat_info->archive_file_size = file->stat_info->stat.st_size;
file->stat_info->stat.st_size =
OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
return true;
file->stat_info->stat.st_size = max (0, realsize);
return 0 <= realsize;
}
/* Convert old GNU format sparse data to internal representation */
@@ -768,10 +768,10 @@ star_fixup_header (struct tar_sparse_file *file)
{
/* NOTE! st_size was initialized from the header
which actually contains archived size. The following fixes it */
off_t realsize = OFF_FROM_HEADER (current_header->star_in_header.realsize);
file->stat_info->archive_file_size = file->stat_info->stat.st_size;
file->stat_info->stat.st_size =
OFF_FROM_HEADER (current_header->star_in_header.realsize);
return true;
file->stat_info->stat.st_size = max (0, realsize);
return 0 <= realsize;
}
/* Convert STAR format sparse data to internal representation */
@@ -1080,6 +1080,7 @@ decode_num (uintmax_t *num, char const *arg, uintmax_t maxval)
if (!ISDIGIT (*arg))
return false;
errno = 0;
u = strtoumax (arg, &arg_lim, 10);
if (! (u <= maxval && errno != ERANGE) || *arg_lim)

View File

@@ -1383,8 +1383,8 @@ expand_pax_option (struct tar_args *targs, const char *arg)
tmp[len-2] = 0;
if (get_date_or_file (targs, "--pax-option", tmp, &ts) == 0)
{
char buf[UINTMAX_STRSIZE_BOUND], *s;
s = umaxtostr (ts.tv_sec, buf);
char buf[TIMESPEC_STRSIZE_BOUND];
char const *s = code_timespec (ts, buf);
obstack_grow (&stk, s, strlen (s));
}
else

View File

@@ -167,14 +167,13 @@ xheader_set_single_keyword (char *kw)
static void
assign_time_option (char **sval, time_t *tval, const char *input)
{
uintmax_t u;
char *p;
time_t t = u = strtoumax (input, &p, 10);
if (t != u || *p || errno == ERANGE)
struct timespec t = decode_timespec (input, &p, false);
if (! valid_timespec (t) || *p)
ERROR ((0, 0, _("Time stamp is out of allowed range")));
else
{
*tval = t;
*tval = t.tv_sec;
assign_string (sval, input);
}
}
@@ -452,7 +451,8 @@ xheader_write_global (struct xheader *xhdr)
char *name;
xheader_finish (xhdr);
xheader_write (XGLTYPE, name = xheader_ghdr_name (), time (NULL), xhdr);
name = xheader_ghdr_name ();
xheader_write (XGLTYPE, name, start_time.tv_sec, xhdr);
free (name);
}
}
@@ -652,7 +652,6 @@ decode_record (struct xheader *xhdr,
{
char *start = *ptr;
char *p = start;
uintmax_t u;
size_t len;
char *len_lim;
char const *keyword;
@@ -669,13 +668,7 @@ decode_record (struct xheader *xhdr,
return false;
}
errno = 0;
len = u = strtoumax (p, &len_lim, 10);
if (len != u || errno == ERANGE)
{
ERROR ((0, 0, _("Extended header length is out of allowed range")));
return false;
}
len = strtoumax (p, &len_lim, 10);
if (len_max < len)
{
@@ -817,10 +810,16 @@ xheader_store (char const *keyword, struct tar_stat_info *st,
}
void
xheader_read (struct xheader *xhdr, union block *p, size_t size)
xheader_read (struct xheader *xhdr, union block *p, off_t size)
{
size_t j = 0;
if (size < 0)
size = 0; /* Already diagnosed. */
if (SIZE_MAX - BLOCKSIZE <= size)
xalloc_die ();
size += BLOCKSIZE;
xhdr->size = size;
xhdr->buffer = xmalloc (size + 1);
@@ -1031,14 +1030,12 @@ xheader_string_end (struct xheader *xhdr, char const *keyword)
static void
out_of_range_header (char const *keyword, char const *value,
uintmax_t minus_minval, uintmax_t maxval)
intmax_t minval, uintmax_t maxval)
{
char minval_buf[UINTMAX_STRSIZE_BOUND + 1];
char minval_buf[INT_BUFSIZE_BOUND (intmax_t)];
char maxval_buf[UINTMAX_STRSIZE_BOUND];
char *minval_string = umaxtostr (minus_minval, minval_buf + 1);
char *minval_string = imaxtostr (minval, minval_buf);
char *maxval_string = umaxtostr (maxval, maxval_buf);
if (minus_minval)
*--minval_string = '-';
/* TRANSLATORS: The first %s is the pax extended header keyword
(atime, gid, etc.). */
@@ -1081,138 +1078,74 @@ code_time (struct timespec t, char const *keyword, struct xheader *xhdr)
xheader_print (xhdr, keyword, code_timespec (t, buf));
}
enum decode_time_status
{
decode_time_success,
decode_time_range,
decode_time_bad_header
};
static enum decode_time_status
_decode_time (struct timespec *ts, char const *arg, char const *keyword)
{
time_t s;
unsigned long int ns = 0;
char *p;
char *arg_lim;
bool negative = *arg == '-';
errno = 0;
if (ISDIGIT (arg[negative]))
{
if (negative)
{
intmax_t i = strtoimax (arg, &arg_lim, 10);
if (TYPE_SIGNED (time_t) ? i < TYPE_MINIMUM (time_t) : i < 0)
return decode_time_range;
s = i;
}
else
{
uintmax_t i = strtoumax (arg, &arg_lim, 10);
if (TYPE_MAXIMUM (time_t) < i)
return decode_time_range;
s = i;
}
p = arg_lim;
if (errno == ERANGE)
return decode_time_range;
if (*p == '.')
{
int digits = 0;
bool trailing_nonzero = false;
while (ISDIGIT (*++p))
if (digits < LOG10_BILLION)
{
ns = 10 * ns + (*p - '0');
digits++;
}
else
trailing_nonzero |= *p != '0';
while (digits++ < LOG10_BILLION)
ns *= 10;
if (negative)
{
/* Convert "-1.10000000000001" to s == -2, ns == 89999999.
I.e., truncate time stamps towards minus infinity while
converting them to internal form. */
ns += trailing_nonzero;
if (ns != 0)
{
if (s == TYPE_MINIMUM (time_t))
return decode_time_range;
s--;
ns = BILLION - ns;
}
}
}
if (! *p)
{
ts->tv_sec = s;
ts->tv_nsec = ns;
return decode_time_success;
}
}
return decode_time_bad_header;
}
static bool
decode_time (struct timespec *ts, char const *arg, char const *keyword)
{
switch (_decode_time (ts, arg, keyword))
char *arg_lim;
struct timespec t = decode_timespec (arg, &arg_lim, true);
if (! valid_timespec (t))
{
case decode_time_success:
return true;
case decode_time_bad_header:
ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
keyword, arg));
return false;
case decode_time_range:
out_of_range_header (keyword, arg, - (uintmax_t) TYPE_MINIMUM (time_t),
TYPE_MAXIMUM (time_t));
if (arg < arg_lim && !*arg_lim)
out_of_range_header (keyword, arg, TYPE_MINIMUM (time_t),
TYPE_MAXIMUM (time_t));
else
ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
keyword, arg));
return false;
}
*ts = t;
return true;
}
static void
code_signed_num (uintmax_t value, char const *keyword,
intmax_t minval, uintmax_t maxval, struct xheader *xhdr)
{
char sbuf[SYSINT_BUFSIZE];
xheader_print (xhdr, keyword, sysinttostr (value, minval, maxval, sbuf));
}
static void
code_num (uintmax_t value, char const *keyword, struct xheader *xhdr)
{
char sbuf[UINTMAX_STRSIZE_BOUND];
xheader_print (xhdr, keyword, umaxtostr (value, sbuf));
code_signed_num (value, keyword, 0, UINTMAX_MAX, xhdr);
}
static bool
decode_signed_num (intmax_t *num, char const *arg,
intmax_t minval, uintmax_t maxval,
char const *keyword)
{
char *arg_lim;
intmax_t u = strtosysint (arg, &arg_lim, minval, maxval);
if (errno == EINVAL || *arg_lim)
{
ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
keyword, arg));
return false;
}
if (errno == ERANGE)
{
out_of_range_header (keyword, arg, minval, maxval);
return false;
}
*num = u;
return true;
}
static bool
decode_num (uintmax_t *num, char const *arg, uintmax_t maxval,
char const *keyword)
{
uintmax_t u;
char *arg_lim;
if (! (ISDIGIT (*arg)
&& (errno = 0, u = strtoumax (arg, &arg_lim, 10), !*arg_lim)))
{
ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
keyword, arg));
return false;
}
if (! (u <= maxval && errno != ERANGE))
{
out_of_range_header (keyword, arg, 0, maxval);
return false;
}
*num = u;
intmax_t i;
if (! decode_signed_num (&i, arg, 0, maxval, keyword))
return false;
*num = i;
return true;
}
@@ -1254,7 +1187,8 @@ static void
gid_coder (struct tar_stat_info const *st, char const *keyword,
struct xheader *xhdr, void const *data __attribute__ ((unused)))
{
code_num (st->stat.st_gid, keyword, xhdr);
code_signed_num (st->stat.st_gid, keyword,
TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t), xhdr);
}
static void
@@ -1263,8 +1197,9 @@ gid_decoder (struct tar_stat_info *st,
char const *arg,
size_t size __attribute__((unused)))
{
uintmax_t u;
if (decode_num (&u, arg, TYPE_MAXIMUM (gid_t), keyword))
intmax_t u;
if (decode_signed_num (&u, arg, TYPE_MINIMUM (gid_t),
TYPE_MAXIMUM (gid_t), keyword))
st->stat.st_gid = u;
}
@@ -1377,7 +1312,8 @@ static void
uid_coder (struct tar_stat_info const *st, char const *keyword,
struct xheader *xhdr, void const *data __attribute__ ((unused)))
{
code_num (st->stat.st_uid, keyword, xhdr);
code_signed_num (st->stat.st_uid, keyword,
TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), xhdr);
}
static void
@@ -1386,8 +1322,9 @@ uid_decoder (struct tar_stat_info *st,
char const *arg,
size_t size __attribute__((unused)))
{
uintmax_t u;
if (decode_num (&u, arg, TYPE_MAXIMUM (uid_t), keyword))
intmax_t u;
if (decode_signed_num (&u, arg, TYPE_MINIMUM (uid_t),
TYPE_MAXIMUM (uid_t), keyword))
st->stat.st_uid = u;
}
@@ -1509,7 +1446,7 @@ sparse_map_decoder (struct tar_stat_info *st,
st->sparse_map_avail = 0;
while (1)
{
uintmax_t u;
intmax_t u;
char *delim;
struct sp_array e;
@@ -1521,11 +1458,16 @@ sparse_map_decoder (struct tar_stat_info *st,
}
errno = 0;
u = strtoumax (arg, &delim, 10);
u = strtoimax (arg, &delim, 10);
if (TYPE_MAXIMUM (off_t) < u)
{
u = TYPE_MAXIMUM (off_t);
errno = ERANGE;
}
if (offset)
{
e.offset = u;
if (!(u == e.offset && errno != ERANGE))
if (errno == ERANGE)
{
out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
return;
@@ -1534,7 +1476,7 @@ sparse_map_decoder (struct tar_stat_info *st,
else
{
e.numbytes = u;
if (!(u == e.numbytes && errno != ERANGE))
if (errno == ERANGE)
{
out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
return;

View File

@@ -157,6 +157,7 @@ TESTSUITE_AT = \
spmvp00.at\
spmvp01.at\
spmvp10.at\
time01.at\
truncate.at\
update.at\
update01.at\

View File

@@ -284,6 +284,8 @@ m4_include([lustar01.at])
m4_include([lustar02.at])
m4_include([lustar03.at])
m4_include([time01.at])
m4_include([multiv01.at])
m4_include([multiv02.at])
m4_include([multiv03.at])
@@ -357,4 +359,3 @@ m4_include([star/ustar-big-2g.at])
m4_include([star/ustar-big-8g.at])
m4_include([star/pax-big-10g.at])

70
tests/time01.at Normal file
View File

@@ -0,0 +1,70 @@
# Test time stamps for GNU tar. -*- Autotest -*-
#
# Copyright 2012 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# written by Paul Eggert
AT_SETUP([time: tricky time stamps])
AT_KEYWORDS([time time01])
AT_TAR_CHECK([
export TZ=UTC0
mkdir dir
# Test files with time stamps that are near common sources of error,
# typically near powers of 2 (for seconds) or near 0, 1970, or 9999 (years).
# Use GNU-style @ notation for very large time stamps, since they
# typically don't render into years correctly due to int overflow.
for s in \
@-9223372036854775809 @-9223372036854775808 @-9223372036854775807 \
0000-01-01T00:00:00 0000-01-01T00:00:01 \
0000-01-02T00:00:00 \
1697-10-17T11:03:27 1697-10-17T11:03:28 1697-10-17T11:03:29 \
1833-11-24T17:31:43 1833-11-24T17:31:44 1833-11-24T17:31:45 \
1901-12-13T20:45:51 1901-12-13T20:45:52 1901-12-13T20:45:53 \
1901-12-14T20:45:51 \
1969-12-31T23:59:58 1969-12-31T23:59:59 \
1970-01-01T00:00:00 1970-01-01T00:00:01 \
2038-01-18T03:14:07 \
2038-01-19T03:14:07 2038-01-19T03:14:08 \
2106-02-07T06:28:15 2106-02-07T06:28:16 \
2242-03-16T12:56:31 2242-03-16T12:56:32 \
9999-12-31T23:59:58 9999-12-31T23:59:59 \
@9223372036854775807 @9223372036854775808
do
# Skip a time stamp $s if it's out of range for this platform,
# of if it uses a notation that this platform does not recognize.
touch -d $s dir/f$s >/dev/null 2>&1 || continue
# Likewise for $s.1. If $s is the most negative time stamp and
# time stamps are signed, then $s.1 is out of range.
touch -d $s.1 dir/f$s.$ns >/dev/null 2>&1 || continue
for frac in 01 001 00001 000001 0000001 00000001 000000001 0000000001 \
9 99 999 99999 999999 9999999 99999999 999999999 9999999999
do
touch -d $s.$frac dir/f$s.$frac
done
done
tar -c -f archive.tar dir
tar -d -f archive.tar dir
],
[0],
[], [], [], [],
[pax])
AT_CLEANUP