]> Dogcows Code - chaz/tar/blobdiff - src/list.c
Assign orig_file_name
[chaz/tar] / src / list.c
index 5067a86c816341573c78f519c4bfe1c9abcf02ff..16a6970a58f618c1c16aeb0c5ccd2d6b0757134c 100644 (file)
@@ -1,5 +1,8 @@
 /* List a tar archive, with support routines for reading a tar archive.
-   Copyright 1988,92,93,94,96,97,98,1999 Free Software Foundation, Inc.
+
+   Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
+   2001, 2003, 2004, 2005 Free Software Foundation, Inc.
+
    Written by John Gilmore, on 1985-08-26.
 
    This program is free software; you can redistribute it and/or modify it
    with this program; if not, write to the Free Software Foundation, Inc.,
    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
-/* Define to non-zero for forcing old ctime() instead of isotime().  */
+/* Define to non-zero for forcing old ctime format instead of ISO format.  */
 #undef USE_OLD_CTIME
 
-#include "system.h"
+#include <system.h>
 #include <quotearg.h>
 
-#include <time.h>
-
 #include "common.h"
 
 #define max(a, b) ((a) < (b) ? (b) : (a))
 
 union block *current_header;   /* points to current archive header */
-struct stat current_stat;      /* stat struct corresponding */
 enum archive_format current_format; /* recognized format */
+union block *recent_long_name; /* recent long name header and contents */
+union block *recent_long_link; /* likewise, for long link */
+size_t recent_long_name_blocks;        /* number of blocks in recent_long_name */
+size_t recent_long_link_blocks;        /* likewise, for long link */
 
-static uintmax_t from_chars PARAMS ((const char *, size_t, const char *,
-                                    uintmax_t, uintmax_t));
+static uintmax_t from_header (const char *, size_t, const char *,
+                             uintmax_t, uintmax_t, bool, bool);
 
-/* Table of base 64 digit values indexed by unsigned chars.
-   The value is 64 for unsigned chars that are not base 64 digits.  */
-static char base64_map[1 + (unsigned char) -1];
+/* Base 64 digits; see Internet RFC 2045 Table 1.  */
+static char const base_64_digits[64] =
+{
+  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/* Table of base-64 digit values indexed by unsigned chars.
+   The value is 64 for unsigned chars that are not base-64 digits.  */
+static char base64_map[UCHAR_MAX + 1];
 
 static void
 base64_init (void)
@@ -48,27 +62,28 @@ base64_init (void)
     base64_map[(int) base_64_digits[i]] = i;
 }
 
-/*-----------------------------------.
-| Main loop for reading an archive.  |
-`-----------------------------------*/
-
+/* Main loop for reading an archive.  */
 void
-read_and (void (*do_something) ())
+read_and (void (*do_something) (void))
 {
   enum read_header status = HEADER_STILL_UNREAD;
   enum read_header prev_status;
 
   base64_init ();
   name_gather ();
-  open_archive (ACCESS_READ);
 
-  while (1)
+  open_archive (ACCESS_READ);
+  do
     {
       prev_status = status;
-      status = read_header ();
+      tar_stat_destroy (&current_stat_info);
+      xheader_destroy (&extended_header);
+
+      status = read_header (false);
       switch (status)
        {
        case HEADER_STILL_UNREAD:
+       case HEADER_SUCCESS_EXTENDED:
          abort ();
 
        case HEADER_SUCCESS:
@@ -76,39 +91,38 @@ read_and (void (*do_something) ())
          /* Valid header.  We should decode next field (mode) first.
             Ensure incoming names are null terminated.  */
 
-         /* FIXME: This is a quick kludge before 1.12 goes out.  */
-         current_stat.st_mtime
-           = TIME_FROM_CHARS (current_header->header.mtime);
-
-         if (!name_match (current_file_name)
-             || current_stat.st_mtime < newer_mtime_option
-             || excluded_name (current_file_name))
+         if (! name_match (current_stat_info.file_name)
+             || (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
+                      = 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
+                     OLDER_STAT_TIME (current_stat_info.stat, m)))
+             || excluded_name (current_stat_info.file_name))
            {
-             char save_typeflag;
-
-             if (current_header->header.typeflag == GNUTYPE_VOLHDR
-                 || current_header->header.typeflag == GNUTYPE_MULTIVOL
-                 || current_header->header.typeflag == GNUTYPE_NAMES)
+             switch (current_header->header.typeflag)
                {
-                 (*do_something) ();
+               case GNUTYPE_VOLHDR:
+               case GNUTYPE_MULTIVOL:
+               case GNUTYPE_NAMES:
+                 break;
+
+               case DIRTYPE:
+                 if (show_omitted_dirs_option)
+                   WARN ((0, 0, _("%s: Omitting"),
+                          quotearg_colon (current_stat_info.file_name)));
+                 /* Fall through.  */
+               default:
+                 decode_header (current_header,
+                                &current_stat_info, &current_format, 0);
+                 skip_member ();
                  continue;
                }
-             if (show_omitted_dirs_option
-                 && current_header->header.typeflag == DIRTYPE)
-               WARN ((0, 0, _("Omitting %s"), current_file_name));
-
-             /* Skip past it in the archive.  */
-
-             save_typeflag = current_header->header.typeflag;
-             set_next_block_after (current_header);
-             if (current_header->oldgnu_header.isextended)
-               skip_extended_headers ();
-
-             /* Skip to the next header on the archive.  */
-
-             if (save_typeflag != DIRTYPE)
-               skip_file (current_stat.st_size);
-             continue;
            }
 
          (*do_something) ();
@@ -123,10 +137,20 @@ read_and (void (*do_something) ())
            }
 
          set_next_block_after (current_header);
+
+         if (!ignore_zeros_option)
+           {
+             char buf[UINTMAX_STRSIZE_BOUND];
+
+             status = read_header (false);
+             if (status == HEADER_ZERO_BLOCK)
+               break;
+             WARN ((0, 0, _("A lone zero block at %s"),
+                    STRINGIFY_BIGINT (current_block_ordinal (), buf)));
+             break;
+           }
          status = prev_status;
-         if (ignore_zeros_option)
-           continue;
-         break;
+         continue;
 
        case HEADER_END_OF_FILE:
          if (block_number_option)
@@ -144,44 +168,50 @@ read_and (void (*do_something) ())
          switch (prev_status)
            {
            case HEADER_STILL_UNREAD:
-             WARN ((0, 0, _("Hmm, this doesn't look like a tar archive")));
+             ERROR ((0, 0, _("This does not look like a tar archive")));
              /* Fall through.  */
 
            case HEADER_ZERO_BLOCK:
            case HEADER_SUCCESS:
-             WARN ((0, 0, _("Skipping to next file header")));
+             if (block_number_option)
+               {
+                 char buf[UINTMAX_STRSIZE_BOUND];
+                 off_t block_ordinal = current_block_ordinal ();
+                 block_ordinal -= recent_long_name_blocks;
+                 block_ordinal -= recent_long_link_blocks;
+                 fprintf (stdlis, _("block %s: "),
+                          STRINGIFY_BIGINT (block_ordinal, buf));
+               }
+             ERROR ((0, 0, _("Skipping to next header")));
              break;
 
            case HEADER_END_OF_FILE:
            case HEADER_FAILURE:
              /* We are in the middle of a cascade of errors.  */
              break;
+
+           case HEADER_SUCCESS_EXTENDED:
+             abort ();
            }
          continue;
        }
       break;
     }
+  while (!all_names_found (&current_stat_info));
 
-  apply_delayed_set_stat ();
   close_archive ();
   names_notfound ();           /* print names not found */
 }
 
-/*---------------------------------------------.
-| Print a header block, based on tar options.  |
-`---------------------------------------------*/
-
+/* Print a header block, based on tar options.  */
 void
 list_archive (void)
 {
   /* Print the header block.  */
 
+  decode_header (current_header, &current_stat_info, &current_format, 0);
   if (verbose_option)
-    {
-      if (verbose_option > 1)
-       decode_header (current_header, &current_stat, &current_format, 0);
-      print_header ();
-    }
+    print_header (&current_stat_info, -1);
 
   if (incremental_option && current_header->header.typeflag == GNUTYPE_DUMPDIR)
     {
@@ -192,31 +222,29 @@ list_archive (void)
       set_next_block_after (current_header);
       if (multi_volume_option)
        {
-         assign_string (&save_name, current_file_name);
-         save_totsize = current_stat.st_size;
+         assign_string (&save_name, current_stat_info.orig_file_name);
+         save_totsize = current_stat_info.stat.st_size;
        }
-      for (size = current_stat.st_size; size > 0; size -= written)
+      for (size = current_stat_info.stat.st_size; size > 0; size -= written)
        {
          if (multi_volume_option)
            save_sizeleft = size;
          data_block = find_next_block ();
          if (!data_block)
            {
-             ERROR ((0, 0, _("EOF in archive file")));
+             ERROR ((0, 0, _("Unexpected EOF in archive")));
              break;            /* FIXME: What happens, then?  */
            }
          written = available_space_after (data_block);
          if (written > size)
            written = size;
-         errno = 0;            /* FIXME: errno should be read-only */
+         errno = 0;
          check = fwrite (data_block->buffer, sizeof (char), written, stdlis);
          set_next_block_after ((union block *)
                                (data_block->buffer + written - 1));
          if (check != written)
            {
-             ERROR ((0, errno, _("Only wrote %lu of %lu bytes to file %s"),
-                     (unsigned long) check,
-                     (unsigned long) written, current_file_name));
+             write_error_details (current_stat_info.file_name, check, written);
              skip_file (size - written);
              break;
            }
@@ -229,138 +257,182 @@ list_archive (void)
 
     }
 
-  /* Skip past the header in the archive, and past any extended headers.  */
-
-  set_next_block_after (current_header);
-  if (current_header->oldgnu_header.isextended)
-    skip_extended_headers ();
-
   if (multi_volume_option)
-    assign_string (&save_name, current_file_name);
+    assign_string (&save_name, current_stat_info.orig_file_name);
 
-  /* Skip to the next header on the archive.  */
-
-  skip_file (current_stat.st_size);
+  skip_member ();
 
   if (multi_volume_option)
     assign_string (&save_name, 0);
 }
 
-/*-----------------------------------------------------------------------.
-| Read a block that's supposed to be a header block.  Return its address |
-| in "current_header", and if it is good, the file's size in             |
-| current_stat.st_size.                                                  |
-|                                                                        |
-| Return 1 for success, 0 if the checksum is bad, EOF on eof, 2 for a    |
-| block full of zeros (EOF marker).                                      |
-|                                                                        |
-| You must always set_next_block_after(current_header) to skip past the  |
-| header which this routine reads.                                       |
-`-----------------------------------------------------------------------*/
-
+/* Check header checksum */
 /* The standard BSD tar sources create the checksum by adding up the
    bytes in the header as type char.  I think the type char was unsigned
    on the PDP-11, but it's signed on the Next and Sun.  It looks like the
    sources to BSD tar were never changed to compute the checksum
    correctly, so both the Sun and Next add the bytes of the header as
    signed chars.  This doesn't cause a problem until you get a file with
-   a name containing characters with the high bit set.  So read_header
+   a name containing characters with the high bit set.  So tar_checksum
    computes two checksums -- signed and unsigned.  */
 
 enum read_header
-read_header (void)
+tar_checksum (union block *header, bool silent)
 {
   size_t i;
-  int unsigned_sum;            /* the POSIX one :-) */
-  int signed_sum;              /* the Sun one :-( */
+  int unsigned_sum = 0;                /* the POSIX one :-) */
+  int signed_sum = 0;          /* the Sun one :-( */
   int recorded_sum;
   uintmax_t parsed_sum;
   char *p;
+
+  p = header->buffer;
+  for (i = sizeof *header; i-- != 0;)
+    {
+      unsigned_sum += (unsigned char) *p;
+      signed_sum += (signed char) (*p++);
+    }
+
+  if (unsigned_sum == 0)
+    return HEADER_ZERO_BLOCK;
+
+  /* Adjust checksum to count the "chksum" field as blanks.  */
+
+  for (i = sizeof header->header.chksum; i-- != 0;)
+    {
+      unsigned_sum -= (unsigned char) header->header.chksum[i];
+      signed_sum -= (signed char) (header->header.chksum[i]);
+    }
+  unsigned_sum += ' ' * sizeof header->header.chksum;
+  signed_sum += ' ' * sizeof header->header.chksum;
+
+  parsed_sum = from_header (header->header.chksum,
+                           sizeof header->header.chksum, 0,
+                           (uintmax_t) 0,
+                           (uintmax_t) TYPE_MAXIMUM (int), true, silent);
+  if (parsed_sum == (uintmax_t) -1)
+    return HEADER_FAILURE;
+
+  recorded_sum = parsed_sum;
+
+  if (unsigned_sum != recorded_sum && signed_sum != recorded_sum)
+    return HEADER_FAILURE;
+
+  return HEADER_SUCCESS;
+}
+
+/* Read a block that's supposed to be a header block.  Return its
+   address in "current_header", and if it is good, the file's size in
+   current_stat_info.stat.st_size.
+
+   Return 1 for success, 0 if the checksum is bad, EOF on eof, 2 for a
+   block full of zeros (EOF marker).
+
+   If RAW_EXTENDED_HEADERS is nonzero, do not automagically fold the
+   GNU long name and link headers into later headers.
+
+   You must always set_next_block_after(current_header) to skip past
+   the header which this routine reads.  */
+
+enum read_header
+read_header (bool raw_extended_headers)
+{
   union block *header;
-  char **longp;
+  union block *header_copy;
   char *bp;
   union block *data_block;
   size_t size, written;
-  static char *next_long_name, *next_long_link;
+  union block *next_long_name = 0;
+  union block *next_long_link = 0;
+  size_t next_long_name_blocks;
+  size_t next_long_link_blocks;
 
   while (1)
     {
+      enum read_header status;
+
       header = find_next_block ();
       current_header = header;
       if (!header)
        return HEADER_END_OF_FILE;
 
-      unsigned_sum = 0;
-      signed_sum = 0;
-      p = header->buffer;
-      for (i = sizeof *header; i-- != 0;)
-       {
-         unsigned_sum += (unsigned char) *p;
-         signed_sum += signed_char (*p++);
-       }
-
-      if (unsigned_sum == 0)
-       return HEADER_ZERO_BLOCK;
-
-      /* Adjust checksum to count the "chksum" field as blanks.  */
-
-      for (i = sizeof header->header.chksum; i-- != 0;)
-       {
-         unsigned_sum -= (unsigned char) header->header.chksum[i];
-         signed_sum -= signed_char (header->header.chksum[i]);
-       }
-      unsigned_sum += ' ' * sizeof header->header.chksum;
-      signed_sum += ' ' * sizeof header->header.chksum;
-
-      parsed_sum = from_chars (header->header.chksum,
-                              sizeof header->header.chksum, 0,
-                              (uintmax_t) 0, (uintmax_t) TYPE_MAXIMUM (int));
-      if (parsed_sum == (uintmax_t) -1)
-       return HEADER_FAILURE;
-
-      recorded_sum = parsed_sum;
-
-      if (unsigned_sum != recorded_sum && signed_sum != recorded_sum)
-       return HEADER_FAILURE;
+      if ((status = tar_checksum (header, false)) != HEADER_SUCCESS)
+       return status;
 
       /* Good block.  Decode file size and return.  */
 
       if (header->header.typeflag == LNKTYPE)
-       current_stat.st_size = 0;       /* links 0 size on tape */
+       current_stat_info.stat.st_size = 0;     /* links 0 size on tape */
       else
-       current_stat.st_size = OFF_FROM_CHARS (header->header.size);
+       current_stat_info.stat.st_size = OFF_FROM_HEADER (header->header.size);
 
       if (header->header.typeflag == GNUTYPE_LONGNAME
-         || header->header.typeflag == GNUTYPE_LONGLINK)
+         || header->header.typeflag == GNUTYPE_LONGLINK
+         || header->header.typeflag == XHDTYPE
+         || header->header.typeflag == XGLTYPE)
        {
-         longp = ((header->header.typeflag == GNUTYPE_LONGNAME)
-                  ? &next_long_name
-                  : &next_long_link);
-
-         set_next_block_after (header);
-         if (*longp)
-           free (*longp);
-         size = current_stat.st_size;
-         if (size != current_stat.st_size)
-           FATAL_ERROR ((0, 0, _("Memory exhausted")));
-         bp = *longp = xmalloc (size);
-
-         for (; size > 0; size -= written)
+         if (raw_extended_headers)
+           return HEADER_SUCCESS_EXTENDED;
+         else if (header->header.typeflag == GNUTYPE_LONGNAME
+                  || header->header.typeflag == GNUTYPE_LONGLINK)
            {
-             data_block = find_next_block ();
-             if (! data_block)
+             size_t name_size = current_stat_info.stat.st_size;
+             size_t n = name_size % BLOCKSIZE;
+             size = name_size + BLOCKSIZE;
+             if (n)
+               size += BLOCKSIZE - n;
+
+             if (name_size != current_stat_info.stat.st_size
+                 || size < name_size)
+               xalloc_die ();
+
+             header_copy = xmalloc (size + 1);
+
+             if (header->header.typeflag == GNUTYPE_LONGNAME)
                {
-                 ERROR ((0, 0, _("Unexpected EOF on archive file")));
-                 break;
+                 if (next_long_name)
+                   free (next_long_name);
+                 next_long_name = header_copy;
+                 next_long_name_blocks = size / BLOCKSIZE;
+               }
+             else
+               {
+                 if (next_long_link)
+                   free (next_long_link);
+                 next_long_link = header_copy;
+                 next_long_link_blocks = size / BLOCKSIZE;
+               }
+
+             set_next_block_after (header);
+             *header_copy = *header;
+             bp = header_copy->buffer + BLOCKSIZE;
+
+             for (size -= BLOCKSIZE; size > 0; size -= written)
+               {
+                 data_block = find_next_block ();
+                 if (! data_block)
+                   {
+                     ERROR ((0, 0, _("Unexpected EOF in archive")));
+                     break;
+                   }
+                 written = available_space_after (data_block);
+                 if (written > size)
+                   written = size;
+
+                 memcpy (bp, data_block->buffer, written);
+                 bp += written;
+                 set_next_block_after ((union block *)
+                                       (data_block->buffer + written - 1));
                }
-             written = available_space_after (data_block);
-             if (written > size)
-               written = size;
-
-             memcpy (bp, data_block->buffer, written);
-             bp += written;
-             set_next_block_after ((union block *)
-                                   (data_block->buffer + written - 1));
+
+             *bp = '\0';
+           }
+         else if (header->header.typeflag == XHDTYPE)
+           xheader_read (header, OFF_FROM_HEADER (header->header.size));
+         else if (header->header.typeflag == XGLTYPE)
+           {
+             xheader_read (header, OFF_FROM_HEADER (header->header.size));
+             xheader_decode_global ();
            }
 
          /* Loop!  */
@@ -368,19 +440,26 @@ read_header (void)
        }
       else
        {
-         char *name;
-         struct posix_header *h = &current_header->header;
+         char const *name;
+         struct posix_header const *h = &current_header->header;
          char namebuf[sizeof h->prefix + 1 + NAME_FIELD_SIZE + 1];
 
-         name = next_long_name;
-         if (! name)
+         if (recent_long_name)
+           free (recent_long_name);
+
+         if (next_long_name)
+           {
+             name = next_long_name->buffer + BLOCKSIZE;
+             recent_long_name = next_long_name;
+             recent_long_name_blocks = next_long_name_blocks;
+           }
+         else
            {
              /* Accept file names as specified by POSIX.1-1996
                  section 10.1.1.  */
-             int posix_header = strcmp (h->magic, TMAGIC) == 0;
              char *np = namebuf;
 
-             if (posix_header && h->prefix[0])
+             if (h->prefix[0] && strcmp (h->magic, TMAGIC) == 0)
                {
                  memcpy (np, h->prefix, sizeof h->prefix);
                  np[sizeof h->prefix] = '\0';
@@ -390,123 +469,160 @@ read_header (void)
              memcpy (np, h->name, sizeof h->name);
              np[sizeof h->name] = '\0';
              name = namebuf;
+             recent_long_name = 0;
+             recent_long_name_blocks = 0;
            }
-         assign_string (&current_file_name, name);
-         if (next_long_name)
+         assign_string (&current_stat_info.orig_file_name, name);
+         assign_string (&current_stat_info.file_name, name);
+         current_stat_info.had_trailing_slash = strip_trailing_slashes (current_stat_info.file_name);
+
+         if (recent_long_link)
+           free (recent_long_link);
+
+         if (next_long_link)
            {
-             free (next_long_name);
-             next_long_name = 0;
+             name = next_long_link->buffer + BLOCKSIZE;
+             recent_long_link = next_long_link;
+             recent_long_link_blocks = next_long_link_blocks;
            }
-         
-         name = next_long_link;
-         if (! name)
+         else
            {
              memcpy (namebuf, h->linkname, sizeof h->linkname);
              namebuf[sizeof h->linkname] = '\0';
              name = namebuf;
+             recent_long_link = 0;
+             recent_long_link_blocks = 0;
            }
-         assign_string (&current_link_name, name);
-         if (next_long_link)
-           {
-             free (next_long_link);
-             next_long_link = 0;
-           }
+         assign_string (&current_stat_info.link_name, name);
 
          return HEADER_SUCCESS;
        }
     }
 }
 
-/*-------------------------------------------------------------------------.
-| Decode things from a file HEADER block into STAT_INFO, also setting     |
-| *FORMAT_POINTER depending on the header block format.  If DO_USER_GROUP, |
-| decode the user/group information (this is useful for extraction, but           |
-| waste time when merely listing).                                        |
-|                                                                         |
-| read_header() has already decoded the checksum and length, so we don't.  |
-|                                                                         |
-| This routine should *not* be called twice for the same block, since the  |
-| two calls might use different DO_USER_GROUP values and thus might end up |
-| with different uid/gid for the two calls.  If anybody wants the uid/gid  |
-| they should decode it first, and other callers should decode it without  |
-| uid/gid before calling a routine, e.g. print_header, that assumes       |
-| decoded data.                                                                   |
-`-------------------------------------------------------------------------*/
+#define ISOCTAL(c) ((c)>='0'&&(c)<='7')
+
+/* Decode things from a file HEADER block into STAT_INFO, also setting
+   *FORMAT_POINTER depending on the header block format.  If
+   DO_USER_GROUP, decode the user/group information (this is useful
+   for extraction, but waste time when merely listing).
+
+   read_header() has already decoded the checksum and length, so we don't.
 
+   This routine should *not* be called twice for the same block, since
+   the two calls might use different DO_USER_GROUP values and thus
+   might end up with different uid/gid for the two calls.  If anybody
+   wants the uid/gid they should decode it first, and other callers
+   should decode it without uid/gid before calling a routine,
+   e.g. print_header, that assumes decoded data.  */
 void
-decode_header (union block *header, struct stat *stat_info,
+decode_header (union block *header, struct tar_stat_info *stat_info,
               enum archive_format *format_pointer, int do_user_group)
 {
   enum archive_format format;
 
   if (strcmp (header->header.magic, TMAGIC) == 0)
-    format = POSIX_FORMAT;
+    {
+      if (header->star_header.prefix[130] == 0
+         && ISOCTAL (header->star_header.atime[0])
+         && header->star_header.atime[11] == ' '
+         && ISOCTAL (header->star_header.ctime[0])
+         && header->star_header.ctime[11] == ' ')
+       format = STAR_FORMAT;
+      else if (extended_header.size)
+       format = POSIX_FORMAT;
+      else
+       format = USTAR_FORMAT;
+    }
   else if (strcmp (header->header.magic, OLDGNU_MAGIC) == 0)
     format = OLDGNU_FORMAT;
   else
     format = V7_FORMAT;
   *format_pointer = format;
 
-  stat_info->st_mode = MODE_FROM_CHARS (header->header.mode);
-  stat_info->st_mtime = TIME_FROM_CHARS (header->header.mtime);
+  stat_info->stat.st_mode = MODE_FROM_HEADER (header->header.mode);
+  stat_info->stat.st_mtime = TIME_FROM_HEADER (header->header.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->st_atime = TIME_FROM_CHARS (header->oldgnu_header.atime);
-      stat_info->st_ctime = TIME_FROM_CHARS (header->oldgnu_header.ctime);
+      stat_info->stat.st_atime = TIME_FROM_HEADER (header->oldgnu_header.atime);
+      stat_info->stat.st_ctime = TIME_FROM_HEADER (header->oldgnu_header.ctime);
     }
 
   if (format == V7_FORMAT)
     {
-      stat_info->st_uid = UID_FROM_CHARS (header->header.uid);
-      stat_info->st_gid = GID_FROM_CHARS (header->header.gid);
-      stat_info->st_rdev = 0;
+      stat_info->stat.st_uid = UID_FROM_HEADER (header->header.uid);
+      stat_info->stat.st_gid = GID_FROM_HEADER (header->header.gid);
+      stat_info->stat.st_rdev = 0;
     }
   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.  */
 
          if (numeric_owner_option
              || !*header->header.uname
-             || !uname_to_uid (header->header.uname, &stat_info->st_uid))
-           stat_info->st_uid = UID_FROM_CHARS (header->header.uid);
+             || !uname_to_uid (header->header.uname, &stat_info->stat.st_uid))
+           stat_info->stat.st_uid = UID_FROM_HEADER (header->header.uid);
 
          if (numeric_owner_option
              || !*header->header.gname
-             || !gname_to_gid (header->header.gname, &stat_info->st_gid))
-           stat_info->st_gid = GID_FROM_CHARS (header->header.gid);
+             || !gname_to_gid (header->header.gname, &stat_info->stat.st_gid))
+           stat_info->stat.st_gid = GID_FROM_HEADER (header->header.gid);
        }
+
       switch (header->header.typeflag)
        {
        case BLKTYPE:
-         stat_info->st_rdev
-           = makedev (MAJOR_FROM_CHARS (header->header.devmajor),
-                      MINOR_FROM_CHARS (header->header.devminor));
-         break;
-
        case CHRTYPE:
-         stat_info->st_rdev
-           = makedev (MAJOR_FROM_CHARS (header->header.devmajor),
-                      MINOR_FROM_CHARS (header->header.devminor));
+         stat_info->stat.st_rdev = makedev (stat_info->devmajor,
+                                            stat_info->devminor);
          break;
 
        default:
-         stat_info->st_rdev = 0;
+         stat_info->stat.st_rdev = 0;
        }
     }
-}
 
-/*------------------------------------------------------------------------.
-| Convert buffer at WHERE0 of size DIGS from external format to uintmax_t.|
-| The data is of type TYPE.  The buffer must represent a value in the     |
-| range -MINUS_MINVAL through MAXVAL.                                    |
-`------------------------------------------------------------------------*/
+  stat_info->archive_file_size = stat_info->stat.st_size;
+  xheader_decode (stat_info);
 
+  if (sparse_member_p (stat_info))
+    {
+      sparse_fixup_header (stat_info);
+      stat_info->is_sparse = true;
+    }
+  else
+    stat_info->is_sparse = false;
+}
+
+/* Convert buffer at WHERE0 of size DIGS from external format to
+   uintmax_t.  DIGS must be positive.  If TYPE is nonnull, the data
+   are of type TYPE.  The buffer must represent a value in the range
+   -MINUS_MINVAL through MAXVAL.  If OCTAL_ONLY, allow only octal
+   numbers instead of the other GNU extensions.  Return -1 on error,
+   diagnosing the error if TYPE is nonnull and if !SILENT.  */
 static uintmax_t
-from_chars (char const *where0, size_t digs, char const *type,
-           uintmax_t minus_minval, uintmax_t maxval)
+from_header (char const *where0, size_t digs, char const *type,
+            uintmax_t minus_minval, uintmax_t maxval,
+            bool octal_only, bool silent)
 {
   uintmax_t value;
   char const *where = where0;
@@ -522,8 +638,9 @@ from_chars (char const *where0, size_t digs, char const *type,
     {
       if (where == lim)
        {
-         if (type)
+         if (type && !silent)
            ERROR ((0, 0,
+                   /* TRANSLATORS: %s is type of the value (gid_t, uid_t, etc.) */
                    _("Blanks in header where numeric %s value expected"),
                    type));
          return -1;
@@ -536,47 +653,135 @@ from_chars (char const *where0, size_t digs, char const *type,
   value = 0;
   if (ISODIGIT (*where))
     {
-      do
+      char const *where1 = where;
+      uintmax_t overflow = 0;
+
+      for (;;)
+       {
+         value += *where++ - '0';
+         if (where == lim || ! ISODIGIT (*where))
+           break;
+         overflow |= value ^ (value << LG_8 >> LG_8);
+         value <<= LG_8;
+       }
+
+      /* Parse the output of older, unportable tars, which generate
+         negative values in two's complement octal.  If the leading
+         nonzero digit is 1, we can't recover the original value
+         reliably; so do this only if the digit is 2 or more.  This
+         catches the common case of 32-bit negative time stamps.  */
+      if ((overflow || maxval < value) && '2' <= *where1 && type)
        {
-         if (value << LG_8 >> LG_8 != value)
+         /* Compute the negative of the input value, assuming two's
+            complement.  */
+         int digit = (*where1 - '0') | 4;
+         overflow = 0;
+         value = 0;
+         where = where1;
+         for (;;)
            {
-             ERROR ((0, 0,
-                     _("Archive octal string `%.*s' is out of %s range"),
-                     (int) digs, where0, type));
-             return -1;
+             value += 7 - digit;
+             where++;
+             if (where == lim || ! ISODIGIT (*where))
+               break;
+             digit = *where - '0';
+             overflow |= value ^ (value << LG_8 >> LG_8);
+             value <<= LG_8;
+           }
+         value++;
+         overflow |= !value;
+
+         if (!overflow && value <= minus_minval)
+           {
+             if (!silent)
+               WARN ((0, 0,
+                      /* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
+                      _("Archive octal value %.*s is out of %s range; assuming two's complement"),
+                      (int) (where - where1), where1, type));
+             negative = 1;
            }
-         value = (value << LG_8) | (*where++ - '0');
        }
-      while (where != lim && ISODIGIT (*where));
 
-      /* Parse the output of older tars, which output negative values
-        in two's complement octal.  This method works only if the
-        type has the same number of bits as it did on the host that
-        created the tar file, but that's the best we can do.  */
-      if (maxval < value && value - maxval <= minus_minval)
+      if (overflow)
        {
-         value = minus_minval - (value - maxval);
-         negative = 1;
+         if (type && !silent)
+           ERROR ((0, 0,
+                   /* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
+                   _("Archive octal value %.*s is out of %s range"),
+                   (int) (where - where1), where1, type));
+         return -1;
        }
     }
+  else if (octal_only)
+    {
+      /* Suppress the following extensions.  */
+    }
   else if (*where == '-' || *where == '+')
     {
+      /* Parse base-64 output produced only by tar test versions
+        1.13.6 (1999-08-11) through 1.13.11 (1999-08-23).
+        Support for this will be withdrawn in future releases.  */
       int dig;
+      if (!silent)
+       {
+         static bool warned_once;
+         if (! warned_once)
+           {
+             warned_once = true;
+             WARN ((0, 0, _("Archive contains obsolescent base-64 headers")));
+           }
+       }
       negative = *where++ == '-';
       while (where != lim
             && (dig = base64_map[(unsigned char) *where]) < 64)
        {
          if (value << LG_64 >> LG_64 != value)
            {
-             ERROR ((0, 0,
-                     _("Archive signed base 64 string `%.*s' is out of %s range"),
-                     (int) digs, where0, type));
+             char *string = alloca (digs + 1);
+             memcpy (string, where0, digs);
+             string[digs] = '\0';
+             if (type && !silent)
+               ERROR ((0, 0,
+                       _("Archive signed base-64 string %s is out of %s range"),
+                       quote (string), type));
              return -1;
            }
          value = (value << LG_64) | dig;
          where++;
        }
     }
+  else if (*where == '\200' /* positive base-256 */
+          || *where == '\377' /* negative base-256 */)
+    {
+      /* Parse base-256 output.  A nonnegative number N is
+        represented as (256**DIGS)/2 + N; a negative number -N is
+        represented as (256**DIGS) - N, i.e. as two's complement.
+        The representation guarantees that the leading bit is
+        always on, so that we don't confuse this format with the
+        others (assuming ASCII bytes of 8 bits or more).  */
+      int signbit = *where & (1 << (LG_256 - 2));
+      uintmax_t topbits = (((uintmax_t) - signbit)
+                          << (CHAR_BIT * sizeof (uintmax_t)
+                              - LG_256 - (LG_256 - 2)));
+      value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+      for (;;)
+       {
+         value = (value << LG_256) + (unsigned char) *where++;
+         if (where == lim)
+           break;
+         if (((value << LG_256 >> LG_256) | topbits) != value)
+           {
+             if (type && !silent)
+               ERROR ((0, 0,
+                       _("Archive base-256 value is out of %s range"),
+                       type));
+             return -1;
+           }
+       }
+      negative = signbit;
+      if (negative)
+       value = -value;
+    }
 
   if (where != lim && *where && !ISSPACE ((unsigned char) *where))
     {
@@ -588,15 +793,17 @@ from_chars (char const *where0, size_t digs, char const *type,
          if (!o)
            {
              o = clone_quoting_options (0);
-             set_quoting_style (o, c_quoting_style);
+             set_quoting_style (o, locale_quoting_style);
            }
 
          while (where0 != lim && ! lim[-1])
            lim--;
          quotearg_buffer (buf, sizeof buf, where0, lim - where, o);
-         ERROR ((0, 0,
-                 _("Archive contains `%.*s' where numeric %s value expected"),
-                 (int) sizeof buf, buf, type));
+         if (!silent)
+           ERROR ((0, 0,
+                   /* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
+                   _("Archive contains %.*s where numeric %s value expected"),
+                   (int) sizeof buf, buf, type));
        }
 
       return -1;
@@ -605,7 +812,7 @@ from_chars (char const *where0, size_t digs, char const *type,
   if (value <= (negative ? minus_minval : maxval))
     return negative ? -value : value;
 
-  if (type)
+  if (type && !silent)
     {
       char minval_buf[UINTMAX_STRSIZE_BOUND + 1];
       char maxval_buf[UINTMAX_STRSIZE_BOUND];
@@ -616,6 +823,7 @@ from_chars (char const *where0, size_t digs, char const *type,
        *--value_string = '-';
       if (minus_minval)
        *--minval_string = '-';
+      /* TRANSLATORS: Second %s is type name (gid_t,uid_t,etc.) */
       ERROR ((0, 0, _("Archive value %s is out of %s range %s..%s"),
              value_string, type,
              minval_string, STRINGIFY_BIGINT (maxval, maxval_buf)));
@@ -625,36 +833,37 @@ from_chars (char const *where0, size_t digs, char const *type,
 }
 
 gid_t
-gid_from_chars (const char *p, size_t s)
+gid_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "gid_t",
-                    - (uintmax_t) TYPE_MINIMUM (gid_t),
-                    (uintmax_t) TYPE_MAXIMUM (gid_t));
+  return from_header (p, s, "gid_t",
+                     - (uintmax_t) TYPE_MINIMUM (gid_t),
+                     (uintmax_t) TYPE_MAXIMUM (gid_t),
+                     false, false);
 }
 
 major_t
-major_from_chars (const char *p, size_t s)
+major_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "major_t",
-                    - (uintmax_t) TYPE_MINIMUM (major_t),
-                    (uintmax_t) TYPE_MAXIMUM (major_t));
+  return from_header (p, s, "major_t",
+                     - (uintmax_t) TYPE_MINIMUM (major_t),
+                     (uintmax_t) TYPE_MAXIMUM (major_t), false, false);
 }
 
 minor_t
-minor_from_chars (const char *p, size_t s)
+minor_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "minor_t",
-                    - (uintmax_t) TYPE_MINIMUM (minor_t),
-                    (uintmax_t) TYPE_MAXIMUM (minor_t));
+  return from_header (p, s, "minor_t",
+                     - (uintmax_t) TYPE_MINIMUM (minor_t),
+                     (uintmax_t) TYPE_MAXIMUM (minor_t), false, false);
 }
 
 mode_t
-mode_from_chars (const char *p, size_t s)
+mode_from_header (const char *p, size_t s)
 {
   /* Do not complain about unrecognized mode bits.  */
-  unsigned u = from_chars (p, s, "mode_t",
-                          - (uintmax_t) TYPE_MINIMUM (mode_t),
-                          TYPE_MAXIMUM (uintmax_t));
+  unsigned u = from_header (p, s, "mode_t",
+                           - (uintmax_t) TYPE_MINIMUM (mode_t),
+                           TYPE_MAXIMUM (uintmax_t), false, false);
   return ((u & TSUID ? S_ISUID : 0)
          | (u & TSGID ? S_ISGID : 0)
          | (u & TSVTX ? S_ISVTX : 0)
@@ -670,49 +879,47 @@ mode_from_chars (const char *p, size_t s)
 }
 
 off_t
-off_from_chars (const char *p, size_t s)
+off_from_header (const char *p, size_t s)
 {
   /* Negative offsets are not allowed in tar files, so invoke
-     from_chars with minimum value 0, not TYPE_MINIMUM (off_t).  */
-  return from_chars (p, s, "off_t", (uintmax_t) 0,
-                    (uintmax_t) TYPE_MAXIMUM (off_t));
+     from_header with minimum value 0, not TYPE_MINIMUM (off_t).  */
+  return from_header (p, s, "off_t", (uintmax_t) 0,
+                     (uintmax_t) TYPE_MAXIMUM (off_t), false, false);
 }
 
 size_t
-size_from_chars (const char *p, size_t s)
+size_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "size_t", (uintmax_t) 0, 
-                    (uintmax_t) TYPE_MAXIMUM (size_t));
+  return from_header (p, s, "size_t", (uintmax_t) 0,
+                     (uintmax_t) TYPE_MAXIMUM (size_t), false, false);
 }
 
 time_t
-time_from_chars (const char *p, size_t s)
+time_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "time_t",
-                    - (uintmax_t) TYPE_MINIMUM (time_t),
-                    (uintmax_t) TYPE_MAXIMUM (time_t));
+  return from_header (p, s, "time_t",
+                     - (uintmax_t) TYPE_MINIMUM (time_t),
+                     (uintmax_t) TYPE_MAXIMUM (time_t), false, false);
 }
 
 uid_t
-uid_from_chars (const char *p, size_t s)
+uid_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "uid_t",
-                    - (uintmax_t) TYPE_MINIMUM (uid_t),
-                    (uintmax_t) TYPE_MAXIMUM (uid_t));
+  return from_header (p, s, "uid_t",
+                     - (uintmax_t) TYPE_MINIMUM (uid_t),
+                     (uintmax_t) TYPE_MAXIMUM (uid_t), false, false);
 }
 
 uintmax_t
-uintmax_from_chars (const char *p, size_t s)
+uintmax_from_header (const char *p, size_t s)
 {
-  return from_chars (p, s, "uintmax_t", (uintmax_t) 0,
-                    TYPE_MAXIMUM (uintmax_t));
+  return from_header (p, s, "uintmax_t", (uintmax_t) 0,
+                     TYPE_MAXIMUM (uintmax_t), false, false);
 }
 
 
-/*----------------------------------------------------------------------.
-| Format O as a null-terminated decimal string into BUF _backwards_;   |
-| return pointer to start of result.                                   |
-`----------------------------------------------------------------------*/
+/* Format O as a null-terminated decimal string into BUF _backwards_;
+   return pointer to start of result.  */
 char *
 stringify_uintmax_t_backwards (uintmax_t o, char *buf)
 {
@@ -723,85 +930,64 @@ stringify_uintmax_t_backwards (uintmax_t o, char *buf)
   return buf;
 }
 
-#if !USE_OLD_CTIME
-
-/*-------------------------------------------.
-| Return the time formatted along ISO 8601.  |
-`-------------------------------------------*/
-
-/* Also, see http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html.  */
-
-static char const *
-isotime (time_t time)
+/* 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.  */
+char const *
+tartime (time_t t)
 {
   static char buffer[max (UINTMAX_STRSIZE_BOUND + 1,
                          INT_STRLEN_BOUND (int) + 16)];
-  struct tm *tm = localtime (&time);
+  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 (tm)
     {
-      sprintf (buffer, "%04d-%02d-%02d %02d:%02d:%02d",
-              tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+      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);
       return buffer;
     }
-  else
-    {
-      /* 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.  */
-      uintmax_t abstime = time < 0 ? - (uintmax_t) time : time;
-      char *p = stringify_uintmax_t_backwards (abstime,
-                                              buffer + sizeof buffer);
-      if (time < 0)
-       *--p = '-';
-      while (buffer + sizeof buffer - 19 - 1 < p)
-       *--p = ' ';
-      return p;
-    }
-}
+#endif
 
-#endif /* not USE_OLD_CTIME */
+  /* 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)
+    *--p = '-';
+  while (buffer + sizeof buffer - 19 - 1 < p)
+    *--p = ' ';
+  return p;
+}
 
-/*-------------------------------------------------------------------------.
-| Decode MODE from its binary form in a stat structure, and encode it into |
-| a 9 characters string STRING, terminated with a NUL.                     |
-`-------------------------------------------------------------------------*/
+/* Actually print it.
 
-static void
-decode_mode (mode_t mode, char *string)
-{
-  *string++ = mode & S_IRUSR ? 'r' : '-';
-  *string++ = mode & S_IWUSR ? 'w' : '-';
-  *string++ = (mode & S_ISUID
-              ? (mode & S_IXUSR ? 's' : 'S')
-              : (mode & S_IXUSR ? 'x' : '-'));
-  *string++ = mode & S_IRGRP ? 'r' : '-';
-  *string++ = mode & S_IWGRP ? 'w' : '-';
-  *string++ = (mode & S_ISGID
-              ? (mode & S_IXGRP ? 's' : 'S')
-              : (mode & S_IXGRP ? 'x' : '-'));
-  *string++ = mode & S_IROTH ? 'r' : '-';
-  *string++ = mode & S_IWOTH ? 'w' : '-';
-  *string++ = (mode & S_ISVTX
-              ? (mode & S_IXOTH ? 't' : 'T')
-              : (mode & S_IXOTH ? 'x' : '-'));
-  *string = '\0';
-}
+   Plain and fancy file header block logging.  Non-verbose just prints
+   the name, e.g. for "tar t" or "tar x".  This should just contain
+   file names, so it can be fed back into tar with xargs or the "-T"
+   option.  The verbose option can give a bunch of info, one line per
+   file.  I doubt anybody tries to parse its format, or if they do,
+   they shouldn't.  Unix tar is pretty random here anyway.  */
 
-/*-------------------------------------------------------------------------.
-| Actually print it.                                                      |
-|                                                                         |
-| Plain and fancy file header block logging.  Non-verbose just prints the  |
-| name, e.g. for "tar t" or "tar x".  This should just contain file names, |
-| so it can be fed back into tar with xargs or the "-T" option.  The      |
-| verbose option can give a bunch of info, one line per file.  I doubt    |
-| anybody tries to parse its format, or if they do, they shouldn't.  Unix  |
-| tar is pretty random here anyway.                                       |
-`-------------------------------------------------------------------------*/
 
 /* FIXME: Note that print_header uses the globals HEAD, HSTAT, and
-   HEAD_STANDARD, which must be set up in advance.  Not very clean...  */
+   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.  */
@@ -816,40 +1002,35 @@ static int ugswidth = UGSWIDTH;  /* maximum width encountered so far */
 #endif
 
 void
-print_header (void)
+print_header (struct tar_stat_info *st, off_t block_ordinal)
 {
   char modes[11];
   char const *time_stamp;
+  char *temp_name = st->orig_file_name ? st->orig_file_name : st->file_name;
+
   /* These hold formatted ints.  */
   char uform[UINTMAX_STRSIZE_BOUND], gform[UINTMAX_STRSIZE_BOUND];
   char *user, *group;
   char size[2 * UINTMAX_STRSIZE_BOUND];
                                /* holds formatted size or major,minor */
   char uintbuf[UINTMAX_STRSIZE_BOUND];
-  time_t longie;               /* to make ctime() call portable */
   int pad;
-  char *name;
 
   if (block_number_option)
     {
       char buf[UINTMAX_STRSIZE_BOUND];
+      if (block_ordinal < 0)
+       block_ordinal = current_block_ordinal ();
+      block_ordinal -= recent_long_name_blocks;
+      block_ordinal -= recent_long_link_blocks;
       fprintf (stdlis, _("block %s: "),
-              STRINGIFY_BIGINT (current_block_ordinal (), buf));
+              STRINGIFY_BIGINT (block_ordinal, buf));
     }
 
   if (verbose_option <= 1)
     {
       /* Just the fax, mam.  */
-
-      char *quoted_name = quote_copy_string (current_file_name);
-
-      if (quoted_name)
-       {
-         fprintf (stdlis, "%s\n", quoted_name);
-         free (quoted_name);
-       }
-      else
-       fprintf (stdlis, "%s\n", current_file_name);
+      fprintf (stdlis, "%s\n", quotearg (temp_name));
     }
   else
     {
@@ -872,17 +1053,20 @@ print_header (void)
 
        case GNUTYPE_LONGNAME:
        case GNUTYPE_LONGLINK:
-         ERROR ((0, 0, _("Visible longname error")));
+         modes[0] = 'L';
+         ERROR ((0, 0, _("Unexpected long name header")));
          break;
 
        case GNUTYPE_SPARSE:
        case REGTYPE:
        case AREGTYPE:
-       case LNKTYPE:
          modes[0] = '-';
-         if (current_file_name[strlen (current_file_name) - 1] == '/')
+         if (temp_name[strlen (temp_name) - 1] == '/')
            modes[0] = 'd';
          break;
+       case LNKTYPE:
+         modes[0] = 'h';
+         break;
        case GNUTYPE_DUMPDIR:
          modes[0] = 'd';
          break;
@@ -906,45 +1090,63 @@ print_header (void)
          break;
        }
 
-      decode_mode (current_stat.st_mode, modes + 1);
+      decode_mode (st->stat.st_mode, modes + 1);
 
       /* Time stamp.  */
 
-      longie = current_stat.st_mtime;
-#if USE_OLD_CTIME
-      {
-       char *ct = ctime (&longie);
-       if (ct)
-         {
-           time_stamp = ct + 4;
-           for (ct += 16; ct[4] != '\n'; ct++)
-             ct[0] = ct[4];
-           ct[0] = '\0';
-         }
-       else
-         time_stamp = "??? ?? ??:?? ????";
-      }
-#else
-      time_stamp = isotime (longie);
-#endif
+      time_stamp = tartime (st->stat.st_mtime);
 
       /* User and group names.  */
 
-      if (*current_header->header.uname && current_format != V7_FORMAT
+      if (st->uname
+         && st->uname[0]
+         && current_format != V7_FORMAT
          && !numeric_owner_option)
-       user = current_header->header.uname;
+       user = st->uname;
       else
-       user = STRINGIFY_BIGINT (UINTMAX_FROM_CHARS
-                                (current_header->header.uid),
-                                uform);
+       {
+         /* Try parsing it as an unsigned integer first, and as a
+            uid_t if that fails.  This method can list positive user
+            ids that are too large to fit in a uid_t.  */
+         uintmax_t u = from_header (current_header->header.uid,
+                                    sizeof current_header->header.uid, 0,
+                                    (uintmax_t) 0,
+                                    (uintmax_t) TYPE_MAXIMUM (uintmax_t),
+                                    false, false);
+         if (u != -1)
+           user = STRINGIFY_BIGINT (u, uform);
+         else
+           {
+             sprintf (uform, "%ld",
+                      (long) UID_FROM_HEADER (current_header->header.uid));
+             user = uform;
+           }
+       }
 
-      if (*current_header->header.gname && current_format != V7_FORMAT
+      if (st->gname
+         && st->gname[0]
+         && current_format != V7_FORMAT
          && !numeric_owner_option)
-       group = current_header->header.gname;
+       group = st->gname;
       else
-       group = STRINGIFY_BIGINT (UINTMAX_FROM_CHARS
-                                 (current_header->header.gid),
-                                 gform);
+       {
+         /* Try parsing it as an unsigned integer first, and as a
+            gid_t if that fails.  This method can list positive group
+            ids that are too large to fit in a gid_t.  */
+         uintmax_t g = from_header (current_header->header.gid,
+                                    sizeof current_header->header.gid, 0,
+                                    (uintmax_t) 0,
+                                    (uintmax_t) TYPE_MAXIMUM (uintmax_t),
+                                    false, false);
+         if (g != -1)
+           group = STRINGIFY_BIGINT (g, gform);
+         else
+           {
+             sprintf (gform, "%ld",
+                      (long) GID_FROM_HEADER (current_header->header.gid));
+             group = gform;
+           }
+       }
 
       /* Format the file size or major/minor device numbers.  */
 
@@ -953,19 +1155,15 @@ print_header (void)
        case CHRTYPE:
        case BLKTYPE:
          strcpy (size,
-                 STRINGIFY_BIGINT (major (current_stat.st_rdev), uintbuf));
+                 STRINGIFY_BIGINT (major (st->stat.st_rdev), uintbuf));
          strcat (size, ",");
          strcat (size,
-                 STRINGIFY_BIGINT (minor (current_stat.st_rdev), uintbuf));
-         break;
-       case GNUTYPE_SPARSE:
-         strcpy (size,
-                 STRINGIFY_BIGINT
-                 (UINTMAX_FROM_CHARS (current_header->oldgnu_header.realsize),
-                  uintbuf));
+                 STRINGIFY_BIGINT (minor (st->stat.st_rdev), uintbuf));
          break;
+
        default:
-         strcpy (size, STRINGIFY_BIGINT (current_stat.st_size, uintbuf));
+         /* st->stat.st_size keeps stored file size */
+         strcpy (size, STRINGIFY_BIGINT (st->stat.st_size, uintbuf));
          break;
        }
 
@@ -978,42 +1176,26 @@ print_header (void)
       fprintf (stdlis, "%s %s/%s %*s%s %s",
               modes, user, group, ugswidth - pad, "", size, time_stamp);
 
-      name = quote_copy_string (current_file_name);
-      if (name)
-       {
-         fprintf (stdlis, " %s", name);
-         free (name);
-       }
-      else
-       fprintf (stdlis, " %s", current_file_name);
+      fprintf (stdlis, " %s", quotearg (temp_name));
 
       switch (current_header->header.typeflag)
        {
        case SYMTYPE:
-         name = quote_copy_string (current_link_name);
-         if (name)
-           {
-             fprintf (stdlis, " -> %s\n", name);
-             free (name);
-           }
-         else
-           fprintf (stdlis, " -> %s\n", current_link_name);
+         fprintf (stdlis, " -> %s\n", quotearg (st->link_name));
          break;
 
        case LNKTYPE:
-         name = quote_copy_string (current_link_name);
-         if (name)
-           {
-             fprintf (stdlis, _(" link to %s\n"), name);
-             free (name);
-           }
-         else
-           fprintf (stdlis, _(" link to %s\n"), current_link_name);
+         fprintf (stdlis, _(" link to %s\n"), quotearg (st->link_name));
          break;
 
        default:
-         fprintf (stdlis, _(" unknown file type `%c'\n"),
-                  current_header->header.typeflag);
+         {
+           char type_string[2];
+           type_string[0] = current_header->header.typeflag;
+           type_string[1] = '\0';
+           fprintf (stdlis, _(" unknown file type %s\n"),
+                    quote (type_string));
+         }
          break;
 
        case AREGTYPE:
@@ -1028,6 +1210,14 @@ print_header (void)
          putc ('\n', stdlis);
          break;
 
+       case GNUTYPE_LONGLINK:
+         fprintf (stdlis, _("--Long Link--\n"));
+         break;
+
+       case GNUTYPE_LONGNAME:
+         fprintf (stdlis, _("--Long Name--\n"));
+         break;
+
        case GNUTYPE_VOLHDR:
          fprintf (stdlis, _("--Volume Header--\n"));
          break;
@@ -1035,7 +1225,7 @@ print_header (void)
        case GNUTYPE_MULTIVOL:
          strcpy (size,
                  STRINGIFY_BIGINT
-                 (UINTMAX_FROM_CHARS (current_header->oldgnu_header.offset),
+                 (UINTMAX_FROM_HEADER (current_header->oldgnu_header.offset),
                   uintbuf));
          fprintf (stdlis, _("--Continued at byte %s--\n"), size);
          break;
@@ -1048,15 +1238,11 @@ print_header (void)
   fflush (stdlis);
 }
 
-/*--------------------------------------------------------------.
-| Print a similar line when we make a directory automatically.  |
-`--------------------------------------------------------------*/
-
+/* Print a similar line when we make a directory automatically.  */
 void
-print_for_mkdir (char *pathname, int length, mode_t mode)
+print_for_mkdir (char *dirname, int length, mode_t mode)
 {
   char modes[11];
-  char *name;
 
   if (verbose_option > 1)
     {
@@ -1071,23 +1257,13 @@ print_for_mkdir (char *pathname, int length, mode_t mode)
          fprintf (stdlis, _("block %s: "),
                   STRINGIFY_BIGINT (current_block_ordinal (), buf));
        }
-      name = quote_copy_string (pathname);
-      if (name)
-       {
-         fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH,
-                  _("Creating directory:"), length, name);
-         free (name);
-       }
-      else
-       fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH,
-                _("Creating directory:"), length, pathname);
+
+      fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH,
+              _("Creating directory:"), length, quotearg (dirname));
     }
 }
 
-/*--------------------------------------------------------.
-| Skip over SIZE bytes of data in blocks in the archive.  |
-`--------------------------------------------------------*/
-
+/* Skip over SIZE bytes of data in blocks in the archive.  */
 void
 skip_file (off_t size)
 {
@@ -1099,11 +1275,24 @@ skip_file (off_t size)
       save_sizeleft = size;
     }
 
+  if (seekable_archive)
+    {
+      off_t nblk = seek_archive (size);
+      if (nblk >= 0)
+       {
+         size -= nblk * BLOCKSIZE;
+         if (multi_volume_option) /* Argh.. */
+           save_sizeleft -= nblk * BLOCKSIZE;
+       }
+      else
+       seekable_archive = false;
+    }
+
   while (size > 0)
     {
       x = find_next_block ();
       if (! x)
-       FATAL_ERROR ((0, 0, _("Unexpected EOF on archive file")));
+       FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
 
       set_next_block_after (x);
       size -= BLOCKSIZE;
@@ -1112,21 +1301,18 @@ skip_file (off_t size)
     }
 }
 
-/*---.
-| ?  |
-`---*/
-
+/* Skip the current member in the archive.
+   NOTE: Current header must be decoded before calling this function. */
 void
-skip_extended_headers (void)
+skip_member (void)
 {
-  union block *exhdr;
+  char save_typeflag = current_header->header.typeflag;
+  set_next_block_after (current_header);
 
-  do
-    {
-      exhdr = find_next_block ();
-      if (!exhdr)
-       FATAL_ERROR ((0, 0, _("Unexpected EOF on archive file")));
-      set_next_block_after (exhdr);
-    }
-  while (exhdr->sparse_header.isextended);
+  assign_string (&save_name, current_stat_info.orig_file_name);
+
+  if (current_stat_info.is_sparse)
+    sparse_skip_file (&current_stat_info);
+  else if (save_typeflag != DIRTYPE)
+    skip_file (current_stat_info.stat.st_size);
 }
This page took 0.059476 seconds and 4 git commands to generate.