]> Dogcows Code - chaz/tar/commitdiff
Carefully crafted invalid headers can cause buffer overrun.
authorPaul Eggert <eggert@cs.ucla.edu>
Wed, 22 Jun 2005 06:24:39 +0000 (06:24 +0000)
committerPaul Eggert <eggert@cs.ucla.edu>
Wed, 22 Jun 2005 06:24:39 +0000 (06:24 +0000)
Invalid header fields go undiagnosed.
Some valid time strings are ignored.

* NEWS: Better support for full-resolution time stamps.
The -v option now prints time stamps only to 1-minute resolution.
* gnulib.modules: Add utimens.
* lib/.cvsignore: Add imaxtostr.c, inttostr.c, inttostr.h,
offtostr.c, umaxtostr.c, utimens.c, utimens.h.  Remove paxconvert.c.
* lib/Makefile.tmpl (libtar_a_SOURCES): Remove paxconvert.c.
* lib/paxconvert.c: Remove; superseded by umaxtostr.c.
* po/POTFILES.in: Remove lib/paxconvert.c.  Add lib/xalloc-die.c,
lib/obstack.c.
* src/buffer.c (set_start_time, compute_duration, start_time):
Use gettime rather than rolling our own code.
* src/common.h (OLDGNU_NAME_FIELD_SIZE, MAXOCTAL11, MAXOCTAL7): Remove.
(newer_ctime_option): Remove.
(timespec_lt): New function.
(OLDER_STAT_TIME): Use it.
(string_to_chars): First arg is char const *, not char *.
(tartime): Time arg is now struct timespec.  New bool arg.
All callers changed.
(code_ns_fraction): New decl.
(sys_stat_nanoseconds): Remove decl.
(get_stat_atime, get_stat_ctime, get_stat_mtime): New functions.
(set_stat_atime, set_stat_ctime, set_stat_mtime): New functions.
* src/compare.c: Include utimens.h rather than rolling our own.
(diff_dir, diff_file, diff_link, diff_symlink, diff_special):
Prototype.
(diff_dumpdir, diff_multivol): Prototype.
(diff_file): Support higher-resolution time stamps.
* src/create.c: Include utimens.h rather than rolling our own.
(MAX_OCTAL_VAL): New macro.
(tar_copy_str, string_to_chars): Don't bother to zero-fill;
the destination is already zeroed.
(string_to_chars): First arg is char const *.
(start_private_header): Use MINOR_TO_CHARS, not MAJOR_TO_CHARS,
for minor device number.
(write_header_name, dump_hard_link, dump_file0):
Simplify test for old GNU format.
(start_header): Put in placeholders for uid, etc., even when
using extended headers, for benefit of older "tar" implementations.
Don't assume uintmax_t is wider than 32 bits.
Output extended header for mtime if needed.
(dump_regular_finish, dump_file0):
Support extended time stamp resolution.
* src/extract.c: Include utimens.h rather than rolling our own.
(check_time): Support extended time stamp resolution.
* src/list.c: Include <inttostr.h>.
(tartime): Use umaxtostr rather than stringify_uintmax_t_backwards.
* src/xheader.c: Include <inttostr.h>.
Do not include <xstrtol.h>.
(strtoimax) [!HAVE_DECL_STRTOIMAX && !defined strtoimax]: New decl.
(strtoumax) [!HAVE_DECL_STRTOUMAX && !defined strtoumax]: New decl.
(BILLION, LOG10_BILLION): New constants.
(to_decimal): Remove; superseded by inttostr.  All callers changed
to use umaxtostr.
(xheader_format_name): Don't assume pids and uintmax_t values
fit in 63 bytes (!) when printed.
(decode_record): Don't bother to check for ERANGE; an out of range
value must be treater than len_max anyway.
If the length is out of range, output it in the diagnostic.
(format_uintmax): Remove; all callers changed to use umaxtostr.
(xheader_print): Don't assume sizes can be printed in 99 bytes (!).
(out_of_range_header): New function.
(decode_time): Use it.
(code_time): Accept struct timespec, not time_t and unsigned long.
All callers changed.  Size sbuf properly, and remove unnecessary check.
Don't assume time stamps can fit in 199 bytes.
Handle negative time stamps.  Handle fractional time stamps
more consistently.  Don't output unnecessary trailing zeros.
(decode_time): Yield struct timespec, not time_t and unsigned long.
All callers changed.
Handle negative time stamps.  Truncate towards minus infinity
consistently.  Improve overflow checks, and output a better
diagnostic on overflow.
(code_num): Don't assume uintmax_t can be printed in 99 bytes (!).
(decode_num): New function, for better diagnostics.
(atime_coder, atime_decoder, gid_decoder, ctime_coder):
(ctime_decoder, mtime_coder, mtime_decoder, size_decoder):
(uid_decoder, sparse_size_decoder, sparse_numblocks_decoder):
(sparse_offset_decoder, sparse_numbytes_decoder):
Use decode_num, etc., instead of xstrtoumax, etc.

* src/xheader.c (sparse_numblocks_decoder): Remove unchecked use
of `calloc'.  Use xcalloc instead.
(decode_time, gid_decoder, size_decoder, uid_decoder):
(sparse_size_decoder, sparse_offset_decoder, sparse_numblocks_decoder):
Ensure that the result of calling xstrtoumax is no larger than
the maximum value for the target type.  Upon any failure, exit with
a diagnostic.
(sparse_numblocks_decoder): Avoid buffer overrun/heap corruption:
use x2nrealloc, rather than `n *= 2' and xrealloc(p, n,....
(decode_time): Rewrite to accept time strings like
1119018481.000000000.  Before, such strings were always ignored.

18 files changed:
ChangeLog
NEWS
gnulib.modules
lib/.cvsignore
lib/Makefile.tmpl
po/POTFILES.in
src/buffer.c
src/common.h
src/compare.c
src/create.c
src/extract.c
src/incremen.c
src/list.c
src/misc.c
src/system.c
src/tar.c
src/tar.h
src/xheader.c

index bd3d4def124fe7b502f4ea20ad32a188d9945631..14e01a7a69a6abb2c9a6b0f586c00eb15bb94344 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,105 @@
+2005-06-21  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Further improvements inspired by Jim Meyering's fixes.
+
+       * NEWS: Better support for full-resolution time stamps.
+       The -v option now prints time stamps only to 1-minute resolution.
+       * gnulib.modules: Add utimens.
+       * lib/.cvsignore: Add imaxtostr.c, inttostr.c, inttostr.h,
+       offtostr.c, umaxtostr.c, utimens.c, utimens.h.  Remove paxconvert.c.
+       * lib/Makefile.tmpl (libtar_a_SOURCES): Remove paxconvert.c.
+       * lib/paxconvert.c: Remove; superseded by umaxtostr.c.
+       * po/POTFILES.in: Remove lib/paxconvert.c.  Add lib/xalloc-die.c,
+       lib/obstack.c.
+       * src/buffer.c (set_start_time, compute_duration, start_time):
+       Use gettime rather than rolling our own code.
+       * src/common.h (OLDGNU_NAME_FIELD_SIZE, MAXOCTAL11, MAXOCTAL7): Remove.
+       (newer_ctime_option): Remove.
+       (timespec_lt): New function.
+       (OLDER_STAT_TIME): Use it.
+       (string_to_chars): First arg is char const *, not char *.
+       (tartime): Time arg is now struct timespec.  New bool arg.
+       All callers changed.
+       (code_ns_fraction): New decl.
+       (sys_stat_nanoseconds): Remove decl.
+       (get_stat_atime, get_stat_ctime, get_stat_mtime): New functions.
+       (set_stat_atime, set_stat_ctime, set_stat_mtime): New functions.
+       * src/compare.c: Include utimens.h rather than rolling our own.
+       (diff_dir, diff_file, diff_link, diff_symlink, diff_special):
+       Prototype.
+       (diff_dumpdir, diff_multivol): Prototype.
+       (diff_file): Support higher-resolution time stamps.
+       * src/create.c: Include utimens.h rather than rolling our own.
+       (MAX_OCTAL_VAL): New macro.
+       (tar_copy_str, string_to_chars): Don't bother to zero-fill;
+       the destination is already zeroed.
+       (string_to_chars): First arg is char const *.
+       (start_private_header): Use MINOR_TO_CHARS, not MAJOR_TO_CHARS,
+       for minor device number.
+       (write_header_name, dump_hard_link, dump_file0):
+       Simplify test for old GNU format.
+       (start_header): Put in placeholders for uid, etc., even when
+       using extended headers, for benefit of older "tar" implementations.
+       Don't assume uintmax_t is wider than 32 bits.
+       Output extended header for mtime if needed.
+       (dump_regular_finish, dump_file0):
+       Support extended time stamp resolution.
+       * src/extract.c: Include utimens.h rather than rolling our own.
+       (check_time): Support extended time stamp resolution.
+       * src/list.c: Include <inttostr.h>.
+       (tartime): Use umaxtostr rather than stringify_uintmax_t_backwards.
+       * src/xheader.c: Include <inttostr.h>.
+       Do not include <xstrtol.h>.
+       (strtoimax) [!HAVE_DECL_STRTOIMAX && !defined strtoimax]: New decl.
+       (strtoumax) [!HAVE_DECL_STRTOUMAX && !defined strtoumax]: New decl.
+       (BILLION, LOG10_BILLION): New constants.
+       (to_decimal): Remove; superseded by inttostr.  All callers changed
+       to use umaxtostr.
+       (xheader_format_name): Don't assume pids and uintmax_t values
+       fit in 63 bytes (!) when printed.
+       (decode_record): Don't bother to check for ERANGE; an out of range
+       value must be treater than len_max anyway.
+       If the length is out of range, output it in the diagnostic.
+       (format_uintmax): Remove; all callers changed to use umaxtostr.
+       (xheader_print): Don't assume sizes can be printed in 99 bytes (!).
+       (out_of_range_header): New function.
+       (decode_time): Use it.
+       (code_time): Accept struct timespec, not time_t and unsigned long.
+       All callers changed.  Size sbuf properly, and remove unnecessary check.
+       Don't assume time stamps can fit in 199 bytes.
+       Handle negative time stamps.  Handle fractional time stamps
+       more consistently.  Don't output unnecessary trailing zeros.
+       (decode_time): Yield struct timespec, not time_t and unsigned long.
+       All callers changed.
+       Handle negative time stamps.  Truncate towards minus infinity
+       consistently.  Improve overflow checks, and output a better
+       diagnostic on overflow.
+       (code_num): Don't assume uintmax_t can be printed in 99 bytes (!).
+       (decode_num): New function, for better diagnostics.
+       (atime_coder, atime_decoder, gid_decoder, ctime_coder):
+       (ctime_decoder, mtime_coder, mtime_decoder, size_decoder):
+       (uid_decoder, sparse_size_decoder, sparse_numblocks_decoder):
+       (sparse_offset_decoder, sparse_numbytes_decoder):
+       Use decode_num, etc., instead of xstrtoumax, etc.
+
+2005-06-21  Jim Meyering  <jim@meyering.net>
+
+       Carefully crafted invalid headers can cause buffer overrun.
+       Invalid header fields go undiagnosed.
+       Some valid time strings are ignored.
+
+       * src/xheader.c (sparse_numblocks_decoder): Remove unchecked use
+       of `calloc'.  Use xcalloc instead.
+       (decode_time, gid_decoder, size_decoder, uid_decoder):
+       (sparse_size_decoder, sparse_offset_decoder, sparse_numblocks_decoder):
+       Ensure that the result of calling xstrtoumax is no larger than
+       the maximum value for the target type.  Upon any failure, exit with
+       a diagnostic.
+       (sparse_numblocks_decoder): Avoid buffer overrun/heap corruption:
+       use x2nrealloc, rather than `n *= 2' and xrealloc(p, n,....
+       (decode_time): Rewrite to accept time strings like
+       1119018481.000000000.  Before, such strings were always ignored.
+
 2005-06-13  Sergey Poznyakoff  <gray@Mirddin.farlep.net>
 
        * src/create.c (dump_file0): Check for is_avoided_name()
@@ -6,7 +108,7 @@
        * tests/update.at: New file
        * tests/Makefile.am (TESTSUITE_AT): Add update.at
        * tests/testsuite.at: Likewise
-       
+
 2005-06-13  Sergey Poznyakoff  <gray@Mirddin.farlep.net>
 
        * configure.ac (AC_STRUCT_ST_BLKSIZE)
diff --git a/NEWS b/NEWS
index e13e44aee9fd1be7e2c03f9aca9af6a1047bb08e..ab057ecc86a52dfd719bd192ca2c0058e9b4d0a8 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,15 @@ automatically. It is not necessary to give --null option.
 is useful e.g. for processing output from `find dir -print0'.
 An orthogonal option --unquote is provided as well.
 
+* Better support for full-resolution time stamps.  Tar cannot restore
+time stamps to full nanosecond resolution, though, until the kernel
+guys get their act together and give us a system call to set file time
+stamps to nanosecond resolution.
+
+* The -v option now prints time stamps only to 1-minute resolution,
+not full resolution, to avoid using up too many output columns.
+Nanosecond resolution is now supported, but that would be too much.
+
 * Bugfixes
 
 ** Allow non-option arguments to be interspersed with options.
index 27db4dbc13e8e011591884b827de2c203670a662..c1caace1965d4e76169104d8af540e2346f1d51f 100644 (file)
@@ -40,6 +40,7 @@ timespec
 unlinkdir
 unlocked-io
 utime
+utimens
 xalloc
 xalloc-die
 xgetcwd
index 7cc00222b749eae62237fe61376d41c6dc69b43b..529dd0bd5b6b74a09fe323b8b298dbff50856335 100644 (file)
@@ -73,7 +73,10 @@ hash.c
 hash.h
 human.c
 human.h
+imaxtostr.c
 intprops.h
+inttostr.c
+inttostr.h
 lchown.c
 lchown.h
 localcharset.c
@@ -90,10 +93,10 @@ modechange.c
 modechange.h
 obstack.c
 obstack.h
+offtostr.c
 openat.c
 openat.h
 pathmax.h
-paxconvert.c
 paxerror.c
 paxexit.c
 paxlib.h
@@ -149,12 +152,15 @@ system.h
 time_r.c
 time_r.h
 timespec.h
+umaxtostr.c
 unistd-safer.h
 unlinkdir.c
 unlinkdir.h
 unlocked-io.h
 unsetenv.c
 utime.c
+utimens.c
+utimens.h
 vasnprintf.c
 vasnprintf.h
 vsnprintf.c
index 5239449535ef9651bb4335f03a96d720f3be8993..c20e3c4e2e71a81cb40e545ecee8e42dedf6c898 100644 (file)
@@ -20,7 +20,7 @@
 
 noinst_LIBRARIES = libtar.a
 noinst_HEADERS = system.h localedir.h rmt.h paxlib.h
-libtar_a_SOURCES = prepargs.c prepargs.h rtapelib.c paxerror.c paxexit.c paxconvert.c paxnames.c
+libtar_a_SOURCES = prepargs.c prepargs.h rtapelib.c paxerror.c paxexit.c paxnames.c
 
 localedir = $(datadir)/locale
 
index bd2a577ced8d002a5482c92482c956d67b47a13c..dc7320c09bfc16f80eef4df76f4b4ac911dc1e28 100644 (file)
@@ -1,6 +1,7 @@
 # List of files which contain translatable strings.
 
-# Copyright (C) 1996, 1999, 2000, 2003 Free Software Foundation, Inc.
+# Copyright (C) 1996, 1999, 2000, 2003, 2004, 2005 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
 
 # Library files
 lib/argmatch.c
+lib/argp-help.c
+lib/argp-parse.c
 lib/error.c
 lib/getopt.c
 lib/human.c
-lib/quotearg.c
-lib/xmalloc.c
-lib/rtapelib.c
-lib/argp-help.c
-lib/paxconvert.c
+lib/obstack.c
 lib/paxerror.c
 lib/paxexit.c
 lib/paxnames.c
+lib/quotearg.c
+lib/rtapelib.c
+lib/xalloc-die.c
+lib/xmalloc.c
 
 rmt/rmt.c
 
@@ -51,4 +54,3 @@ src/xheader.c
 
 # Testsuite
 tests/genfile.c
-
index 6fc3f4319005a55e6058233bf4e3ab56a5dee998..5977f5a56ed80b17ad6723646fcb46b405928d64 100644 (file)
@@ -125,23 +125,16 @@ double duration;
 void
 set_start_time ()
 {
-#if HAVE_CLOCK_GETTIME
-  if (clock_gettime (CLOCK_REALTIME, &start_timespec) != 0)
-#endif
-    start_time = time (0);
+  gettime (&start_time);
 }
 
 void
 compute_duration ()
 {
-#if HAVE_CLOCK_GETTIME
   struct timespec now;
-  if (clock_gettime (CLOCK_REALTIME, &now) == 0)
-    duration += ((now.tv_sec - start_timespec.tv_sec)
-                + (now.tv_nsec - start_timespec.tv_nsec) / 1e9);
-  else
-#endif
-    duration += time (NULL) - start_time;
+  gettime (&now);
+  duration += ((now.tv_sec - start_time.tv_sec)
+              + (now.tv_nsec - start_time.tv_nsec) / 1e9);
   set_start_time ();
 }
 
@@ -542,7 +535,7 @@ open_archive (enum access_mode wanted_access)
            strip_trailing_slashes (current_stat_info.file_name);
 
          record_start->header.typeflag = GNUTYPE_VOLHDR;
-         TIME_TO_CHARS (start_time, record_start->header.mtime);
+         TIME_TO_CHARS (start_time.tv_sec, record_start->header.mtime);
          finish_header (&current_stat_info, record_start, -1);
        }
       break;
@@ -587,8 +580,8 @@ flush_write (void)
        {
          if (save_name)
            {
-             assign_string (&real_s_name, 
-                            safer_name_suffix (save_name, false, 
+             assign_string (&real_s_name,
+                            safer_name_suffix (save_name, false,
                                                absolute_names_option));
              real_s_totsize = save_totsize;
              real_s_sizeleft = save_sizeleft;
@@ -636,7 +629,7 @@ flush_write (void)
       memset (record_start, 0, BLOCKSIZE);
       sprintf (record_start->header.name, "%s Volume %d",
               volume_label_option, volno);
-      TIME_TO_CHARS (start_time, record_start->header.mtime);
+      TIME_TO_CHARS (start_time.tv_sec, record_start->header.mtime);
       record_start->header.typeflag = GNUTYPE_VOLHDR;
       finish_header (&current_stat_info, record_start, -1);
     }
@@ -695,7 +688,7 @@ flush_write (void)
        assign_string (&real_s_name, 0);
       else
        {
-         assign_string (&real_s_name, 
+         assign_string (&real_s_name,
                         safer_name_suffix (save_name, false,
                                            absolute_names_option));
          real_s_sizeleft = save_sizeleft;
@@ -825,7 +818,7 @@ flush_read (void)
     {
       if (save_name)
        {
-         assign_string (&real_s_name, 
+         assign_string (&real_s_name,
                         safer_name_suffix (save_name, false,
                                            absolute_names_option));
          real_s_sizeleft = save_sizeleft;
index e6860a105463c4419a27906a22f90bcccb7b3666..70a925c912d7a8f073c4f7bc6d630d181cbed263 100644 (file)
 /* The checksum field is filled with this while the checksum is computed.  */
 #define CHKBLANKS      "        "      /* 8 blanks, no null */
 
-/* Old GNU stores zero-terminated file name */
-#define OLDGNU_NAME_FIELD_SIZE   99
-
 /* Some constants from POSIX are given names.  */
 #define NAME_FIELD_SIZE   100
 #define PREFIX_FIELD_SIZE 155
 #define UNAME_FIELD_SIZE   32
 #define GNAME_FIELD_SIZE   32
 
-/* FIXME */
-#define MAXOCTAL11      017777777777L
-#define MAXOCTAL7       07777777
-
 
 \f
 /* Some various global definitions.  */
@@ -185,10 +178,6 @@ GLOBAL mode_t initial_umask;
 
 GLOBAL bool multi_volume_option;
 
-/* The same variable holds the time, whether mtime or ctime.  Just fake a
-   non-existing option, for making the code clearer, elsewhere.  */
-#define newer_ctime_option newer_mtime_option
-
 /* Specified threshold date and time.  Files having an older time stamp
    do not get archived (also see after_date_option above).  */
 GLOBAL struct timespec newer_mtime_option;
@@ -199,9 +188,14 @@ GLOBAL struct timespec newer_mtime_option;
 /* Return true if the struct stat ST's M time is less than
    newer_mtime_option.  */
 #define OLDER_STAT_TIME(st, m) \
-  ((st).st_##m##time < newer_mtime_option.tv_sec \
-   || ((st).st_##m##time == newer_mtime_option.tv_sec \
-       && TIMESPEC_NS ((st).st_##m##tim) < newer_mtime_option.tv_nsec))
+  timespec_lt (get_stat_##m##time (&st), newer_mtime_option)
+
+/* Return true if A < B.  */
+static inline
+timespec_lt (struct timespec a, struct timespec b)
+{
+  return a.tv_sec < b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_nsec < b.tv_nsec);
+}
 
 /* Zero if there is no recursion, otherwise FNM_LEADING_DIR.  */
 GLOBAL int recursion_option;
@@ -281,12 +275,7 @@ GLOBAL int archive;
 GLOBAL bool dev_null_output;
 
 /* Timestamp for when we started execution.  */
-#if HAVE_CLOCK_GETTIME
-  GLOBAL struct timespec start_timespec;
-# define start_time (start_timespec.tv_sec)
-#else
-  GLOBAL time_t start_time;
-#endif
+GLOBAL struct timespec start_time;
 
 GLOBAL struct tar_stat_info current_stat_info;
 
@@ -410,7 +399,7 @@ void size_to_chars (size_t, char *, size_t);
 void time_to_chars (time_t, char *, size_t);
 void uid_to_chars (uid_t, char *, size_t);
 void uintmax_to_chars (uintmax_t, char *, size_t);
-void string_to_chars (char *, char *, size_t);
+void string_to_chars (char const *, char *, size_t);
 
 /* Module diffarch.c.  */
 
@@ -464,7 +453,7 @@ extern size_t recent_long_link_blocks;
 
 void decode_header (union block *, struct tar_stat_info *,
                    enum archive_format *, int);
-char const *tartime (time_t);
+char const *tartime (struct timespec, bool);
 
 #define GID_FROM_HEADER(where) gid_from_header (where, sizeof (where))
 #define MAJOR_FROM_HEADER(where) major_from_header (where, sizeof (where))
@@ -505,6 +494,8 @@ void assign_string (char **, const char *);
 char *quote_copy_string (const char *);
 int unquote_string (char *);
 
+void code_ns_fraction (int, char *);
+
 size_t dot_dot_prefix_len (char const *);
 
 enum remove_option
@@ -621,7 +612,6 @@ void xheader_set_option (char *string);
 
 /* Module system.c */
 
-void sys_stat_nanoseconds (struct tar_stat_info *);
 void sys_detect_dev_null_output (void);
 void sys_save_archive_dev_ino (void);
 void sys_drain_input_pipe (void);
@@ -654,3 +644,120 @@ bool sparse_diff_file (int, struct tar_stat_info *);
 /* Module utf8.c */
 bool string_ascii_p (const char *str);
 bool utf8_convert (bool to_utf, char const *input, char **output);
+
+
+/* FIXME: The following should get moved into gnulib.  */
+
+static inline struct timespec
+get_stat_atime (struct stat const *st)
+{
+#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+  return st->st_atim;
+#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+  return st->st_atimespec;
+#else
+  struct timespec t;
+  t.tv_sec = st->st_atime;
+# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC
+  t.tv_nsec = st->stat.st_atimensec;
+# elif defined HAVE_STRUCT_STAT_ST_SPARE1
+  t.tv_nsec = st->stat.st_spare1 * 1000;
+# else
+  t.tv_nsec = 0;
+# endif
+  return t;
+#endif
+}
+
+static inline struct timespec
+get_stat_ctime (struct stat const *st)
+{
+#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+  return st->st_ctim;
+#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+  return st->st_ctimespec;
+#else
+  struct timespec t;
+  t.tv_sec = st->st_ctime;
+# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC
+  t.tv_nsec = st->stat.st_ctimensec;
+# elif defined HAVE_STRUCT_STAT_ST_SPARE1
+  t.tv_nsec = st->stat.st_spare1 * 1000;
+# else
+  t.tv_nsec = 0;
+# endif
+  return t;
+#endif
+}
+
+static inline struct timespec
+get_stat_mtime (struct stat const *st)
+{
+#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+  return st->st_mtim;
+#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+  return st->st_mtimespec;
+#else
+  struct timespec t;
+  t.tv_sec = st->st_mtime;
+# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC
+  t.tv_nsec = st->stat.st_mtimensec;
+# elif defined HAVE_STRUCT_STAT_ST_SPARE1
+  t.tv_nsec = st->stat.st_spare1 * 1000;
+# else
+  t.tv_nsec = 0;
+# endif
+  return t;
+#endif
+}
+
+static inline void
+set_stat_atime (struct stat *st, struct timespec t)
+{
+#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+  st->st_atim = t;
+#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+  st->st_atimespec = t;
+#else
+  st->st_atime = t.tv_sec;
+# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC
+  st->stat.st_atimensec = t.tv_nsec;
+# elif defined HAVE_STRUCT_STAT_ST_SPARE1
+  st->stat.st_spare1 = t.tv_nsec / 1000;
+# endif
+#endif
+}
+
+static inline void
+set_stat_ctime (struct stat *st, struct timespec t)
+{
+#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+  st->st_ctim = t;
+#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+  st->st_ctimespec = t;
+#else
+  st->st_ctime = t.tv_sec;
+# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC
+  st->stat.st_ctimensec = t.tv_nsec;
+# elif defined HAVE_STRUCT_STAT_ST_SPARE1
+  st->stat.st_spare1 = t.tv_nsec / 1000;
+# endif
+#endif
+}
+
+static inline void
+set_stat_mtime (struct stat *st, struct timespec t)
+{
+#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+  st->st_mtim = t;
+#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+  st->st_mtimespec = t;
+#else
+  st->st_mtime = t.tv_sec;
+# if defined HAVE_STRUCT_STAT_ST_ATIMENSEC
+  st->stat.st_mtimensec = t.tv_nsec;
+# elif defined HAVE_STRUCT_STAT_ST_SPARE1
+  st->stat.st_spare1 = t.tv_nsec / 1000;
+# endif
+#endif
+}
index 0ad12233c9b3d2ec85eb882bb756eaf40d13d779..98a953bf403aa524ead280729c106f1379fd1e35 100644 (file)
@@ -1,7 +1,7 @@
 /* Diff files from a tar archive.
 
    Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
-   2003, 2004 Free Software Foundation, Inc.
+   2003, 2004, 2005 Free Software Foundation, Inc.
 
    Written by John Gilmore, on 1987-04-30.
 
 
 #include <system.h>
 
-#if HAVE_UTIME_H
-# include <utime.h>
-#else
-struct utimbuf
-  {
-    long actime;
-    long modtime;
-  };
-#endif
-
 #if HAVE_LINUX_FD_H
 # include <linux/fd.h>
 #endif
 
 #include <quotearg.h>
+#include <utimens.h>
 
 #include "common.h"
 #include <rmt.h>
@@ -196,7 +187,7 @@ get_stat_data (char const *file_name, struct stat *stat_data)
 
 \f
 static void
-diff_dir ()
+diff_dir (void)
 {
   struct stat stat_data;
 
@@ -211,7 +202,7 @@ diff_dir ()
 }
 
 static void
-diff_file ()
+diff_file (void)
 {
   struct stat stat_data;
 
@@ -254,10 +245,6 @@ diff_file ()
          else
            {
              int status;
-             struct utimbuf restore_times;
-             
-             restore_times.actime = stat_data.st_atime;
-             restore_times.modtime = stat_data.st_mtime;
 
              if (current_stat_info.is_sparse)
                sparse_diff_file (diff_handle, &current_stat_info);
@@ -265,7 +252,7 @@ diff_file ()
                {
                  if (multi_volume_option)
                    {
-                     assign_string (&save_name, 
+                     assign_string (&save_name,
                                      current_stat_info.orig_file_name);
                      save_totsize = current_stat_info.stat.st_size;
                      /* save_sizeleft is set in read_and_process.  */
@@ -283,14 +270,20 @@ diff_file ()
                close_error (current_stat_info.file_name);
 
              if (atime_preserve_option)
-               utime (current_stat_info.file_name, &restore_times);
+               {
+                 struct timespec ts[2];
+                 ts[0] = get_stat_atime (&stat_data);
+                 ts[1] = get_stat_mtime (&stat_data);
+                 if (utimens (current_stat_info.file_name, ts) != 0)
+                   utime_error (current_stat_info.file_name);
+               }
            }
        }
     }
 }
 
 static void
-diff_link ()
+diff_link (void)
 {
   struct stat file_data;
   struct stat link_data;
@@ -305,7 +298,7 @@ diff_link ()
 
 #ifdef HAVE_READLINK
 static void
-diff_symlink ()
+diff_symlink (void)
 {
   size_t len = strlen (current_stat_info.link_name);
   char *linkbuf = alloca (len + 1);
@@ -327,7 +320,7 @@ diff_symlink ()
 #endif
 
 static void
-diff_special ()
+diff_special (void)
 {
   struct stat stat_data;
 
@@ -361,7 +354,7 @@ diff_special ()
 }
 
 static void
-diff_dumpdir ()
+diff_dumpdir (void)
 {
   char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name,
                                                 0);
@@ -387,7 +380,7 @@ diff_dumpdir ()
 }
 
 static void
-diff_multivol ()
+diff_multivol (void)
 {
   struct stat stat_data;
   int fd, status;
@@ -398,7 +391,7 @@ diff_multivol ()
       diff_dir ();
       return;
     }
-  
+
   if (!get_stat_data (current_stat_info.file_name, &stat_data))
     return;
 
@@ -418,7 +411,7 @@ diff_multivol ()
     }
 
   fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
-  
+
   if (fd < 0)
     {
       open_error (current_stat_info.file_name);
@@ -445,7 +438,7 @@ diff_multivol ()
 
   if (multi_volume_option)
     assign_string (&save_name, 0);
-  
+
   status = close (fd);
   if (status != 0)
     close_error (current_stat_info.file_name);
@@ -498,7 +491,7 @@ diff_archive (void)
       diff_symlink ();
       break;
 #endif
-      
+
     case CHRTYPE:
     case BLKTYPE:
     case FIFOTYPE:
@@ -508,7 +501,7 @@ diff_archive (void)
     case GNUTYPE_DUMPDIR:
       diff_dumpdir ();
       /* Fall through.  */
-      
+
     case DIRTYPE:
       diff_dir ();
       break;
index c9be71cf6587d6a3150cc24e0895aad4a9611a90..6381f9fe6ff71d14e01b87484e8f51b277c190f9 100644 (file)
 
 #include <system.h>
 
-#if HAVE_UTIME_H
-# include <utime.h>
-#else
-struct utimbuf
-  {
-    long actime;
-    long modtime;
-  };
-#endif
-
 #include <quotearg.h>
+#include <utimens.h>
 
 #include "common.h"
 #include <hash.h>
@@ -51,6 +42,10 @@ struct link
     ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \
     : (uintmax_t) -1)
 
+/* The maximum uintmax_t value that can be represented with octal
+   digits and a trailing NUL in BUFFER.  */
+#define MAX_OCTAL_VAL(buffer) MAX_VAL_WITH_DIGITS (sizeof (buffer) - 1, LG_8)
+
 /* Convert VALUE to an octal representation suitable for tar headers.
    Output to buffer WHERE with size SIZE.
    The result is undefined if SIZE is 0 or if VALUE is too large to fit.  */
@@ -69,6 +64,29 @@ to_octal (uintmax_t value, char *where, size_t size)
   while (i);
 }
 
+/* Copy at most LEN bytes from the string SRC to DST.  Terminate with
+   NUL unless SRC is LEN or more bytes long.  */
+
+static void
+tar_copy_str (char *dst, const char *src, size_t len)
+{
+  size_t i;
+  for (i = 0; i < len; i++)
+    if (! (dst[i] = src[i]))
+      break;
+}
+
+/* Same as tar_copy_str, but always terminate with NUL if using
+   is OLDGNU format */
+
+static void
+tar_name_copy_str (char *dst, const char *src, size_t len)
+{
+  tar_copy_str (dst, src, len);
+  if (archive_format == OLDGNU_FORMAT)
+    dst[len-1] = 0;
+}
+
 /* Convert NEGATIVE VALUE to a base-256 representation suitable for
    tar headers.  NEGATIVE is 1 if VALUE was negative before being cast
    to uintmax_t, 0 otherwise.  Output to buffer WHERE with size SIZE.
@@ -325,10 +343,10 @@ uintmax_to_chars (uintmax_t v, char *p, size_t s)
 }
 
 void
-string_to_chars (char *str, char *p, size_t s)
+string_to_chars (char const *str, char *p, size_t s)
 {
-  strncpy (p, str, s);
-  p[s-1] = 0;
+  tar_copy_str (p, str, s);
+  p[s - 1] = '\0';
 }
 
 \f
@@ -361,25 +379,6 @@ write_eot (void)
   set_next_block_after (pointer);
 }
 
-/* Copy at most LEN bytes from SRC to DST. Terminate with NUL unless
-   SRC is LEN characters long */
-static void
-tar_copy_str (char *dst, const char *src, size_t len)
-{
-  dst[len-1] = 0;
-  strncpy (dst, src, len);
-}
-
-/* Same as tar_copy_str, but always terminate with NUL if using
-   is OLDGNU format */
-static void
-tar_name_copy_str (char *dst, const char *src, size_t len)
-{
-  tar_copy_str (dst, src, len);
-  if (archive_format == OLDGNU_FORMAT)
-    dst[len-1] = 0;
-}
-
 /* Write a "private" header */
 union block *
 start_private_header (const char *name, size_t size)
@@ -398,7 +397,7 @@ start_private_header (const char *name, size_t size)
   UID_TO_CHARS (getuid (), header->header.uid);
   GID_TO_CHARS (getgid (), header->header.gid);
   MAJOR_TO_CHARS (0, header->header.devmajor);
-  MAJOR_TO_CHARS (0, header->header.devminor);
+  MINOR_TO_CHARS (0, header->header.devminor);
   strncpy (header->header.magic, TMAGIC, TMAGLEN);
   strncpy (header->header.version, TVERSION, TVERSLEN);
   return header;
@@ -600,9 +599,8 @@ write_header_name (struct tar_stat_info *st)
       xheader_store ("path", st, NULL);
       return write_short_name (st);
     }
-  else if ((archive_format == OLDGNU_FORMAT
-           && OLDGNU_NAME_FIELD_SIZE < strlen (st->file_name))
-          || NAME_FIELD_SIZE < strlen (st->file_name))
+  else if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
+          < strlen (st->file_name))
     return write_long_name (st);
   else
     return write_short_name (st);
@@ -662,39 +660,74 @@ start_header (struct tar_stat_info *st)
   else
     MODE_TO_CHARS (st->stat.st_mode, header->header.mode);
 
-  if (st->stat.st_uid > MAXOCTAL7 && archive_format == POSIX_FORMAT)
-    xheader_store ("uid", st, NULL);
-  else
-    UID_TO_CHARS (st->stat.st_uid, header->header.uid);
+  {
+    uid_t uid = st->stat.st_uid;
+    if (archive_format == POSIX_FORMAT
+       && MAX_OCTAL_VAL (header->header.uid) < uid)
+      {
+       xheader_store ("uid", st, NULL);
+       uid = 0;
+      }
+    UID_TO_CHARS (uid, header->header.uid);
+  }
 
-  if (st->stat.st_gid > MAXOCTAL7 && archive_format == POSIX_FORMAT)
-    xheader_store ("gid", st, NULL);
-  else
-    GID_TO_CHARS (st->stat.st_gid, header->header.gid);
+  {
+    gid_t gid = st->stat.st_gid;
+    if (archive_format == POSIX_FORMAT
+       && MAX_OCTAL_VAL (header->header.gid) < gid)
+      {
+       xheader_store ("gid", st, NULL);
+       gid = 0;
+      }
+    GID_TO_CHARS (gid, header->header.gid);
+  }
 
-  if (st->stat.st_size > MAXOCTAL11 && archive_format == POSIX_FORMAT)
-    xheader_store ("size", st, NULL);
-  else
-    OFF_TO_CHARS (st->stat.st_size, header->header.size);
+  {
+    off_t size = st->stat.st_size;
+    if (archive_format == POSIX_FORMAT
+       && MAX_OCTAL_VAL (header->header.size) < size)
+      {
+       xheader_store ("size", st, NULL);
+       size = 0;
+      }
+    OFF_TO_CHARS (size, header->header.size);
+  }
 
-  TIME_TO_CHARS (st->stat.st_mtime, header->header.mtime);
+  {
+    struct timespec mtime = get_stat_mtime (&st->stat);
+    if (archive_format == POSIX_FORMAT)
+      {
+       if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec
+           || mtime.tv_nsec != 0)
+         xheader_store ("mtime", st, NULL);
+       if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec)
+         mtime.tv_sec = 0;
+      }
+    TIME_TO_CHARS (mtime.tv_sec, header->header.mtime);
+  }
 
   /* FIXME */
   if (S_ISCHR (st->stat.st_mode)
       || S_ISBLK (st->stat.st_mode))
     {
-      st->devmajor = major (st->stat.st_rdev);
-      st->devminor = minor (st->stat.st_rdev);
+      major_t devmajor = major (st->stat.st_rdev);
+      minor_t devminor = minor (st->stat.st_rdev);
 
-      if (st->devmajor > MAXOCTAL7 && archive_format == POSIX_FORMAT)
-       xheader_store ("devmajor", st, NULL);
-      else
-       MAJOR_TO_CHARS (st->devmajor, header->header.devmajor);
+      if (archive_format == POSIX_FORMAT
+         && MAX_OCTAL_VAL (header->header.devmajor) < devmajor)
+       {
+         xheader_store ("devmajor", st, NULL);
+         devmajor = 0;
+       }
+      MAJOR_TO_CHARS (devmajor, header->header.devmajor);
 
-      if (st->devminor > MAXOCTAL7 && archive_format == POSIX_FORMAT)
-       xheader_store ("devminor", st, NULL);
-      else
-       MAJOR_TO_CHARS (st->devminor, header->header.devminor);
+      if (archive_format == POSIX_FORMAT
+         && MAX_OCTAL_VAL (header->header.devminor) < devminor)
+       {
+         xheader_store ("devminor", st, NULL);
+         devminor = 0;
+       }
+      MINOR_TO_CHARS (devminor, header->header.devminor);
     }
   else if (archive_format != GNU_FORMAT && archive_format != OLDGNU_FORMAT)
     {
@@ -750,15 +783,13 @@ start_header (struct tar_stat_info *st)
          && (strlen (st->uname) > UNAME_FIELD_SIZE
              || !string_ascii_p (st->uname)))
        xheader_store ("uname", st, NULL);
-      else
-       UNAME_TO_CHARS (st->uname, header->header.uname);
+      UNAME_TO_CHARS (st->uname, header->header.uname);
 
       if (archive_format == POSIX_FORMAT
          && (strlen (st->gname) > GNAME_FIELD_SIZE
              || !string_ascii_p (st->gname)))
        xheader_store ("gname", st, NULL);
-      else
-       GNAME_TO_CHARS (st->gname, header->header.gname);
+      GNAME_TO_CHARS (st->gname, header->header.gname);
     }
 
   return header;
@@ -888,7 +919,7 @@ dump_regular_file (int fd, struct tar_stat_info *st)
       size_left -= count;
       if (count)
        set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
-      
+
       if (count != bufsize)
        {
          char buf[UINTMAX_STRSIZE_BOUND];
@@ -909,7 +940,8 @@ dump_regular_file (int fd, struct tar_stat_info *st)
 }
 
 static void
-dump_regular_finish (int fd, struct tar_stat_info *st, time_t original_ctime)
+dump_regular_finish (int fd, struct tar_stat_info *st,
+                    struct timespec original_ctime)
 {
   if (fd >= 0)
     {
@@ -918,7 +950,9 @@ dump_regular_finish (int fd, struct tar_stat_info *st, time_t original_ctime)
        {
          stat_diag (st->orig_file_name);
        }
-      else if (final_stat.st_ctime != original_ctime)
+      else if (final_stat.st_ctime != original_ctime.tv_sec
+              || (get_stat_ctime (&final_stat).tv_nsec
+                  != original_ctime.tv_nsec))
        {
          WARN ((0, 0, _("%s: file changed as we read it"),
                 quotearg_colon (st->orig_file_name)));
@@ -1270,9 +1304,8 @@ dump_hard_link (struct tar_stat_info *st)
 
          block_ordinal = current_block_ordinal ();
          assign_string (&st->link_name, link_name);
-         if ((archive_format == OLDGNU_FORMAT
-              && OLDGNU_NAME_FIELD_SIZE < strlen (link_name))
-             || NAME_FIELD_SIZE < strlen (link_name))
+         if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
+             < strlen (link_name))
            write_long_link (st);
 
          st->stat.st_size = 0;
@@ -1354,15 +1387,15 @@ dump_file0 (struct tar_stat_info *st, char *p,
 {
   union block *header;
   char type;
-  time_t original_ctime;
-  struct utimbuf restore_times;
+  struct timespec original_ctime;
+  struct timespec restore_times[2];
   off_t block_ordinal = -1;
 
   if (interactive_option && !confirm ("add", p))
     return;
 
   assign_string (&st->orig_file_name, p);
-  assign_string (&st->file_name, 
+  assign_string (&st->file_name,
                  safer_name_suffix (p, false, absolute_names_option));
 
   if (deref_stat (dereference_option, p, &st->stat) != 0)
@@ -1371,10 +1404,9 @@ dump_file0 (struct tar_stat_info *st, char *p,
       return;
     }
   st->archive_file_size = st->stat.st_size;
-  sys_stat_nanoseconds (st);
-  original_ctime = st->stat.st_ctime;
-  restore_times.actime = st->stat.st_atime;
-  restore_times.modtime = st->stat.st_mtime;
+  original_ctime = get_stat_ctime (&st->stat);
+  restore_times[0] = get_stat_atime (&st->stat);
+  restore_times[1] = get_stat_mtime (&st->stat);
 
 #ifdef S_ISHIDDEN
   if (S_ISHIDDEN (st->stat.st_mode))
@@ -1420,7 +1452,7 @@ dump_file0 (struct tar_stat_info *st, char *p,
     {
       dump_dir (st, top_level, parent_device);
       if (atime_preserve_option)
-       utime (p, &restore_times);
+       utimens (p, restore_times);
       return;
     }
   else
@@ -1486,7 +1518,7 @@ dump_file0 (struct tar_stat_info *st, char *p,
            }
 
          if (atime_preserve_option)
-           utime (st->orig_file_name, &restore_times);
+           utimens (st->orig_file_name, restore_times);
          file_count_links (st);
          return;
        }
@@ -1507,8 +1539,7 @@ dump_file0 (struct tar_stat_info *st, char *p,
            }
          buffer[size] = '\0';
          assign_string (&st->link_name, buffer);
-         if ((archive_format == OLDGNU_FORMAT && size > OLDGNU_NAME_FIELD_SIZE)
-             || size > NAME_FIELD_SIZE)
+         if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size)
            write_long_link (st);
 
          block_ordinal = current_block_ordinal ();
index 0e758a2c2456c4b50d526e67f017cfda850dd5ea..9be0a8b1392b95ae200192bdb131c86f0963f532 100644 (file)
@@ -1,7 +1,7 @@
 /* Extract files from a tar archive.
 
    Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
-   2001, 2003, 2004 Free Software Foundation, Inc.
+   2001, 2003, 2004, 2005 Free Software Foundation, Inc.
 
    Written by John Gilmore, on 1985-11-19.
 
 
 #include <system.h>
 #include <quotearg.h>
+#include <utimens.h>
 #include <errno.h>
 #include <xgetcwd.h>
 
-#if HAVE_UTIME_H
-# include <utime.h>
-#else
-struct utimbuf
-  {
-    long actime;
-    long modtime;
-  };
-#endif
-
 #include "common.h"
 
 static bool we_are_root;       /* true if our effective uid == 0 */
@@ -201,15 +192,30 @@ set_mode (char const *file_name,
 
 /* Check time after successfully setting FILE_NAME's time stamp to T.  */
 static void
-check_time (char const *file_name, time_t t)
+check_time (char const *file_name, struct timespec t)
 {
-  time_t now;
-  if (t <= 0)
+  if (t.tv_sec <= 0)
     WARN ((0, 0, _("%s: implausibly old time stamp %s"),
-          file_name, tartime (t)));
-  else if (start_time < t && (now = time (0)) < t)
-    WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"),
-          file_name, tartime (t), (unsigned long) (t - now)));
+          file_name, tartime (t, true)));
+  else if (timespec_lt (start_time, t))
+    {
+      struct timespec now;
+      gettime (&now);
+      if (timespec_lt (now, t))
+       {
+         unsigned long int ds = t.tv_sec - now.tv_sec;
+         int dns = t.tv_nsec - now.tv_nsec;
+         char dnsbuf[sizeof ".FFFFFFFFF"];
+         if (dns < 0)
+           {
+             dns += 1000000000;
+             ds--;
+           }
+         code_ns_fraction (dns, dnsbuf);
+         WARN ((0, 0, _("%s: time stamp %s is %lu%s s in the future"),
+                file_name, tartime (t, true), ds, dnsbuf));
+       }
+    }
 }
 
 /* Restore stat attributes (owner, group, mode and times) for
@@ -233,8 +239,6 @@ set_stat (char const *file_name,
          mode_t invert_permissions, enum permstatus permstatus,
          char typeflag)
 {
-  struct utimbuf utimbuf;
-
   if (typeflag != SYMTYPE)
     {
       /* We do the utime before the chmod because some versions of utime are
@@ -248,19 +252,16 @@ set_stat (char const *file_name,
 
          /* FIXME: incremental_option should set ctime too, but how?  */
 
-         if (incremental_option)
-           utimbuf.actime = stat_info->st_atime;
-         else
-           utimbuf.actime = start_time;
-
-         utimbuf.modtime = stat_info->st_mtime;
+         struct timespec ts[2];
+         ts[0] = incremental_option ? get_stat_atime (stat_info) : start_time;
+         ts[1] = get_stat_mtime (stat_info);
 
-         if (utime (file_name, &utimbuf) < 0)
+         if (utimens (file_name, ts) != 0)
            utime_error (file_name);
          else
            {
-             check_time (file_name, utimbuf.actime);
-             check_time (file_name, utimbuf.modtime);
+             check_time (file_name, ts[0]);
+             check_time (file_name, ts[1]);
            }
        }
 
@@ -778,7 +779,7 @@ extract_file (char *file_name, int typeflag)
 static int
 extract_link (char *file_name, int typeflag)
 {
-  char const *link_name = safer_name_suffix (current_stat_info.link_name, 
+  char const *link_name = safer_name_suffix (current_stat_info.link_name,
                                              true, absolute_names_option);
   int interdir_made = 0;
 
@@ -1134,7 +1135,7 @@ extract_archive (void)
   if (verbose_option)
     print_header (&current_stat_info, -1);
 
-  file_name = safer_name_suffix (current_stat_info.file_name, 
+  file_name = safer_name_suffix (current_stat_info.file_name,
                                  false, absolute_names_option);
   if (strip_name_components)
     {
index 9bd09ffb46d2b50f06e55f955ab77b50c3f78a0a..69805a7f779dc72710865febf85182abc4972636 100644 (file)
@@ -354,6 +354,8 @@ read_directory_file (void)
                _("Time stamp out of range")));
       else
        {
+         /* FIXME: This should also input nanoseconds, but that will be a
+            change in file format.  */
          newer_mtime_option.tv_sec = t;
          newer_mtime_option.tv_nsec = 0;
        }
@@ -444,7 +446,9 @@ write_directory_file (void)
   if (sys_truncate (fileno (fp)) != 0)
     truncate_error (listed_incremental_option);
 
-  fprintf (fp, "%lu\n", (unsigned long) start_time);
+  /* FIXME: This should also output nanoseconds, but that will be a
+     change in file format.  */
+  fprintf (fp, "%lu\n", (unsigned long int) start_time.tv_sec);
   if (! ferror (fp) && directory_table)
     hash_do_for_each (directory_table, write_directory_file_entry, fp);
   if (ferror (fp))
index e56e7dce930d5e42c92aff77a1857c37fa1b6192..ddf044df0e1f1621b8669aceacbf42df43b2c297 100644 (file)
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
-/* Define to non-zero for forcing old ctime format instead of ISO format.  */
-#undef USE_OLD_CTIME
-
 #include <system.h>
+#include <inttostr.h>
 #include <quotearg.h>
 
 #include "common.h"
@@ -68,6 +66,7 @@ read_and (void (*do_something) (void))
 {
   enum read_header status = HEADER_STILL_UNREAD;
   enum read_header prev_status;
+  struct timespec mtime;
 
   base64_init ();
   name_gather ();
@@ -95,13 +94,12 @@ read_and (void (*do_something) (void))
              || (NEWER_OPTION_INITIALIZED (newer_mtime_option)
                  /* FIXME: We get mtime now, and again later; this causes
                     duplicate diagnostics if header.mtime is bogus.  */
-                 && ((current_stat_info.stat.st_mtime
+                 && ((mtime.tv_sec
                       = TIME_FROM_HEADER (current_header->header.mtime)),
-#ifdef ST_MTIM_NSEC
                      /* FIXME: Grab fractional time stamps from
                         extended header.  */
-                     current_stat_info.stat.st_mtim.ST_MTIM_NSEC = 0,
-#endif
+                     mtime.tv_nsec = 0,
+                     set_stat_mtime (&current_stat_info.stat, mtime),
                      OLDER_STAT_TIME (current_stat_info.stat, m)))
              || excluded_name (current_stat_info.file_name))
            {
@@ -522,6 +520,9 @@ decode_header (union block *header, struct tar_stat_info *stat_info,
               enum archive_format *format_pointer, int do_user_group)
 {
   enum archive_format format;
+  struct timespec atime;
+  struct timespec ctime;
+  struct timespec mtime;
 
   if (strcmp (header->header.magic, TMAGIC) == 0)
     {
@@ -543,22 +544,31 @@ decode_header (union block *header, struct tar_stat_info *stat_info,
   *format_pointer = format;
 
   stat_info->stat.st_mode = MODE_FROM_HEADER (header->header.mode);
-  stat_info->stat.st_mtime = TIME_FROM_HEADER (header->header.mtime);
+  mtime.tv_sec = TIME_FROM_HEADER (header->header.mtime);
+  mtime.tv_nsec = 0;
+  set_stat_mtime (&stat_info->stat, mtime);
   assign_string (&stat_info->uname,
                 header->header.uname[0] ? header->header.uname : NULL);
   assign_string (&stat_info->gname,
                 header->header.gname[0] ? header->header.gname : NULL);
-  stat_info->devmajor = MAJOR_FROM_HEADER (header->header.devmajor);
-  stat_info->devminor = MINOR_FROM_HEADER (header->header.devminor);
-
-  stat_info->stat.st_atime = start_time;
-  stat_info->stat.st_ctime = start_time;
 
   if (format == OLDGNU_FORMAT && incremental_option)
     {
-      stat_info->stat.st_atime = TIME_FROM_HEADER (header->oldgnu_header.atime);
-      stat_info->stat.st_ctime = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+      atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime);
+      ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+      atime.tv_nsec = ctime.tv_nsec = 0;
+    }
+  else if (format == STAR_FORMAT)
+    {
+      atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime);
+      ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime);
+      atime.tv_nsec = ctime.tv_nsec = 0;
     }
+  else
+    atime = ctime = start_time;
+
+  set_stat_atime (&stat_info->stat, atime);
+  set_stat_ctime (&stat_info->stat, ctime);
 
   if (format == V7_FORMAT)
     {
@@ -568,13 +578,6 @@ decode_header (union block *header, struct tar_stat_info *stat_info,
     }
   else
     {
-
-      if (format == STAR_FORMAT)
-       {
-         stat_info->stat.st_atime = TIME_FROM_HEADER (header->star_header.atime);
-         stat_info->stat.st_ctime = TIME_FROM_HEADER (header->star_header.ctime);
-       }
-
       if (do_user_group)
        {
          /* FIXME: Decide if this should somewhat depend on -p.  */
@@ -594,8 +597,9 @@ decode_header (union block *header, struct tar_stat_info *stat_info,
        {
        case BLKTYPE:
        case CHRTYPE:
-         stat_info->stat.st_rdev = makedev (stat_info->devmajor,
-                                            stat_info->devminor);
+         stat_info->stat.st_rdev =
+           makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+                    MINOR_FROM_HEADER (header->header.devminor));
          break;
 
        default:
@@ -922,47 +926,59 @@ uintmax_from_header (const char *p, size_t s)
 
 /* Return a printable representation of T.  The result points to
    static storage that can be reused in the next call to this
-   function, to ctime, or to asctime.  */
+   function, to ctime, or to asctime.  If FULL_TIME, then output the
+   time stamp to its full resolution; otherwise, just output it to
+   1-minute resolution.  */
 char const *
-tartime (time_t t)
+tartime (struct timespec t, bool full_time)
 {
+  enum { fraclen = sizeof ".FFFFFFFFF" - 1 };
   static char buffer[max (UINTMAX_STRSIZE_BOUND + 1,
-                         INT_STRLEN_BOUND (int) + 16)];
+                         INT_STRLEN_BOUND (int) + 16)
+                    + fraclen];
+  struct tm *tm;
+  time_t s = t.tv_sec;
+  int ns = t.tv_nsec;
+  bool negative = s < 0;
   char *p;
 
-#if USE_OLD_CTIME
-  p = ctime (&t);
-  if (p)
+  if (negative && ns != 0)
     {
-      char const *time_stamp = p + 4;
-      for (p += 16; p[3] != '\n'; p++)
-       p[0] = p[3];
-      p[0] = '\0';
-      return time_stamp;
+      s++;
+      ns = 1000000000 - ns;
     }
-#else
-  /* Use ISO 8610 format.  See:
-     http://www.cl.cam.ac.uk/~mgk25/iso-time.html  */
-  struct tm *tm = utc_option ? gmtime (&t) : localtime (&t);
+
+  tm = utc_option ? gmtime (&s) : localtime (&s);
   if (tm)
     {
-      sprintf (buffer, "%04ld-%02d-%02d %02d:%02d:%02d",
-              tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
-              tm->tm_hour, tm->tm_min, tm->tm_sec);
+      if (full_time)
+       {
+         sprintf (buffer, "%04ld-%02d-%02d %02d:%02d:%02d",
+                  tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
+                  tm->tm_hour, tm->tm_min, tm->tm_sec);
+         code_ns_fraction (ns, buffer + strlen (buffer));
+       }
+      else
+       sprintf (buffer, "%04ld-%02d-%02d %02d:%02d",
+                tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
+                tm->tm_hour, tm->tm_min);
       return buffer;
     }
-#endif
 
   /* The time stamp cannot be broken down, most likely because it
      is out of range.  Convert it as an integer,
      right-adjusted in a field with the same width as the usual
-     19-byte 4-year ISO time format.  */
-  p = stringify_uintmax_t_backwards (t < 0 ? - (uintmax_t) t : (uintmax_t) t,
-                                    buffer + sizeof buffer);
-  if (t < 0)
+     4-year ISO time format.  */
+  p = umaxtostr (negative ? - (uintmax_t) s : s,
+                buffer + sizeof buffer - UINTMAX_STRSIZE_BOUND - fraclen);
+  if (negative)
     *--p = '-';
-  while (buffer + sizeof buffer - 19 - 1 < p)
+  while ((buffer + sizeof buffer - sizeof "YYYY-MM-DD HH:MM"
+         + (full_time ? sizeof ":SS.FFFFFFFFF" - 1 : 0))
+        < p)
     *--p = ' ';
+  if (full_time)
+    code_ns_fraction (ns, buffer + sizeof buffer - 1 - fraclen);
   return p;
 }
 
@@ -979,23 +995,24 @@ tartime (time_t t)
 /* FIXME: Note that print_header uses the globals HEAD, HSTAT, and
    HEAD_STANDARD, which must be set up in advance.  Not very clean..  */
 
-/* UGSWIDTH starts with 18, so with user and group names <= 8 chars, the
-   columns never shift during the listing.  */
-#define UGSWIDTH 18
-static int ugswidth = UGSWIDTH;        /* maximum width encountered so far */
+/* Width of "user/group size", with initial value chosen
+   heuristically.  This grows as needed, though this may cause some
+   stairstepping in the output.  Make it too small and the output will
+   almost always look ragged.  Make it too large and the output will
+   be spaced out too far.  */
+static int ugswidth = 19;
 
-/* DATEWIDTH is the number of columns taken by the date and time fields.  */
-#if USE_OLD_CDATE
-# define DATEWIDTH 19
-#else
-# define DATEWIDTH 18
-#endif
+/* Width of printed time stamps.  It grows if longer time stamps are
+   found (typically, those with nanosecond resolution).  Like
+   USGWIDTH, some stairstepping may occur.  */
+static int datewidth = sizeof "YYYY-MM-DD HH:MM" - 1;
 
 void
 print_header (struct tar_stat_info *st, off_t block_ordinal)
 {
   char modes[11];
   char const *time_stamp;
+  int time_stamp_len;
   char *temp_name = st->orig_file_name ? st->orig_file_name : st->file_name;
 
   /* These hold formatted ints.  */
@@ -1005,6 +1022,7 @@ print_header (struct tar_stat_info *st, off_t block_ordinal)
                                /* holds formatted size or major,minor */
   char uintbuf[UINTMAX_STRSIZE_BOUND];
   int pad;
+  int sizelen;
 
   if (block_number_option)
     {
@@ -1084,7 +1102,10 @@ print_header (struct tar_stat_info *st, off_t block_ordinal)
 
       /* Time stamp.  */
 
-      time_stamp = tartime (st->stat.st_mtime);
+      time_stamp = tartime (get_stat_mtime (&st->stat), false);
+      time_stamp_len = strlen (time_stamp);
+      if (datewidth < time_stamp_len)
+       datewidth = time_stamp_len;
 
       /* User and group names.  */
 
@@ -1159,12 +1180,14 @@ print_header (struct tar_stat_info *st, off_t block_ordinal)
 
       /* Figure out padding and print the whole line.  */
 
-      pad = strlen (user) + strlen (group) + strlen (size) + 1;
+      sizelen = strlen (size);
+      pad = strlen (user) + 1 + strlen (group) + 1 + sizelen;
       if (pad > ugswidth)
        ugswidth = pad;
 
-      fprintf (stdlis, "%s %s/%s %*s%s %s",
-              modes, user, group, ugswidth - pad, "", size, time_stamp);
+      fprintf (stdlis, "%s %s/%s %*s %-*s",
+              modes, user, group, ugswidth - pad + sizelen, size,
+              datewidth, time_stamp);
 
       fprintf (stdlis, " %s", quotearg (temp_name));
 
@@ -1248,7 +1271,7 @@ print_for_mkdir (char *dirname, int length, mode_t mode)
                   STRINGIFY_BIGINT (current_block_ordinal (), buf));
        }
 
-      fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH,
+      fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + 1 + datewidth,
               _("Creating directory:"), length, quotearg (dirname));
     }
 }
index 2ba52a38ae58fd935de74e9c13662184a2a52c90..55bedd2b2c901669ff4025feadbb2fd338984831 100644 (file)
@@ -206,6 +206,40 @@ unquote_string (char *string)
   return result;
 }
 \f
+/* Handling numbers.  */
+
+/* Output fraction and trailing digits appropriate for a nanoseconds
+   count equal to NS, but don't output unnecessary '.' or trailing
+   zeros.  */
+
+void
+code_ns_fraction (int ns, char *p)
+{
+  if (ns == 0)
+    *p = '\0';
+  else
+    {
+      int i = 9;
+      *p++ = '.';
+
+      while (ns % 10 == 0)
+       {
+         ns /= 10;
+         i--;
+       }
+
+      p[i] = '\0';
+
+      for (;;)
+       {
+         p[--i] = '0' + ns % 10;
+         if (i == 0)
+           break;
+         ns /= 10;
+       }
+    }
+}
+\f
 /* File handling.  */
 
 /* Saved names in case backup needs to be undone.  */
index dee82b2285cb242d7208e4bf46f4cb9722892abd..b1e4eef78ba3c53c53110f63e5d569a2a0e562ea 100644 (file)
 #include <rmt.h>
 #include <signal.h>
 
-void
-sys_stat_nanoseconds (struct tar_stat_info *st)
-{
-#if defined(HAVE_STRUCT_STAT_ST_SPARE1)
-  st->atime_nsec = st->stat.st_spare1 * 1000;
-  st->mtime_nsec = st->stat.st_spare2 * 1000;
-  st->ctime_nsec = st->stat.st_spare3 * 1000;
-#elif defined(HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
-  st->atime_nsec = st->stat.st_atim.tv_nsec;
-  st->mtime_nsec = st->stat.st_mtim.tv_nsec;
-  st->ctime_nsec = st->stat.st_ctim.tv_nsec;
-#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC)
-  st->atime_nsec = st->stat.st_atimespec.tv_nsec;
-  st->mtime_nsec = st->stat.st_mtimespec.tv_nsec;
-  st->ctime_nsec = st->stat.st_ctimespec.tv_nsec;
-#elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC)
-  st->atime_nsec = st->stat.st_atimensec;
-  st->mtime_nsec = st->stat.st_mtimensec;
-  st->ctime_nsec = st->stat.st_ctimensec;
-#else
-  st->atime_nsec  = st->mtime_nsec = st->ctime_nsec = 0;
-#endif
-}
-
 #if MSDOS
 
 bool
@@ -642,7 +618,7 @@ static void
 oct_to_env (char *envar, unsigned long num)
 {
   char buf[1+1+(sizeof(unsigned long)*CHAR_BIT+2)/3];
-  
+
   snprintf (buf, sizeof buf, "0%lo", num);
   setenv (envar, buf, 1);
 }
@@ -679,7 +655,7 @@ stat_to_env (char *name, char type, struct tar_stat_info *st)
   dec_to_env ("TAR_CTIME", st->stat.st_ctime);
   dec_to_env ("TAR_SIZE", st->stat.st_size);
   dec_to_env ("TAR_UID", st->stat.st_uid);
-  dec_to_env ("TAR_GID", st->stat.st_gid);     
+  dec_to_env ("TAR_GID", st->stat.st_gid);
 
   switch (type)
     {
@@ -713,7 +689,7 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
 {
   int p[2];
   char *argv[4];
-  
+
   xpipe (p);
   pipe_handler = signal (SIGPIPE, SIG_IGN);
   pid = xfork ();
@@ -727,7 +703,7 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
   /* Child */
   xdup2 (p[PREAD], STDIN_FILENO);
   xclose (p[PWRITE]);
-  
+
   stat_to_env (file_name, typechar, st);
 
   argv[0] = "/bin/sh";
@@ -744,7 +720,7 @@ void
 sys_wait_command (void)
 {
   int status;
-  
+
   if (pid < 0)
     return;
 
@@ -776,4 +752,3 @@ sys_wait_command (void)
 }
 
 #endif /* not MSDOS */
-
index 692120e0aa0306f4395996812dd5b6963b65da74..b0e039dbc7ff28d6970103fd4d5dabd9801d09db 100644 (file)
--- a/src/tar.c
+++ b/src/tar.c
@@ -656,7 +656,7 @@ read_name_from_file (FILE *fp, struct obstack *stk)
 {
   int c;
   size_t counter = 0;
-  
+
   for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp))
     {
       if (c == 0)
@@ -721,7 +721,7 @@ update_argv (const char *filename, struct argp_state *state)
   size_t new_argc;
   bool is_stdin = false;
   enum read_file_list_state read_state;
-  
+
   if (!strcmp (filename, "-"))
     {
       is_stdin = true;
@@ -741,7 +741,7 @@ update_argv (const char *filename, struct argp_state *state)
   if (read_state == file_list_zero)
     {
       size_t size;
-      
+
       WARN ((0, 0, N_("%s: file name read contains nul character"),
             quotearg_colon (filename)));
 
@@ -751,7 +751,7 @@ update_argv (const char *filename, struct argp_state *state)
       for (; size > 0; size--, p++)
        if (*p)
          obstack_1grow (&argv_stk, *p);
-        else 
+        else
          obstack_1grow (&argv_stk, '\n');
       obstack_1grow (&argv_stk, 0);
       count = 1;
@@ -973,15 +973,14 @@ parse_opt (int key, char *arg, struct argp_state *state)
              stat_error (arg);
              USAGE_ERROR ((0, 0, _("Date sample file not found")));
            }
-         newer_mtime_option.tv_sec = st.st_mtime;
-         newer_mtime_option.tv_nsec = TIMESPEC_NS (st.st_mtim);
+         newer_mtime_option = get_stat_mtime (&st);
        }
       else
        {
          if (! get_date (&newer_mtime_option, arg, NULL))
            {
              WARN ((0, 0, _("Substituting %s for unknown date format %s"),
-                    tartime (newer_mtime_option.tv_sec), quote (arg)));
+                    tartime (newer_mtime_option, false), quote (arg)));
              newer_mtime_option.tv_nsec = 0;
            }
          else
@@ -1819,16 +1818,10 @@ decode_options (int argc, char **argv)
 
   if (verbose_option && args.textual_date_option)
     {
-      /* FIXME: tartime should support nanoseconds, too, so that this
-        comparison doesn't complain about lost nanoseconds.  */
-      char const *treated_as = tartime (newer_mtime_option.tv_sec);
+      char const *treated_as = tartime (newer_mtime_option, true);
       if (strcmp (args.textual_date_option, treated_as) != 0)
-       WARN ((0, 0,
-              ngettext ("Treating date `%s' as %s + %ld nanosecond",
-                        "Treating date `%s' as %s + %ld nanoseconds",
-                        newer_mtime_option.tv_nsec),
-              args.textual_date_option, treated_as,
-              newer_mtime_option.tv_nsec));
+       WARN ((0, 0, _("Treating date `%s' as %s"),
+              args.textual_date_option, treated_as));
     }
 }
 
index cc937bbc73dfac1e853f8f1e9cf598799bbad14e..3e958ec80086a526560e4f31c33486cc528a5a06 100644 (file)
--- a/src/tar.h
+++ b/src/tar.h
@@ -277,17 +277,10 @@ struct tar_stat_info
                               trailing slash before it was normalized. */
   char *link_name;          /* name of link for the current archive entry.  */
 
-  unsigned int  devminor;   /* device minor number */
-  unsigned int  devmajor;   /* device major number */
   char          *uname;     /* user name of owner */
   char          *gname;     /* group name of owner */
   struct stat   stat;       /* regular filesystem stat */
 
-  /* Nanosecond parts of file timestamps (if available) */
-  unsigned long atime_nsec;
-  unsigned long mtime_nsec;
-  unsigned long ctime_nsec;
-
   off_t archive_file_size;  /* Size of file as stored in the archive.
                               Equals stat.st_size for non-sparse files */
 
index a45d24a902f521d160e7303509119120f1c87035..e88934e85ec82823e8b6ee1ebcf55338b3feba46 100644 (file)
 
 #include <fnmatch.h>
 #include <hash.h>
+#include <inttostr.h>
 #include <quotearg.h>
 #include <stpcpy.h>
-#include <xstrtol.h>
 
 #include "common.h"
 
 #include <fnmatch.h>
 
+#if !HAVE_DECL_STRTOIMAX && !defined strtoimax
+intmax_t strtoimax ();
+#endif
+#if !HAVE_DECL_STRTOUMAX && !defined strtoumax
+uintmax_t strtoumax ();
+#endif
+
 static bool xheader_protected_pattern_p (char const *pattern);
 static bool xheader_protected_keyword_p (char const *keyword);
 static void xheader_set_single_keyword (char *) __attribute__ ((noreturn));
@@ -50,6 +57,8 @@ static size_t global_header_count;
 
    However it should wait until buffer.c is finally rewritten */
 
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
 \f
 /* Keyword options */
 
@@ -191,26 +200,6 @@ xheader_set_option (char *string)
     }
 }
 
-static void
-to_decimal (uintmax_t value, char *where, size_t size)
-{
-  size_t i = 0, j;
-
-  where[i++] = 0;
-  do
-    {
-      where[i++] = '0' + value % 10;
-      value /= 10;
-    }
-  while (i < size && value);
-  for (j = 0, i--; j < i; j++, i--)
-    {
-      char c = where[j];
-      where[j] = where[i];
-      where[i] = c;
-    }
-}
-
 /*
     string Includes:          Replaced By:
      %d                       The directory name of the file,
@@ -232,8 +221,10 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n)
   const char *p;
   char *dir = NULL;
   char *base = NULL;
-  char pidbuf[64];
-  char nbuf[64];
+  char pidbuf[UINTMAX_STRSIZE_BOUND];
+  char const *pptr;
+  char nbuf[UINTMAX_STRSIZE_BOUND];
+  char const *nptr = NULL;
 
   for (p = fmt; *p && (p = strchr (p, '%')); )
     {
@@ -246,7 +237,7 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n)
        case 'd':
          if (st)
            {
-             dir = safer_name_suffix (dir_name (st->orig_file_name), 
+             dir = safer_name_suffix (dir_name (st->orig_file_name),
                                       false, absolute_names_option);
              len += strlen (dir) - 1;
            }
@@ -261,15 +252,15 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n)
          break;
 
        case 'p':
-         to_decimal (getpid (), pidbuf, sizeof pidbuf);
-         len += strlen (pidbuf) - 1;
+         pptr = umaxtostr (getpid (), pidbuf);
+         len += pidbuf + sizeof pidbuf - 1 - pptr - 1;
          break;
 
        case 'n':
          if (allow_n)
            {
-             to_decimal (global_header_count + 1, pidbuf, sizeof pidbuf);
-             len += strlen (nbuf) - 1;
+             nptr = umaxtostr (global_header_count + 1, nbuf);
+             len += nbuf + sizeof nbuf - 1 - nptr - 1;
            }
          break;
        }
@@ -301,14 +292,14 @@ xheader_format_name (struct tar_stat_info *st, const char *fmt, bool allow_n)
              break;
 
            case 'p':
-             q = stpcpy (q, pidbuf);
+             q = stpcpy (q, pptr);
              p += 2;
              break;
 
            case 'n':
-             if (allow_n)
+             if (nptr)
                {
-                 q = stpcpy (q, nbuf);
+                 q = stpcpy (q, nptr);
                  p += 2;
                }
              /* else fall through */
@@ -492,9 +483,11 @@ decode_record (char **ptr,
   errno = 0;
   len = strtoul (p, &len_lim, 10);
 
-  if (len_max < len || (len == ULONG_MAX && errno == ERANGE))
+  if (len_max < len)
     {
-      ERROR ((0, 0, _("Malformed extended header: length out of range")));
+      int len_len = len_lim - p;
+      ERROR ((0, 0, _("Extended header length %*s is out of range"),
+             len_len, p));
       return false;
     }
 
@@ -650,48 +643,24 @@ xheader_read (union block *p, size_t size)
   while (size > 0);
 }
 
-static size_t
-format_uintmax (uintmax_t val, char *buf, size_t s)
-{
-  if (!buf)
-    {
-      s = 0;
-      do
-       s++;
-      while ((val /= 10) != 0);
-    }
-  else
-    {
-      char *p = buf + s - 1;
-
-      do
-       {
-         *p-- = val % 10 + '0';
-       }
-      while ((val /= 10) != 0);
-
-      while (p >= buf)
-       *p-- = '0';
-    }
-  return s;
-}
-
 static void
 xheader_print (struct xheader *xhdr, char const *keyword, char const *value)
 {
   size_t len = strlen (keyword) + strlen (value) + 3; /* ' ' + '=' + '\n' */
-  size_t p, n = 0;
-  char nbuf[100];
+  size_t p;
+  size_t n = 0;
+  char nbuf[UINTMAX_STRSIZE_BOUND];
+  char const *np;
 
   do
     {
       p = n;
-      n = format_uintmax (len + p, NULL, 0);
+      np = umaxtostr (len + p, nbuf);
+      n = nbuf + sizeof nbuf - 1 - np;
     }
   while (n != p);
 
-  format_uintmax (len + n, nbuf, n);
-  obstack_grow (xhdr->stk, nbuf, n);
+  obstack_grow (xhdr->stk, np, n);
   obstack_1grow (xhdr->stk, ' ');
   obstack_grow (xhdr->stk, keyword, strlen (keyword));
   obstack_1grow (xhdr->stk, '=');
@@ -729,6 +698,24 @@ xheader_destroy (struct xheader *xhdr)
 
 \f
 /* Implementations */
+
+static void
+out_of_range_header (char const *keyword, char const *value,
+                    uintmax_t minus_minval, uintmax_t maxval)
+{
+  char minval_buf[UINTMAX_STRSIZE_BOUND + 1];
+  char maxval_buf[UINTMAX_STRSIZE_BOUND];
+  char *minval_string = umaxtostr (minus_minval, minval_buf + 1);
+  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.).  */
+  ERROR ((0, 0, _("Extended header %s=%s is out of range %s..%s"),
+         keyword, value, minval_string, maxval_string));
+}
+
 static void
 code_string (char const *string, char const *keyword, struct xheader *xhdr)
 {
@@ -758,41 +745,141 @@ decode_string (char **string, char const *arg)
 }
 
 static void
-code_time (time_t t, unsigned long nano,
-          char const *keyword, struct xheader *xhdr)
+code_time (struct timespec t, char const *keyword, struct xheader *xhdr)
 {
-  char sbuf[200];
-  size_t s = format_uintmax (t, NULL, 0);
-  if (s + 11 >= sizeof sbuf)
-    return;
-  format_uintmax (t, sbuf, s);
-  sbuf[s++] = '.';
-  s += format_uintmax (nano, sbuf + s, 9);
-  sbuf[s] = 0;
-  xheader_print (xhdr, keyword, sbuf);
+  time_t s = t.tv_sec;
+  int ns = t.tv_nsec;
+  char sbuf[1/*"-"*/ + UINTMAX_STRSIZE_BOUND + 1/*"."*/ + LOG10_BILLION];
+  char *np;
+  bool negative = s < 0;
+
+  if (negative && ns != 0)
+    {
+      s++;
+      ns = BILLION - ns;
+    }
+
+  np = umaxtostr (negative ? - (uintmax_t) s : (uintmax_t) s, sbuf + 1);
+  if (negative)
+    *--np = '-';
+  code_ns_fraction (ns, sbuf + UINTMAX_STRSIZE_BOUND);
+  xheader_print (xhdr, keyword, np);
 }
 
-static void
-decode_time (char const *arg, time_t *secs, unsigned long *nsecs)
+static bool
+decode_time (struct timespec *ts, char const *arg, char const *keyword)
 {
-  uintmax_t u;
+  time_t s;
+  unsigned long int ns = 0;
   char *p;
-  if (xstrtoumax (arg, &p, 10, &u, "") == LONGINT_OK)
+  char *arg_lim;
+  bool negative = *arg == '-';
+
+  errno = 0;
+
+  if (ISDIGIT (arg[negative]))
     {
-      *secs = u;
-      if (*p == '.' && xstrtoumax (p+1, NULL, 10, &u, "") == LONGINT_OK)
-       *nsecs = u;
+      if (negative)
+       {
+         intmax_t i = strtoimax (arg, &arg_lim, 10);
+         if (TYPE_SIGNED (time_t) ? i < TYPE_MINIMUM (time_t) : i < 0)
+           goto out_of_range;
+         s = i;
+       }
+      else
+       {
+         uintmax_t i = strtoumax (arg, &arg_lim, 10);
+         if (TYPE_MAXIMUM (time_t) < i)
+           goto out_of_range;
+         s = i;
+       }
+
+      p = arg_lim;
+
+      if (errno == ERANGE)
+       goto out_of_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))
+                   goto out_of_range;
+                 s--;
+                 ns = BILLION - ns;
+               }
+           }
+       }
+
+      if (! *p)
+       {
+         ts->tv_sec = s;
+         ts->tv_nsec = ns;
+         return true;
+       }
     }
+
+  ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+         keyword, arg));
+  return false;
+
+ out_of_range:
+  out_of_range_header (keyword, arg, - (uintmax_t) TYPE_MINIMUM (time_t),
+                      TYPE_MAXIMUM (time_t));
+  return false;
 }
 
 static void
 code_num (uintmax_t value, char const *keyword, struct xheader *xhdr)
 {
-  char sbuf[100];
-  size_t s = format_uintmax (value, NULL, 0);
-  format_uintmax (value, sbuf, s);
-  sbuf[s] = 0;
-  xheader_print (xhdr, keyword, sbuf);
+  char sbuf[UINTMAX_STRSIZE_BOUND];
+  xheader_print (xhdr, keyword, umaxtostr (value, sbuf));
+}
+
+static bool
+decode_num (uintmax_t *num, char const *arg, uintmax_t maxval,
+           char const *keyword)
+{
+  uintmax_t u;
+  char *arg_lim;
+
+  if (! (ISDIGIT (*arg)
+        && (errno = 0, u = strtoumax (arg, &arg_lim, 10), !*arg_lim)))
+    {
+      ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+             keyword, arg));
+      return false;
+    }
+
+  if (! (u <= maxval && errno != ERANGE))
+    {
+      out_of_range_header (keyword, arg, 0, maxval);
+      return false;
+    }
+
+  *num = u;
+  return true;
 }
 
 static void
@@ -813,13 +900,15 @@ static void
 atime_coder (struct tar_stat_info const *st, char const *keyword,
             struct xheader *xhdr, void *data __attribute__ ((unused)))
 {
-  code_time (st->stat.st_atime, st->atime_nsec, keyword, xhdr);
+  code_time (get_stat_atime (&st->stat), keyword, xhdr);
 }
 
 static void
 atime_decoder (struct tar_stat_info *st, char const *arg)
 {
-  decode_time (arg, &st->stat.st_atime, &st->atime_nsec);
+  struct timespec ts;
+  if (decode_time (&ts, arg, "atime"))
+    set_stat_atime (&st->stat, ts);
 }
 
 static void
@@ -833,7 +922,7 @@ static void
 gid_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, TYPE_MAXIMUM (gid_t), "gid"))
     st->stat.st_gid = u;
 }
 
@@ -867,26 +956,30 @@ static void
 ctime_coder (struct tar_stat_info const *st, char const *keyword,
             struct xheader *xhdr, void *data __attribute__ ((unused)))
 {
-  code_time (st->stat.st_ctime, st->ctime_nsec, keyword, xhdr);
+  code_time (get_stat_ctime (&st->stat), keyword, xhdr);
 }
 
 static void
 ctime_decoder (struct tar_stat_info *st, char const *arg)
 {
-  decode_time (arg, &st->stat.st_ctime, &st->ctime_nsec);
+  struct timespec ts;
+  if (decode_time (&ts, arg, "ctime"))
+    set_stat_ctime (&st->stat, ts);
 }
 
 static void
 mtime_coder (struct tar_stat_info const *st, char const *keyword,
             struct xheader *xhdr, void *data __attribute__ ((unused)))
 {
-  code_time (st->stat.st_mtime, st->mtime_nsec, keyword, xhdr);
+  code_time (get_stat_mtime (&st->stat), keyword, xhdr);
 }
 
 static void
 mtime_decoder (struct tar_stat_info *st, char const *arg)
 {
-  decode_time (arg, &st->stat.st_mtime, &st->mtime_nsec);
+  struct timespec ts;
+  if (decode_time (&ts, arg, "mtime"))
+    set_stat_mtime (&st->stat, ts);
 }
 
 static void
@@ -915,7 +1008,7 @@ static void
 size_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), "size"))
     st->archive_file_size = st->stat.st_size = u;
 }
 
@@ -930,7 +1023,7 @@ static void
 uid_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, TYPE_MAXIMUM (uid_t), "uid"))
     st->stat.st_uid = u;
 }
 
@@ -958,7 +1051,7 @@ static void
 sparse_size_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), "GNU.sparse.size"))
     st->stat.st_size = u;
 }
 
@@ -974,10 +1067,10 @@ static void
 sparse_numblocks_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, SIZE_MAX, "GNU.sparse.numblocks"))
     {
       st->sparse_map_size = u;
-      st->sparse_map = calloc(st->sparse_map_size, sizeof(st->sparse_map[0]));
+      st->sparse_map = xcalloc (u, sizeof st->sparse_map[0]);
       st->sparse_map_avail = 0;
     }
 }
@@ -994,7 +1087,7 @@ static void
 sparse_offset_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), "GNU.sparse.offset"))
     st->sparse_map[st->sparse_map_avail].offset = u;
 }
 
@@ -1010,15 +1103,12 @@ static void
 sparse_numbytes_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (decode_num (&u, arg, SIZE_MAX, "GNU.sparse.numbytes"))
     {
       if (st->sparse_map_avail == st->sparse_map_size)
-       {
-         st->sparse_map_size *= 2;
-         st->sparse_map = xrealloc (st->sparse_map,
-                                    st->sparse_map_size
-                                    * sizeof st->sparse_map[0]);
-       }
+       st->sparse_map = x2nrealloc (st->sparse_map,
+                                    &st->sparse_map_size,
+                                    sizeof st->sparse_map[0]);
       st->sparse_map[st->sparse_map_avail++].numbytes = u;
     }
 }
This page took 0.08285 seconds and 4 git commands to generate.