bug-tar
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Bug-tar] tar-1.15.1+: numerous bugs in xheader.c


From: Paul Eggert
Subject: Re: [Bug-tar] tar-1.15.1+: numerous bugs in xheader.c
Date: Tue, 21 Jun 2005 23:38:43 -0700
User-agent: Gnus/5.1006 (Gnus v5.10.6) Emacs/21.4 (gnu/linux)

Jim Meyering <address@hidden> writes:

> IMHO, any header decoder/parsing failure should cause tar to fail.

Yes, though I think it's worthwhile for tar to continue to try again
with the next file in the archive, as that's the longstanding
tradition here.  (The patch enclosed below doesn't do that.)

> Here's a patch that fixes all of the above problems:

I reviewed it and found a bunch more problems in the neighborhood,
mostly having to do with time stamp handling.  I installed the
following patch, which fixed most (but not all) the problems I found.

I think the sparse-file handling still has some bugs.  I'll submit a
separate bug report about one of the bugs I found.

2005-06-21  Paul Eggert  <address@hidden>

        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  <address@hidden>

        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.

Index: NEWS
===================================================================
RCS file: /cvsroot/tar/tar/NEWS,v
retrieving revision 1.102
diff -p -u -r1.102 NEWS
--- NEWS        30 May 2005 15:15:25 -0000      1.102
+++ NEWS        22 Jun 2005 06:17:51 -0000
@@ -25,6 +25,15 @@ automatically. It is not necessary to gi
 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: gnulib.modules
===================================================================
RCS file: /cvsroot/tar/tar/gnulib.modules,v
retrieving revision 1.2
diff -p -u -r1.2 gnulib.modules
--- gnulib.modules      15 May 2005 06:23:49 -0000      1.2
+++ gnulib.modules      22 Jun 2005 06:17:51 -0000
@@ -40,6 +40,7 @@ timespec
 unlinkdir
 unlocked-io
 utime
+utimens
 xalloc
 xalloc-die
 xgetcwd
Index: lib/.cvsignore
===================================================================
RCS file: /cvsroot/tar/tar/lib/.cvsignore,v
retrieving revision 1.22
diff -p -u -r1.22 .cvsignore
--- lib/.cvsignore      21 May 2005 23:09:16 -0000      1.22
+++ lib/.cvsignore      22 Jun 2005 06:17:51 -0000
@@ -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: lib/Makefile.tmpl
===================================================================
RCS file: /cvsroot/tar/tar/lib/Makefile.tmpl,v
retrieving revision 1.9
diff -p -u -r1.9 Makefile.tmpl
--- lib/Makefile.tmpl   21 May 2005 23:09:42 -0000      1.9
+++ lib/Makefile.tmpl   22 Jun 2005 06:17:51 -0000
@@ -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: po/POTFILES.in
===================================================================
RCS file: /cvsroot/tar/tar/po/POTFILES.in,v
retrieving revision 1.21
diff -p -u -r1.21 POTFILES.in
--- po/POTFILES.in      21 May 2005 23:09:42 -0000      1.21
+++ po/POTFILES.in      22 Jun 2005 06:17:51 -0000
@@ -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
@@ -19,17 +20,19 @@
 
 # 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: src/buffer.c
===================================================================
RCS file: /cvsroot/tar/tar/src/buffer.c,v
retrieving revision 1.85
diff -p -u -r1.85 buffer.c
--- src/buffer.c        21 May 2005 23:10:42 -0000      1.85
+++ src/buffer.c        22 Jun 2005 06:17:51 -0000
@@ -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_ac
            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: src/common.h
===================================================================
RCS file: /cvsroot/tar/tar/src/common.h,v
retrieving revision 1.54
diff -p -u -r1.54 common.h
--- src/common.h        21 May 2005 23:10:57 -0000      1.54
+++ src/common.h        22 Jun 2005 06:17:51 -0000
@@ -23,19 +23,12 @@
 /* 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
-
 
 
 /* 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_optio
 /* 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
 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_s
 /* 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: src/compare.c
===================================================================
RCS file: /cvsroot/tar/tar/src/compare.c,v
retrieving revision 1.27
diff -p -u -r1.27 compare.c
--- src/compare.c       15 May 2005 03:59:09 -0000      1.27
+++ src/compare.c       22 Jun 2005 06:17:51 -0000
@@ -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.
 
@@ -21,21 +21,12 @@
 
 #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, st
 
 
 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: src/create.c
===================================================================
RCS file: /cvsroot/tar/tar/src/create.c,v
retrieving revision 1.100
diff -p -u -r1.100 create.c
--- src/create.c        13 Jun 2005 19:09:45 -0000      1.100
+++ src/create.c        22 Jun 2005 06:17:52 -0000
@@ -21,17 +21,8 @@
 
 #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, 
   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, 
 }
 
 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';
 }
 
 
@@ -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, 
   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 
       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_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_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_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, ch
 {
   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, ch
       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, ch
     {
       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, ch
            }
 
          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, ch
            }
          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: src/extract.c
===================================================================
RCS file: /cvsroot/tar/tar/src/extract.c,v
retrieving revision 1.77
diff -p -u -r1.77 extract.c
--- src/extract.c       21 May 2005 23:10:42 -0000      1.77
+++ src/extract.c       22 Jun 2005 06:17:52 -0000
@@ -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.
 
@@ -21,19 +21,10 @@
 
 #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 typef
 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: src/incremen.c
===================================================================
RCS file: /cvsroot/tar/tar/src/incremen.c,v
retrieving revision 1.28
diff -p -u -r1.28 incremen.c
--- src/incremen.c      15 May 2005 03:59:10 -0000      1.28
+++ src/incremen.c      22 Jun 2005 06:17:52 -0000
@@ -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: src/list.c
===================================================================
RCS file: /cvsroot/tar/tar/src/list.c,v
retrieving revision 1.90
diff -p -u -r1.90 list.c
--- src/list.c  19 May 2005 15:34:19 -0000      1.90
+++ src/list.c  22 Jun 2005 06:17:52 -0000
@@ -19,10 +19,8 @@
    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, stru
               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, stru
   *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, stru
     }
   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, stru
        {
        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
 
 /* 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)
-    {
-      char const *time_stamp = p + 4;
-      for (p += 16; p[3] != '\n'; p++)
-       p[0] = p[3];
-      p[0] = '\0';
-      return time_stamp;
-    }
-#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);
+  if (negative && ns != 0)
+    {
+      s++;
+      ns = 1000000000 - ns;
+    }
+
+  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 */
-
-/* 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 "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;
+
+/* 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, 
                                /* 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, 
 
       /* 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, 
 
       /* 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 leng
                   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: src/misc.c
===================================================================
RCS file: /cvsroot/tar/tar/src/misc.c,v
retrieving revision 1.31
diff -p -u -r1.31 misc.c
--- src/misc.c  19 May 2005 15:34:19 -0000      1.31
+++ src/misc.c  22 Jun 2005 06:17:52 -0000
@@ -206,6 +206,40 @@ unquote_string (char *string)
   return result;
 }
 
+/* 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;
+       }
+    }
+}
+
 /* File handling.  */
 
 /* Saved names in case backup needs to be undone.  */
Index: src/system.c
===================================================================
RCS file: /cvsroot/tar/tar/src/system.c,v
retrieving revision 1.12
diff -p -u -r1.12 system.c
--- src/system.c        15 May 2005 03:59:10 -0000      1.12
+++ src/system.c        22 Jun 2005 06:17:52 -0000
@@ -22,30 +22,6 @@
 #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, stru
   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 t
 {
   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 t
   /* 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: src/tar.c
===================================================================
RCS file: /cvsroot/tar/tar/src/tar.c,v
retrieving revision 1.122
diff -p -u -r1.122 tar.c
--- src/tar.c   30 May 2005 15:15:10 -0000      1.122
+++ src/tar.c   22 Jun 2005 06:17:52 -0000
@@ -656,7 +656,7 @@ read_name_from_file (FILE *fp, struct ob
 {
   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, struc
   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, struc
   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, struc
       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 ar
              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: src/tar.h
===================================================================
RCS file: /cvsroot/tar/tar/src/tar.h,v
retrieving revision 1.28
diff -p -u -r1.28 tar.h
--- src/tar.h   15 May 2005 03:59:10 -0000      1.28
+++ src/tar.h   22 Jun 2005 06:17:52 -0000
@@ -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: src/xheader.c
===================================================================
RCS file: /cvsroot/tar/tar/src/xheader.c,v
retrieving revision 1.26
diff -p -u -r1.26 xheader.c
--- src/xheader.c       21 May 2005 23:10:42 -0000      1.26
+++ src/xheader.c       22 Jun 2005 06:17:52 -0000
@@ -20,14 +20,21 @@
 
 #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 };
+
 
 /* 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_inf
   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_inf
        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_inf
          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_inf
              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 siz
   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)
 
 
 /* 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
 }
 
 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;
     }
 }




reply via email to

[Prev in Thread] Current Thread [Next in Thread]