]> Dogcows Code - chaz/tar/commitdiff
Fix some problems with negative and out-of-range integers.
authorPaul Eggert <eggert@cs.ucla.edu>
Sun, 23 Dec 2012 04:41:23 +0000 (20:41 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 23 Dec 2012 04:41:52 +0000 (20:41 -0800)
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.

13 files changed:
gnulib.modules
src/common.h
src/compare.c
src/create.c
src/incremen.c
src/list.c
src/misc.c
src/sparse.c
src/tar.c
src/xheader.c
tests/Makefile.am
tests/testsuite.at
tests/time01.at [new file with mode: 0644]

index 4ac1a3c63a057c4cb4f448b132f74d275aab99a9..f347d32380f0dc43eae8ef67a60284e3024e85ce 100644 (file)
@@ -12,6 +12,7 @@ configmake
 dirname
 error
 exclude
+extern-inline
 exitfail
 fchmodat
 fchownat
index 18f7bc60eee3bca1a32c5e41096dd0a40c18577b..c1edba86ef8a0cbf7a5686b3dbe8121d500286ae 100644 (file)
 #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
 \f
 /* 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
index 7c514b96e3129ab4d205bbf512d12e4990bf686e..0e99a7201cadf528bf57f1dc866c68c35091146c 100644 (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 ();
index 36ca30ec6a31487d22ba0fe67fd1399e48ae17a3..3cb21f38642e400e05eb1c586dc564a194b16281 100644 (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
     {
index d5bc1e41d88fa6c0d8067da6d27a62f6a60e3774..540afc64fa672c5b9feba6aac8fa5f7f57f04d08 100644 (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);
index ffe255e477f3999523213b37bde1412aa0abdc09..8ba07cd95490182fbd2dfa3b469ff4f9b1e17e02 100644 (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.  */
index 734dc4eded7adcac4f7a29d84111a0d49306cfb4..5fe80f4bb56e3616010cbb9075ca06e009572b6e 100644 (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,
 \f
 /* 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;
+}
 \f
 /* File handling.  */
 
index f7a9fe7c93ff046b12f19054fde10d787e097c14..9a9c2a82edefabe8584fcb9a7cb69dc3b7bf9bbb 100644 (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)
index d230e958df4ab1e530a6d20099d0edb4e513364a..1f7289a4ef373b69939b633f4ccfec86f2e459a0 100644 (file)
--- 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
index 291d9950dd3737325326ce73af26cfc7004267aa..57ace12adf6f7b28d2234ac8ac4ab8ec85f1169a 100644 (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,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;
index 401b74841cb559b1cc8d9dc1579793e115b04084..dc656378769ad37797dc92d958763c9be6d58f97 100644 (file)
@@ -157,6 +157,7 @@ TESTSUITE_AT = \
  spmvp00.at\
  spmvp01.at\
  spmvp10.at\
+ time01.at\
  truncate.at\
  update.at\
  update01.at\
index 9ec848f947b134f2f625c48659ffe050fd6f9bef..652da5745184058a5d963e38e6ae678ef77580d0 100644 (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])
-
diff --git a/tests/time01.at b/tests/time01.at
new file mode 100644 (file)
index 0000000..43a8110
--- /dev/null
@@ -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
This page took 0.057435 seconds and 4 git commands to generate.