From df7b55a8f6354e30e8da62eec7f706df033d0c81 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sat, 22 Dec 2012 20:41:23 -0800 Subject: [PATCH] Fix some problems with negative and out-of-range integers. Original problem reported for HP-UX LVM v2.2 by Michael White in . 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. --- gnulib.modules | 1 + src/common.h | 42 +++++++++- src/compare.c | 7 +- src/create.c | 24 ++---- src/incremen.c | 112 +++++++++---------------- src/list.c | 127 ++++++++++++++-------------- src/misc.c | 151 +++++++++++++++++++++++++++++++++- src/sparse.c | 17 ++-- src/tar.c | 4 +- src/xheader.c | 200 ++++++++++++++++----------------------------- tests/Makefile.am | 1 + tests/testsuite.at | 3 +- tests/time01.at | 70 ++++++++++++++++ 13 files changed, 458 insertions(+), 301 deletions(-) create mode 100644 tests/time01.at diff --git a/gnulib.modules b/gnulib.modules index 4ac1a3c..f347d32 100644 --- a/gnulib.modules +++ b/gnulib.modules @@ -12,6 +12,7 @@ configmake dirname error exclude +extern-inline exitfail fchmodat fchownat diff --git a/src/common.h b/src/common.h index 18f7bc6..c1edba8 100644 --- a/src/common.h +++ b/src/common.h @@ -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 diff --git a/src/compare.c b/src/compare.c index 7c514b9..0e99a72 100644 --- a/src/compare.c +++ b/src/compare.c @@ -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 (¤t_stat_info, _("Size differs")); skip_member (); diff --git a/src/create.c b/src/create.c index 36ca30e..3cb21f3 100644 --- a/src/create.c +++ b/src/create.c @@ -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 { diff --git a/src/incremen.c b/src/incremen.c index d5bc1e4..540afc6 100644 --- a/src/incremen.c +++ b/src/incremen.c @@ -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); diff --git a/src/list.c b/src/list.c index ffe255e..8ba07cd 100644 --- a/src/list.c +++ b/src/list.c @@ -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. */ diff --git a/src/misc.c b/src/misc.c index 734dc4e..5fe80f4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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 #include #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. */ diff --git a/src/sparse.c b/src/sparse.c index f7a9fe7..9a9c2a8 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -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) diff --git a/src/tar.c b/src/tar.c index d230e95..1f7289a 100644 --- a/src/tar.c +++ b/src/tar.c @@ -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 diff --git a/src/xheader.c b/src/xheader.c index 291d995..57ace12 100644 --- a/src/xheader.c +++ b/src/xheader.c @@ -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,134 +1078,59 @@ 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) +static bool +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 == '-'; + struct timespec t = decode_timespec (arg, &arg_lim, true); - errno = 0; - - if (ISDIGIT (arg[negative])) + if (! valid_timespec (t)) { - 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; - } + if (arg < arg_lim && !*arg_lim) + out_of_range_header (keyword, arg, TYPE_MINIMUM (time_t), + TYPE_MAXIMUM (time_t)); 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; - } + ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"), + keyword, arg)); + return false; } - return decode_time_bad_header; + *ts = t; + return true; } -static bool -decode_time (struct timespec *ts, char const *arg, char const *keyword) +static void +code_signed_num (uintmax_t value, char const *keyword, + intmax_t minval, uintmax_t maxval, struct xheader *xhdr) { - switch (_decode_time (ts, arg, keyword)) - { - 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)); - return false; - } - return true; + 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_num (uintmax_t *num, char const *arg, uintmax_t maxval, - char const *keyword) +decode_signed_num (intmax_t *num, char const *arg, + intmax_t minval, uintmax_t maxval, + char const *keyword) { - uintmax_t u; char *arg_lim; + intmax_t u = strtosysint (arg, &arg_lim, minval, maxval); - if (! (ISDIGIT (*arg) - && (errno = 0, u = strtoumax (arg, &arg_lim, 10), !*arg_lim))) + if (errno == EINVAL || *arg_lim) { ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"), keyword, arg)); return false; } - if (! (u <= maxval && errno != ERANGE)) + if (errno == ERANGE) { - out_of_range_header (keyword, arg, 0, maxval); + out_of_range_header (keyword, arg, minval, maxval); return false; } @@ -1216,6 +1138,17 @@ decode_num (uintmax_t *num, char const *arg, uintmax_t maxval, return true; } +static bool +decode_num (uintmax_t *num, char const *arg, uintmax_t maxval, + char const *keyword) +{ + intmax_t i; + if (! decode_signed_num (&i, arg, 0, maxval, keyword)) + return false; + *num = i; + return true; +} + static void dummy_coder (struct tar_stat_info const *st __attribute__ ((unused)), char const *keyword __attribute__ ((unused)), @@ -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; diff --git a/tests/Makefile.am b/tests/Makefile.am index 401b748..dc65637 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -157,6 +157,7 @@ TESTSUITE_AT = \ spmvp00.at\ spmvp01.at\ spmvp10.at\ + time01.at\ truncate.at\ update.at\ update01.at\ diff --git a/tests/testsuite.at b/tests/testsuite.at index 9ec848f..652da57 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -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]) - diff --git a/tests/time01.at b/tests/time01.at new file mode 100644 index 0000000..43a8110 --- /dev/null +++ b/tests/time01.at @@ -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 . + +# 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 -- 2.45.2