Sergey Poznyakoff пре 22 година
родитељ
комит
d858a433c8
6 измењених фајлова са 914 додато и 1163 уклоњено
  1. 4 8
      src/buffer.c
  2. 214 361
      src/compare.c
  3. 77 77
      src/create.c
  4. 210 184
      src/delete.c
  5. 65 60
      src/extract.c
  6. 344 473
      src/incremen.c

+ 4 - 8
src/buffer.c

@@ -669,8 +669,7 @@ open_archive (enum access_mode wanted_access)
   if (archive_names == 0)
   if (archive_names == 0)
     FATAL_ERROR ((0, 0, _("No archive name given")));
     FATAL_ERROR ((0, 0, _("No archive name given")));
 
 
-  current_file_name = 0;
-  current_link_name = 0;
+  destroy_stat (&current_stat_info);
   save_name = 0;
   save_name = 0;
   real_s_name = 0;
   real_s_name = 0;
 
 
@@ -845,8 +844,8 @@ open_archive (enum access_mode wanted_access)
 	  else
 	  else
 	    strcpy (record_start->header.name, volume_label_option);
 	    strcpy (record_start->header.name, volume_label_option);
 
 
-	  assign_string (&current_file_name, record_start->header.name);
-	  current_trailing_slash = strip_trailing_slashes (current_file_name);
+	  assign_string (&current_stat_info.file_name, record_start->header.name);
+	  current_stat_info.had_trailing_slash = strip_trailing_slashes (current_stat_info.file_name);
 
 
 	  record_start->header.typeflag = GNUTYPE_VOLHDR;
 	  record_start->header.typeflag = GNUTYPE_VOLHDR;
 	  TIME_TO_CHARS (start_time, record_start->header.mtime);
 	  TIME_TO_CHARS (start_time, record_start->header.mtime);
@@ -1362,10 +1361,7 @@ close_archive (void)
     }
     }
 #endif /* !MSDOS */
 #endif /* !MSDOS */
 
 
-  if (current_file_name)
-    free (current_file_name);
-  if (current_link_name)
-    free (current_link_name);
+  destroy_stat (&current_stat_info);
   if (save_name)
   if (save_name)
     free (save_name);
     free (save_name);
   if (real_s_name)
   if (real_s_name)

+ 214 - 361
src/compare.c

@@ -1,5 +1,8 @@
 /* Diff files from a tar archive.
 /* Diff files from a tar archive.
-   Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc.
+
+   Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+   2003 Free Software Foundation, Inc.
+
    Written by John Gilmore, on 1987-04-30.
    Written by John Gilmore, on 1987-04-30.
 
 
    This program is free software; you can redistribute it and/or modify it
    This program is free software; you can redistribute it and/or modify it
@@ -14,14 +17,26 @@
 
 
    You should have received a copy of the GNU General Public License along
    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    with this program; if not, write to the Free Software Foundation, Inc.,
-   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 
 #include "system.h"
 #include "system.h"
 
 
+#if HAVE_UTIME_H
+# include <utime.h>
+#else
+struct utimbuf
+  {
+    long actime;
+    long modtime;
+  };
+#endif
+
 #if HAVE_LINUX_FD_H
 #if HAVE_LINUX_FD_H
 # include <linux/fd.h>
 # include <linux/fd.h>
 #endif
 #endif
 
 
+#include <quotearg.h>
+
 #include "common.h"
 #include "common.h"
 #include "rmt.h"
 #include "rmt.h"
 
 
@@ -29,105 +44,85 @@
 #define MESSAGE_BUFFER_SIZE 100
 #define MESSAGE_BUFFER_SIZE 100
 
 
 /* Nonzero if we are verifying at the moment.  */
 /* Nonzero if we are verifying at the moment.  */
-int now_verifying = 0;
+bool now_verifying;
 
 
 /* File descriptor for the file we are diffing.  */
 /* File descriptor for the file we are diffing.  */
 static int diff_handle;
 static int diff_handle;
 
 
 /* Area for reading file contents into.  */
 /* Area for reading file contents into.  */
-static char *diff_buffer = NULL;
-
-/*--------------------------------.
-| Initialize for a diff operation |
-`--------------------------------*/
+static char *diff_buffer;
 
 
+/* Initialize for a diff operation.  */
 void
 void
 diff_init (void)
 diff_init (void)
 {
 {
-  diff_buffer = (char *) valloc ((unsigned) record_size);
+  diff_buffer = valloc (record_size);
   if (!diff_buffer)
   if (!diff_buffer)
-    FATAL_ERROR ((0, 0,
-		  _("Could not allocate memory for diff buffer of %d bytes"),
-		  record_size));
+    xalloc_die ();
 }
 }
 
 
-/*------------------------------------------------------------------------.
-| Sigh about something that differs by writing a MESSAGE to stdlis, given |
-| MESSAGE is not NULL.  Also set the exit status if not already.          |
-`------------------------------------------------------------------------*/
-
+/* Sigh about something that differs by writing a MESSAGE to stdlis,
+   given MESSAGE is nonzero.  Also set the exit status if not already.  */
 static void
 static void
 report_difference (const char *message)
 report_difference (const char *message)
 {
 {
   if (message)
   if (message)
-    fprintf (stdlis, "%s: %s\n", current_file_name, message);
+    fprintf (stdlis, "%s: %s\n", quotearg_colon (current_stat_info.file_name), message);
 
 
   if (exit_status == TAREXIT_SUCCESS)
   if (exit_status == TAREXIT_SUCCESS)
     exit_status = TAREXIT_DIFFERS;
     exit_status = TAREXIT_DIFFERS;
 }
 }
 
 
-/*-----------------------------------------------------------------------.
-| Takes a buffer returned by read_and_process and does nothing with it.	 |
-`-----------------------------------------------------------------------*/
-
-/* Yes, I know.  SIZE and DATA are unused in this function.  Some compilers
-   may even report it.  That's OK, just relax!  */
-
+/* Take a buffer returned by read_and_process and do nothing with it.  */
 static int
 static int
-process_noop (long size, char *data)
+process_noop (size_t size, char *data)
 {
 {
+  /* Yes, I know.  SIZE and DATA are unused in this function.  Some
+     compilers may even report it.  That's OK, just relax!  */
   return 1;
   return 1;
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
-
 static int
 static int
-process_rawdata (long bytes, char *buffer)
+process_rawdata (size_t bytes, char *buffer)
 {
 {
-  int status = read (diff_handle, diff_buffer, (size_t) bytes);
+  ssize_t status = safe_read (diff_handle, diff_buffer, bytes);
   char message[MESSAGE_BUFFER_SIZE];
   char message[MESSAGE_BUFFER_SIZE];
 
 
   if (status != bytes)
   if (status != bytes)
     {
     {
       if (status < 0)
       if (status < 0)
 	{
 	{
-	  WARN ((0, errno, _("Cannot read %s"), current_file_name));
-	  report_difference (NULL);
+	  read_error (current_stat_info.file_name);
+	  report_difference (0);
 	}
 	}
       else
       else
 	{
 	{
-	  sprintf (message, _("Could only read %d of %ld bytes"),
-		   status, bytes);
+	  sprintf (message, _("Could only read %lu of %lu bytes"),
+		   (unsigned long) status, (unsigned long) bytes);
 	  report_difference (message);
 	  report_difference (message);
 	}
 	}
       return 0;
       return 0;
     }
     }
 
 
-  if (memcmp (buffer, diff_buffer, (size_t) bytes))
+  if (memcmp (buffer, diff_buffer, bytes))
     {
     {
-      report_difference (_("Data differs"));
+      report_difference (_("Contents differ"));
       return 0;
       return 0;
     }
     }
 
 
   return 1;
   return 1;
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
-
 /* Directory contents, only for GNUTYPE_DUMPDIR.  */
 /* Directory contents, only for GNUTYPE_DUMPDIR.  */
 
 
 static char *dumpdir_cursor;
 static char *dumpdir_cursor;
 
 
 static int
 static int
-process_dumpdir (long bytes, char *buffer)
+process_dumpdir (size_t bytes, char *buffer)
 {
 {
-  if (memcmp (buffer, dumpdir_cursor, (size_t) bytes))
+  if (memcmp (buffer, dumpdir_cursor, bytes))
     {
     {
-      report_difference (_("Data differs"));
+      report_difference (_("Contents differ"));
       return 0;
       return 0;
     }
     }
 
 
@@ -135,28 +130,25 @@ process_dumpdir (long bytes, char *buffer)
   return 1;
   return 1;
 }
 }
 
 
-/*------------------------------------------------------------------------.
-| Some other routine wants SIZE bytes in the archive.  For each chunk of  |
-| the archive, call PROCESSOR with the size of the chunk, and the address |
-| of the chunk it can work with.  The PROCESSOR should return nonzero for |
-| success.  It it return error once, continue skipping without calling    |
-| PROCESSOR anymore.                                                      |
-`------------------------------------------------------------------------*/
-
+/* Some other routine wants SIZE bytes in the archive.  For each chunk
+   of the archive, call PROCESSOR with the size of the chunk, and the
+   address of the chunk it can work with.  The PROCESSOR should return
+   nonzero for success.  It it return error once, continue skipping
+   without calling PROCESSOR anymore.  */
 static void
 static void
-read_and_process (long size, int (*processor) (long, char *))
+read_and_process (off_t size, int (*processor) (size_t, char *))
 {
 {
   union block *data_block;
   union block *data_block;
-  long data_size;
+  size_t data_size;
 
 
   if (multi_volume_option)
   if (multi_volume_option)
     save_sizeleft = size;
     save_sizeleft = size;
   while (size)
   while (size)
     {
     {
       data_block = find_next_block ();
       data_block = find_next_block ();
-      if (data_block == NULL)
+      if (! data_block)
 	{
 	{
-	  ERROR ((0, 0, _("Unexpected EOF on archive file")));
+	  ERROR ((0, 0, _("Unexpected EOF in archive")));
 	  return;
 	  return;
 	}
 	}
 
 
@@ -173,98 +165,9 @@ read_and_process (long size, int (*processor) (long, char *))
     }
     }
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
-
-/* JK This routine should be used more often than it is ... look into
-   that.  Anyhow, what it does is translate the sparse information on the
-   header, and in any subsequent extended headers, into an array of
-   structures with true numbers, as opposed to character strings.  It
-   simply makes our life much easier, doing so many comparisong and such.
-   */
-
-static void
-fill_in_sparse_array (void)
-{
-  int counter;
-
-  /* Allocate space for our scratch space; it's initially 10 elements
-     long, but can change in this routine if necessary.  */
-
-  sp_array_size = 10;
-  sparsearray = (struct sp_array *) xmalloc (sp_array_size * sizeof (struct sp_array));
-
-  /* There are at most five of these structures in the header itself;
-     read these in first.  */
-
-  for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
-    {
-      /* Compare to 0, or use !(int)..., for Pyramid's dumb compiler.  */
-      if (current_header->oldgnu_header.sp[counter].numbytes == 0)
-	break;
-
-      sparsearray[counter].offset =
-	from_oct (1 + 12, current_header->oldgnu_header.sp[counter].offset);
-      sparsearray[counter].numbytes =
-	from_oct (1 + 12, current_header->oldgnu_header.sp[counter].numbytes);
-    }
-
-  /* If the header's extended, we gotta read in exhdr's till we're done.  */
-
-  if (current_header->oldgnu_header.isextended)
-    {
-      /* How far into the sparsearray we are `so far'.  */
-      static int so_far_ind = SPARSES_IN_OLDGNU_HEADER;
-      union block *exhdr;
-
-      while (1)
-	{
-	  exhdr = find_next_block ();
-	  for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
-	    {
-	      if (counter + so_far_ind > sp_array_size - 1)
-		{
-		  /* We just ran out of room in our scratch area -
-		     realloc it.  */
-
-		  sp_array_size *= 2;
-		  sparsearray = (struct sp_array *)
-		    xrealloc (sparsearray,
-			      sp_array_size * sizeof (struct sp_array));
-		}
-
-	      /* Convert the character strings into longs.  */
-
-	      sparsearray[counter + so_far_ind].offset =
-		from_oct (1 + 12, exhdr->sparse_header.sp[counter].offset);
-	      sparsearray[counter + so_far_ind].numbytes =
-		from_oct (1 + 12, exhdr->sparse_header.sp[counter].numbytes);
-	    }
-
-	  /* If this is the last extended header for this file, we can
-	     stop.  */
-
-	  if (!exhdr->sparse_header.isextended)
-	    break;
-
-	  so_far_ind += SPARSES_IN_SPARSE_HEADER;
-	  set_next_block_after (exhdr);
-	}
-
-      /* Be sure to skip past the last one.  */
-
-      set_next_block_after (exhdr);
-    }
-}
-
-/*---.
-| ?  |
-`---*/
-
 /* JK Diff'ing a sparse file with its counterpart on the tar file is a
 /* JK Diff'ing a sparse file with its counterpart on the tar file is a
    bit of a different story than a normal file.  First, we must know what
    bit of a different story than a normal file.  First, we must know what
-   areas of the file to skip through, i.e., we need to contruct a
+   areas of the file to skip through, i.e., we need to construct a
    sparsearray, which will hold all the information we need.  We must
    sparsearray, which will hold all the information we need.  We must
    compare small amounts of data at a time as we find it.  */
    compare small amounts of data at a time as we find it.  */
 
 
@@ -273,56 +176,68 @@ fill_in_sparse_array (void)
    I'm not sure overall identical sparsity is verified.  */
    I'm not sure overall identical sparsity is verified.  */
 
 
 static void
 static void
-diff_sparse_files (int size_of_file)
+diff_sparse_files (void)
 {
 {
-  int remaining_size = size_of_file;
-  char *buffer = (char *) xmalloc (BLOCKSIZE * sizeof (char));
-  int buffer_size = BLOCKSIZE;
-  union block *data_block = NULL;
+  off_t remaining_size = current_stat_info.stat.st_size;
+  char *buffer = xmalloc (BLOCKSIZE * sizeof (char));
+  size_t buffer_size = BLOCKSIZE;
+  union block *data_block = 0;
   int counter = 0;
   int counter = 0;
   int different = 0;
   int different = 0;
 
 
-  fill_in_sparse_array ();
+  if (! fill_in_sparse_array ())
+    fatal_exit ();
 
 
   while (remaining_size > 0)
   while (remaining_size > 0)
     {
     {
-      int status;
-      long chunk_size;
+      ssize_t status;
+      size_t chunk_size;
+      off_t offset;
+
 #if 0
 #if 0
-      int amount_read = 0;
+      off_t amount_read = 0;
 #endif
 #endif
 
 
       data_block = find_next_block ();
       data_block = find_next_block ();
+      if (!data_block)
+	FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
       chunk_size = sparsearray[counter].numbytes;
       chunk_size = sparsearray[counter].numbytes;
       if (!chunk_size)
       if (!chunk_size)
 	break;
 	break;
 
 
-      lseek (diff_handle, sparsearray[counter].offset, 0);
+      offset = sparsearray[counter].offset;
+      if (lseek (diff_handle, offset, SEEK_SET) < 0)
+	{
+	  seek_error_details (current_stat_info.file_name, offset);
+	  report_difference (0);
+	}
 
 
       /* Take care to not run out of room in our buffer.  */
       /* Take care to not run out of room in our buffer.  */
 
 
       while (buffer_size < chunk_size)
       while (buffer_size < chunk_size)
 	{
 	{
+	  if (buffer_size * 2 < buffer_size)
+	    xalloc_die ();
 	  buffer_size *= 2;
 	  buffer_size *= 2;
-	  buffer = (char *) xrealloc (buffer, buffer_size * sizeof (char));
+	  buffer = xrealloc (buffer, buffer_size * sizeof (char));
 	}
 	}
 
 
       while (chunk_size > BLOCKSIZE)
       while (chunk_size > BLOCKSIZE)
 	{
 	{
-	  if (status = read (diff_handle, buffer, BLOCKSIZE),
+	  if (status = safe_read (diff_handle, buffer, BLOCKSIZE),
 	      status != BLOCKSIZE)
 	      status != BLOCKSIZE)
 	    {
 	    {
 	      if (status < 0)
 	      if (status < 0)
 		{
 		{
-		  WARN ((0, errno, _("Cannot read %s"), current_file_name));
-		  report_difference (NULL);
+		  read_error (current_stat_info.file_name);
+		  report_difference (0);
 		}
 		}
 	      else
 	      else
 		{
 		{
 		  char message[MESSAGE_BUFFER_SIZE];
 		  char message[MESSAGE_BUFFER_SIZE];
 
 
-		  sprintf (message, _("Could only read %d of %ld bytes"),
-			   status, chunk_size);
+		  sprintf (message, _("Could only read %lu of %lu bytes"),
+			   (unsigned long) status, (unsigned long) chunk_size);
 		  report_difference (message);
 		  report_difference (message);
 		}
 		}
 	      break;
 	      break;
@@ -338,27 +253,29 @@ diff_sparse_files (int size_of_file)
 	  remaining_size -= status;
 	  remaining_size -= status;
 	  set_next_block_after (data_block);
 	  set_next_block_after (data_block);
 	  data_block = find_next_block ();
 	  data_block = find_next_block ();
+	  if (!data_block)
+	    FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
 	}
 	}
-      if (status = read (diff_handle, buffer, (size_t) chunk_size),
+      if (status = safe_read (diff_handle, buffer, chunk_size),
 	  status != chunk_size)
 	  status != chunk_size)
 	{
 	{
 	  if (status < 0)
 	  if (status < 0)
 	    {
 	    {
-	      WARN ((0, errno, _("Cannot read %s"), current_file_name));
-	      report_difference (NULL);
+	      read_error (current_stat_info.file_name);
+	      report_difference (0);
 	    }
 	    }
 	  else
 	  else
 	    {
 	    {
 	      char message[MESSAGE_BUFFER_SIZE];
 	      char message[MESSAGE_BUFFER_SIZE];
 
 
-	      sprintf (message, _("Could only read %d of %ld bytes"),
-		       status, chunk_size);
+	      sprintf (message, _("Could only read %lu of %lu bytes"),
+		       (unsigned long) status, (unsigned long) chunk_size);
 	      report_difference (message);
 	      report_difference (message);
 	    }
 	    }
 	  break;
 	  break;
 	}
 	}
 
 
-      if (memcmp (buffer, data_block->buffer, (size_t) chunk_size))
+      if (memcmp (buffer, data_block->buffer, chunk_size))
 	{
 	{
 	  different = 1;
 	  different = 1;
 	  break;
 	  break;
@@ -370,6 +287,8 @@ diff_sparse_files (int size_of_file)
 	  amount_read = 0;
 	  amount_read = 0;
 	  set_next_block_after (data_block);
 	  set_next_block_after (data_block);
 	  data_block = find_next_block ();
 	  data_block = find_next_block ();
+	  if (!data_block)
+	    FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
 	}
 	}
 #endif
 #endif
       set_next_block_after (data_block);
       set_next_block_after (data_block);
@@ -381,7 +300,7 @@ diff_sparse_files (int size_of_file)
   /* If the number of bytes read isn't the number of bytes supposedly in
   /* If the number of bytes read isn't the number of bytes supposedly in
      the file, they're different.  */
      the file, they're different.  */
 
 
-  if (amount_read != size_of_file)
+  if (amount_read != current_stat_info.stat.st_size)
     different = 1;
     different = 1;
 #endif
 #endif
 
 
@@ -389,71 +308,56 @@ diff_sparse_files (int size_of_file)
   free (sparsearray);
   free (sparsearray);
 
 
   if (different)
   if (different)
-    report_difference (_("Data differs"));
+    report_difference (_("Contents differ"));
 }
 }
 
 
-/*---------------------------------------------------------------------.
-| Call either stat or lstat over STAT_DATA, depending on --dereference |
-| (-h), for a file which should exist.  Diagnose any problem.  Return  |
-| nonzero for success, zero otherwise.				       |
-`---------------------------------------------------------------------*/
-
+/* Call either stat or lstat over STAT_DATA, depending on
+   --dereference (-h), for a file which should exist.  Diagnose any
+   problem.  Return nonzero for success, zero otherwise.  */
 static int
 static int
-get_stat_data (struct stat *stat_data)
+get_stat_data (char const *file_name, struct stat *stat_data)
 {
 {
-  int status = (dereference_option
-		? stat (current_file_name, stat_data)
-		: lstat (current_file_name, stat_data));
+  int status = deref_stat (dereference_option, file_name, stat_data);
 
 
-  if (status < 0)
+  if (status != 0)
     {
     {
       if (errno == ENOENT)
       if (errno == ENOENT)
-	report_difference (_("File does not exist"));
+	stat_warn (file_name);
       else
       else
-	{
-	  ERROR ((0, errno, _("Cannot stat file %s"), current_file_name));
-	  report_difference (NULL);
-	}
-#if 0
-      skip_file ((long) current_stat.st_size);
-#endif
+	stat_error (file_name);
+      report_difference (0);
       return 0;
       return 0;
     }
     }
 
 
   return 1;
   return 1;
 }
 }
 
 
-/*----------------------------------.
-| Diff a file against the archive.  |
-`----------------------------------*/
-
+/* Diff a file against the archive.  */
 void
 void
 diff_archive (void)
 diff_archive (void)
 {
 {
   struct stat stat_data;
   struct stat stat_data;
-  int name_length;
   int status;
   int status;
-
-  errno = EPIPE;		/* FIXME: errno should be read-only */
-				/* FIXME: remove perrors */
+  struct utimbuf restore_times;
 
 
   set_next_block_after (current_header);
   set_next_block_after (current_header);
-  decode_header (current_header, &current_stat, &current_format, 1);
+  decode_header (current_header, &current_stat_info, &current_format, 1);
 
 
-  /* Print the block from `current_header' and `current_stat'.  */
+  /* Print the block from current_header and current_stat_info.  */
 
 
   if (verbose_option)
   if (verbose_option)
     {
     {
       if (now_verifying)
       if (now_verifying)
 	fprintf (stdlis, _("Verify "));
 	fprintf (stdlis, _("Verify "));
-      print_header ();
+      print_header (-1);
     }
     }
 
 
   switch (current_header->header.typeflag)
   switch (current_header->header.typeflag)
     {
     {
     default:
     default:
-      WARN ((0, 0, _("Unknown file type '%c' for %s, diffed as normal file"),
-		 current_header->header.typeflag, current_file_name));
+      ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
+	      quotearg_colon (current_stat_info.file_name),
+	      current_header->header.typeflag));
       /* Fall through.  */
       /* Fall through.  */
 
 
     case AREGTYPE:
     case AREGTYPE:
@@ -463,92 +367,83 @@ diff_archive (void)
 
 
       /* Appears to be a file.  See if it's really a directory.  */
       /* Appears to be a file.  See if it's really a directory.  */
 
 
-      name_length = strlen (current_file_name) - 1;
-      if (current_file_name[name_length] == '/')
+      if (current_stat_info.had_trailing_slash)
 	goto really_dir;
 	goto really_dir;
 
 
-      if (!get_stat_data (&stat_data))
+      if (!get_stat_data (current_stat_info.file_name, &stat_data))
 	{
 	{
-	  if (current_header->oldgnu_header.isextended)
-	    skip_extended_headers ();
-	  skip_file ((long) current_stat.st_size);
+	  skip_member ();
 	  goto quit;
 	  goto quit;
 	}
 	}
 
 
       if (!S_ISREG (stat_data.st_mode))
       if (!S_ISREG (stat_data.st_mode))
 	{
 	{
-	  report_difference (_("Not a regular file"));
-	  skip_file ((long) current_stat.st_size);
+	  report_difference (_("File type differs"));
+	  skip_member ();
 	  goto quit;
 	  goto quit;
 	}
 	}
 
 
-      stat_data.st_mode &= 07777;
-      if (stat_data.st_mode != current_stat.st_mode)
+      if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
 	report_difference (_("Mode differs"));
 	report_difference (_("Mode differs"));
 
 
 #if !MSDOS
 #if !MSDOS
       /* stat() in djgpp's C library gives a constant number of 42 as the
       /* stat() in djgpp's C library gives a constant number of 42 as the
 	 uid and gid of a file.  So, comparing an FTP'ed archive just after
 	 uid and gid of a file.  So, comparing an FTP'ed archive just after
 	 unpack would fail on MSDOS.  */
 	 unpack would fail on MSDOS.  */
-      if (stat_data.st_uid != current_stat.st_uid)
+      if (stat_data.st_uid != current_stat_info.stat.st_uid)
 	report_difference (_("Uid differs"));
 	report_difference (_("Uid differs"));
-      if (stat_data.st_gid != current_stat.st_gid)
+      if (stat_data.st_gid != current_stat_info.stat.st_gid)
 	report_difference (_("Gid differs"));
 	report_difference (_("Gid differs"));
 #endif
 #endif
 
 
-      if (stat_data.st_mtime != current_stat.st_mtime)
+      if (stat_data.st_mtime != current_stat_info.stat.st_mtime)
 	report_difference (_("Mod time differs"));
 	report_difference (_("Mod time differs"));
       if (current_header->header.typeflag != GNUTYPE_SPARSE &&
       if (current_header->header.typeflag != GNUTYPE_SPARSE &&
-	  stat_data.st_size != current_stat.st_size)
+	  stat_data.st_size != current_stat_info.stat.st_size)
 	{
 	{
 	  report_difference (_("Size differs"));
 	  report_difference (_("Size differs"));
-	  skip_file ((long) current_stat.st_size);
+	  skip_member ();
 	  goto quit;
 	  goto quit;
 	}
 	}
 
 
-      diff_handle = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
+      diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
 
 
-      if (diff_handle < 0 && !absolute_names_option)
-	{
-	  char *tmpbuf = xmalloc (strlen (current_file_name) + 2);
-
-	  *tmpbuf = '/';
-	  strcpy (tmpbuf + 1, current_file_name);
-	  diff_handle = open (tmpbuf, O_NDELAY | O_RDONLY);
-	  free (tmpbuf);
-	}
       if (diff_handle < 0)
       if (diff_handle < 0)
 	{
 	{
-	  ERROR ((0, errno, _("Cannot open %s"), current_file_name));
-	  if (current_header->oldgnu_header.isextended)
-	    skip_extended_headers ();
-	  skip_file ((long) current_stat.st_size);
-	  report_difference (NULL);
+	  open_error (current_stat_info.file_name);
+	  skip_member ();
+	  report_difference (0);
 	  goto quit;
 	  goto quit;
 	}
 	}
 
 
+      restore_times.actime = stat_data.st_atime;
+      restore_times.modtime = stat_data.st_mtime;
+
       /* Need to treat sparse files completely differently here.  */
       /* Need to treat sparse files completely differently here.  */
 
 
       if (current_header->header.typeflag == GNUTYPE_SPARSE)
       if (current_header->header.typeflag == GNUTYPE_SPARSE)
-	diff_sparse_files (current_stat.st_size);
+	diff_sparse_files ();
       else
       else
 	{
 	{
 	  if (multi_volume_option)
 	  if (multi_volume_option)
 	    {
 	    {
-	      assign_string (&save_name, current_file_name);
-	      save_totsize = current_stat.st_size;
+	      assign_string (&save_name, current_stat_info.file_name);
+	      save_totsize = current_stat_info.stat.st_size;
 	      /* save_sizeleft is set in read_and_process.  */
 	      /* save_sizeleft is set in read_and_process.  */
 	    }
 	    }
 
 
-	  read_and_process ((long) (current_stat.st_size), process_rawdata);
+	  read_and_process (current_stat_info.stat.st_size, process_rawdata);
 
 
 	  if (multi_volume_option)
 	  if (multi_volume_option)
-	    assign_string (&save_name, NULL);
+	    assign_string (&save_name, 0);
 	}
 	}
 
 
       status = close (diff_handle);
       status = close (diff_handle);
-      if (status < 0)
-	ERROR ((0, errno, _("Error while closing %s"), current_file_name));
+      if (status != 0)
+	close_error (current_stat_info.file_name);
+
+      if (atime_preserve_option)
+	utime (current_stat_info.file_name, &restore_times);
 
 
     quit:
     quit:
       break;
       break;
@@ -556,33 +451,21 @@ diff_archive (void)
 #if !MSDOS
 #if !MSDOS
     case LNKTYPE:
     case LNKTYPE:
       {
       {
-	dev_t dev;
-	ino_t ino;
+	struct stat link_data;
 
 
-	if (!get_stat_data (&stat_data))
+	if (!get_stat_data (current_stat_info.file_name, &stat_data))
+	  break;
+	if (!get_stat_data (current_stat_info.link_name, &link_data))
 	  break;
 	  break;
 
 
-	dev = stat_data.st_dev;
-	ino = stat_data.st_ino;
-	status = stat (current_link_name, &stat_data);
-	if (status < 0)
-	  {
-	    if (errno == ENOENT)
-	      report_difference (_("Does not exist"));
-	    else
-	      {
-		WARN ((0, errno, _("Cannot stat file %s"), current_file_name));
-		report_difference (NULL);
-	      }
-	    break;
-	  }
-
-	if (stat_data.st_dev != dev || stat_data.st_ino != ino)
+	if (stat_data.st_dev != link_data.st_dev
+	    || stat_data.st_ino != link_data.st_ino)
 	  {
 	  {
-	    char *message = (char *)
-	      xmalloc (MESSAGE_BUFFER_SIZE + strlen (current_link_name));
+	    char *message =
+	      xmalloc (MESSAGE_BUFFER_SIZE + 4 * strlen (current_stat_info.link_name));
 
 
-	    sprintf (message, _("Not linked to %s"), current_link_name);
+	    sprintf (message, _("Not linked to %s"),
+		     quote (current_stat_info.link_name));
 	    report_difference (message);
 	    report_difference (message);
 	    free (message);
 	    free (message);
 	    break;
 	    break;
@@ -592,80 +475,61 @@ diff_archive (void)
       }
       }
 #endif /* not MSDOS */
 #endif /* not MSDOS */
 
 
-#ifdef S_ISLNK
+#ifdef HAVE_READLINK
     case SYMTYPE:
     case SYMTYPE:
       {
       {
-	char linkbuf[NAME_FIELD_SIZE + 3]; /* FIXME: may be too short.  */
+	size_t len = strlen (current_stat_info.link_name);
+	char *linkbuf = alloca (len + 1);
 
 
-	status = readlink (current_file_name, linkbuf, (sizeof linkbuf) - 1);
+	status = readlink (current_stat_info.file_name, linkbuf, len + 1);
 
 
 	if (status < 0)
 	if (status < 0)
 	  {
 	  {
 	    if (errno == ENOENT)
 	    if (errno == ENOENT)
-	      report_difference (_("No such file or directory"));
+	      readlink_warn (current_stat_info.file_name);
 	    else
 	    else
-	      {
-		WARN ((0, errno, _("Cannot read link %s"), current_file_name));
-		report_difference (NULL);
-	      }
-	    break;
+	      readlink_error (current_stat_info.file_name);
+	    report_difference (0);
 	  }
 	  }
-
-	linkbuf[status] = '\0';	/* null-terminate it */
-	if (strncmp (current_link_name, linkbuf, (size_t) status) != 0)
+	else if (status != len
+		 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
 	  report_difference (_("Symlink differs"));
 	  report_difference (_("Symlink differs"));
 
 
 	break;
 	break;
       }
       }
-#endif /* not S_ISLNK */
+#endif
 
 
-#ifdef S_IFCHR
     case CHRTYPE:
     case CHRTYPE:
-      current_stat.st_mode |= S_IFCHR;
-      goto check_node;
-#endif /* not S_IFCHR */
-
-#ifdef S_IFBLK
-      /* If local system doesn't support block devices, use default case.  */
-
     case BLKTYPE:
     case BLKTYPE:
-      current_stat.st_mode |= S_IFBLK;
-      goto check_node;
-#endif /* not S_IFBLK */
-
-#ifdef S_ISFIFO
-      /* If local system doesn't support FIFOs, use default case.  */
-
     case FIFOTYPE:
     case FIFOTYPE:
-# ifdef S_IFIFO
-      current_stat.st_mode |= S_IFIFO;
-# endif
-      current_stat.st_rdev = 0;	/* FIXME: do we need this? */
-      goto check_node;
-#endif /* S_ISFIFO */
-
-    check_node:
+
       /* FIXME: deal with umask.  */
       /* FIXME: deal with umask.  */
 
 
-      if (!get_stat_data (&stat_data))
+      if (!get_stat_data (current_stat_info.file_name, &stat_data))
 	break;
 	break;
 
 
-      if (current_stat.st_rdev != stat_data.st_rdev)
+      if (current_header->header.typeflag == CHRTYPE
+	  ? !S_ISCHR (stat_data.st_mode)
+	  : current_header->header.typeflag == BLKTYPE
+	  ? !S_ISBLK (stat_data.st_mode)
+	  : /* current_header->header.typeflag == FIFOTYPE */
+	  !S_ISFIFO (stat_data.st_mode))
 	{
 	{
-	  report_difference (_("Device numbers changed"));
+	  report_difference (_("File type differs"));
 	  break;
 	  break;
 	}
 	}
 
 
-      if (
-#ifdef S_IFMT
-	  current_stat.st_mode != stat_data.st_mode
-#else
-	  /* POSIX lossage.  */
-	  (current_stat.st_mode & 07777) != (stat_data.st_mode & 07777)
-#endif
-	  )
+      if ((current_header->header.typeflag == CHRTYPE
+	   || current_header->header.typeflag == BLKTYPE)
+	  && current_stat_info.stat.st_rdev != stat_data.st_rdev)
+	{
+	  report_difference (_("Device number differs"));
+	  break;
+	}
+
+      if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
 	{
 	{
-	  report_difference (_("Mode or device-type changed"));
+	  report_difference (_("Mode differs"));
 	  break;
 	  break;
 	}
 	}
 
 
@@ -673,49 +537,46 @@ diff_archive (void)
 
 
     case GNUTYPE_DUMPDIR:
     case GNUTYPE_DUMPDIR:
       {
       {
-	char *dumpdir_buffer = get_directory_contents (current_file_name, 0);
+	char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0);
 
 
 	if (multi_volume_option)
 	if (multi_volume_option)
 	  {
 	  {
-	    assign_string (&save_name, current_file_name);
-	    save_totsize = current_stat.st_size;
+	    assign_string (&save_name, current_stat_info.file_name);
+	    save_totsize = current_stat_info.stat.st_size;
 	    /* save_sizeleft is set in read_and_process.  */
 	    /* save_sizeleft is set in read_and_process.  */
 	  }
 	  }
 
 
 	if (dumpdir_buffer)
 	if (dumpdir_buffer)
 	  {
 	  {
 	    dumpdir_cursor = dumpdir_buffer;
 	    dumpdir_cursor = dumpdir_buffer;
-	    read_and_process ((long) (current_stat.st_size), process_dumpdir);
+	    read_and_process (current_stat_info.stat.st_size, process_dumpdir);
 	    free (dumpdir_buffer);
 	    free (dumpdir_buffer);
 	  }
 	  }
 	else
 	else
-	  read_and_process ((long) (current_stat.st_size), process_noop);
+	  read_and_process (current_stat_info.stat.st_size, process_noop);
 
 
 	if (multi_volume_option)
 	if (multi_volume_option)
-	  assign_string (&save_name, NULL);
+	  assign_string (&save_name, 0);
 	/* Fall through.  */
 	/* Fall through.  */
       }
       }
 
 
     case DIRTYPE:
     case DIRTYPE:
-      /* Check for trailing /.  */
-
-      name_length = strlen (current_file_name) - 1;
-
     really_dir:
     really_dir:
-      while (name_length && current_file_name[name_length] == '/')
-	current_file_name[name_length--] = '\0';	/* zap / */
-
-      if (!get_stat_data (&stat_data))
+      if (!get_stat_data (current_stat_info.file_name, &stat_data))
 	break;
 	break;
 
 
       if (!S_ISDIR (stat_data.st_mode))
       if (!S_ISDIR (stat_data.st_mode))
 	{
 	{
-	  report_difference (_("No longer a directory"));
+	  report_difference (_("File type differs"));
+	  break;
+	}
+
+      if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
+	{
+	  report_difference (_("Mode differs"));
 	  break;
 	  break;
 	}
 	}
 
 
-      if ((stat_data.st_mode & 07777) != (current_stat.st_mode & 07777))
-	report_difference (_("Mode differs"));
       break;
       break;
 
 
     case GNUTYPE_VOLHDR:
     case GNUTYPE_VOLHDR:
@@ -725,73 +586,65 @@ diff_archive (void)
       {
       {
 	off_t offset;
 	off_t offset;
 
 
-	name_length = strlen (current_file_name) - 1;
-	if (current_file_name[name_length] == '/')
+	if (current_stat_info.had_trailing_slash)
 	  goto really_dir;
 	  goto really_dir;
 
 
-	if (!get_stat_data (&stat_data))
+	if (!get_stat_data (current_stat_info.file_name, &stat_data))
 	  break;
 	  break;
 
 
 	if (!S_ISREG (stat_data.st_mode))
 	if (!S_ISREG (stat_data.st_mode))
 	  {
 	  {
-	    report_difference (_("Not a regular file"));
-	    skip_file ((long) current_stat.st_size);
+	    report_difference (_("File type differs"));
+	    skip_member ();
 	    break;
 	    break;
 	  }
 	  }
 
 
-	stat_data.st_mode &= 07777;
-	offset = from_oct (1 + 12, current_header->oldgnu_header.offset);
-	if (stat_data.st_size != current_stat.st_size + offset)
+	offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
+	if (stat_data.st_size != current_stat_info.stat.st_size + offset)
 	  {
 	  {
 	    report_difference (_("Size differs"));
 	    report_difference (_("Size differs"));
-	    skip_file ((long) current_stat.st_size);
+	    skip_member ();
 	    break;
 	    break;
 	  }
 	  }
 
 
-	diff_handle = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
+	diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
 
 
 	if (diff_handle < 0)
 	if (diff_handle < 0)
 	  {
 	  {
-	    WARN ((0, errno, _("Cannot open file %s"), current_file_name));
-	    report_difference (NULL);
-	    skip_file ((long) current_stat.st_size);
+	    open_error (current_stat_info.file_name);
+	    report_difference (0);
+	    skip_member ();
 	    break;
 	    break;
 	  }
 	  }
 
 
-	status = lseek (diff_handle, offset, 0);
-	if (status != offset)
+	if (lseek (diff_handle, offset, SEEK_SET) < 0)
 	  {
 	  {
-	    WARN ((0, errno, _("Cannot seek to %ld in file %s"),
-		   offset, current_file_name));
-	    report_difference (NULL);
+	    seek_error_details (current_stat_info.file_name, offset);
+	    report_difference (0);
 	    break;
 	    break;
 	  }
 	  }
 
 
 	if (multi_volume_option)
 	if (multi_volume_option)
 	  {
 	  {
-	    assign_string (&save_name, current_file_name);
+	    assign_string (&save_name, current_stat_info.file_name);
 	    save_totsize = stat_data.st_size;
 	    save_totsize = stat_data.st_size;
 	    /* save_sizeleft is set in read_and_process.  */
 	    /* save_sizeleft is set in read_and_process.  */
 	  }
 	  }
 
 
-	read_and_process ((long) (current_stat.st_size), process_rawdata);
+	read_and_process (current_stat_info.stat.st_size, process_rawdata);
 
 
 	if (multi_volume_option)
 	if (multi_volume_option)
-	  assign_string (&save_name, NULL);
+	  assign_string (&save_name, 0);
 
 
 	status = close (diff_handle);
 	status = close (diff_handle);
-	if (status < 0)
-	  ERROR ((0, errno, _("Error while closing %s"), current_file_name));
+	if (status != 0)
+	  close_error (current_stat_info.file_name);
 
 
 	break;
 	break;
       }
       }
     }
     }
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
-
 void
 void
 verify_volume (void)
 verify_volume (void)
 {
 {
@@ -829,12 +682,10 @@ verify_volume (void)
 		status < 0))
 		status < 0))
 	  {
 	  {
 #endif
 #endif
-	    if (rmtlseek (archive, 0L, 0) != 0)
+	    if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
 	      {
 	      {
 		/* Lseek failed.  Try a different method.  */
 		/* Lseek failed.  Try a different method.  */
-
-		WARN ((0, errno,
-		       _("Could not rewind archive file for verify")));
+		seek_warn (archive_name_array[0]);
 		return;
 		return;
 	      }
 	      }
 #ifdef MTIOCTOP
 #ifdef MTIOCTOP
@@ -849,17 +700,19 @@ verify_volume (void)
   flush_read ();
   flush_read ();
   while (1)
   while (1)
     {
     {
-      enum read_header status = read_header ();
+      enum read_header status = read_header (0);
 
 
       if (status == HEADER_FAILURE)
       if (status == HEADER_FAILURE)
 	{
 	{
 	  int counter = 0;
 	  int counter = 0;
 
 
-	  while (status == HEADER_FAILURE);
+	  do
 	    {
 	    {
 	      counter++;
 	      counter++;
-	      status = read_header ();
+	      status = read_header (0);
 	    }
 	    }
+	  while (status == HEADER_FAILURE);
+
 	  ERROR ((0, 0,
 	  ERROR ((0, 0,
 		  _("VERIFY FAILURE: %d invalid header(s) detected"), counter));
 		  _("VERIFY FAILURE: %d invalid header(s) detected"), counter));
 	}
 	}

+ 77 - 77
src/create.c

@@ -359,7 +359,7 @@ write_eot (void)
 
 
 /* FIXME: Cross recursion between start_header and write_long!  */
 /* FIXME: Cross recursion between start_header and write_long!  */
 
 
-static union block *start_header (const char *, struct stat *);
+static union block *start_header (const char *, struct tar_stat_info *);
 
 
 static void
 static void
 write_long (const char *p, char type)
 write_long (const char *p, char type)
@@ -367,10 +367,10 @@ write_long (const char *p, char type)
   size_t size = strlen (p) + 1;
   size_t size = strlen (p) + 1;
   size_t bufsize;
   size_t bufsize;
   union block *header;
   union block *header;
-  struct stat foo;
+  struct tar_stat_info foo;
 
 
   memset (&foo, 0, sizeof foo);
   memset (&foo, 0, sizeof foo);
-  foo.st_size = size;
+  foo.stat.st_size = size;
 
 
   header = start_header ("././@LongLink", &foo);
   header = start_header ("././@LongLink", &foo);
   header->header.typeflag = type;
   header->header.typeflag = type;
@@ -400,7 +400,7 @@ write_long (const char *p, char type)
    and return its address.  */
    and return its address.  */
 
 
 static union block *
 static union block *
-start_header (const char *name, struct stat *st)
+start_header (const char *name, struct tar_stat_info *st)
 {
 {
   union block *header;
   union block *header;
 
 
@@ -411,7 +411,7 @@ start_header (const char *name, struct stat *st)
   header = find_next_block ();
   header = find_next_block ();
   memset (header->buffer, 0, sizeof (union block));
   memset (header->buffer, 0, sizeof (union block));
 
 
-  assign_string (&current_file_name, name);
+  assign_string (&current_stat_info.file_name, name);
 
 
   strncpy (header->header.name, name, NAME_FIELD_SIZE);
   strncpy (header->header.name, name, NAME_FIELD_SIZE);
   header->header.name[NAME_FIELD_SIZE - 1] = '\0';
   header->header.name[NAME_FIELD_SIZE - 1] = '\0';
@@ -419,12 +419,12 @@ start_header (const char *name, struct stat *st)
   /* Override some stat fields, if requested to do so.  */
   /* Override some stat fields, if requested to do so.  */
 
 
   if (owner_option != (uid_t) -1)
   if (owner_option != (uid_t) -1)
-    st->st_uid = owner_option;
+    st->stat.st_uid = owner_option;
   if (group_option != (gid_t) -1)
   if (group_option != (gid_t) -1)
-    st->st_gid = group_option;
+    st->stat.st_gid = group_option;
   if (mode_option)
   if (mode_option)
-    st->st_mode = ((st->st_mode & ~MODE_ALL)
-		   | mode_adjust (st->st_mode, mode_option));
+    st->stat.st_mode = ((st->stat.st_mode & ~MODE_ALL)
+		   | mode_adjust (st->stat.st_mode, mode_option));
 
 
   /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
   /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
      for a few tars and came up with the following interoperability
      for a few tars and came up with the following interoperability
@@ -450,22 +450,22 @@ start_header (const char *name, struct stat *st)
      acceptor for Paul's test.  */
      acceptor for Paul's test.  */
 
 
   if (archive_format == V7_FORMAT)
   if (archive_format == V7_FORMAT)
-    MODE_TO_CHARS (st->st_mode & MODE_ALL, header->header.mode);
+    MODE_TO_CHARS (st->stat.st_mode & MODE_ALL, header->header.mode);
   else
   else
-    MODE_TO_CHARS (st->st_mode, header->header.mode);
+    MODE_TO_CHARS (st->stat.st_mode, header->header.mode);
 
 
-  UID_TO_CHARS (st->st_uid, header->header.uid);
-  GID_TO_CHARS (st->st_gid, header->header.gid);
-  OFF_TO_CHARS (st->st_size, header->header.size);
-  TIME_TO_CHARS (st->st_mtime, header->header.mtime);
+  UID_TO_CHARS (st->stat.st_uid, header->header.uid);
+  GID_TO_CHARS (st->stat.st_gid, header->header.gid);
+  OFF_TO_CHARS (st->stat.st_size, header->header.size);
+  TIME_TO_CHARS (st->stat.st_mtime, header->header.mtime);
   MAJOR_TO_CHARS (0, header->header.devmajor);
   MAJOR_TO_CHARS (0, header->header.devmajor);
   MINOR_TO_CHARS (0, header->header.devminor);
   MINOR_TO_CHARS (0, header->header.devminor);
 
 
   if (incremental_option)
   if (incremental_option)
     if (archive_format == OLDGNU_FORMAT)
     if (archive_format == OLDGNU_FORMAT)
       {
       {
-	TIME_TO_CHARS (st->st_atime, header->oldgnu_header.atime);
-	TIME_TO_CHARS (st->st_ctime, header->oldgnu_header.ctime);
+	TIME_TO_CHARS (st->stat.st_atime, header->oldgnu_header.atime);
+	TIME_TO_CHARS (st->stat.st_ctime, header->oldgnu_header.ctime);
       }
       }
 
 
   header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
   header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
@@ -496,8 +496,8 @@ start_header (const char *name, struct stat *st)
     }
     }
   else
   else
     {
     {
-      uid_to_uname (st->st_uid, header->header.uname);
-      gid_to_gname (st->st_gid, header->header.gname);
+      uid_to_uname (st->stat.st_uid, header->header.uname);
+      gid_to_gname (st->stat.st_gid, header->header.gname);
     }
     }
 
 
   return header;
   return header;
@@ -541,7 +541,7 @@ finish_header (union block *header, off_t block_ordinal)
       /* These globals are parameters to print_header, sigh.  */
       /* These globals are parameters to print_header, sigh.  */
 
 
       current_header = header;
       current_header = header;
-      /* current_stat is already set up.  */
+      /* current_stat_info is already set up.  */
       current_format = archive_format;
       current_format = archive_format;
       print_header (block_ordinal);
       print_header (block_ordinal);
     }
     }
@@ -859,7 +859,7 @@ static Hash_table *link_table;
    of an incremental dump.  PARENT_DEVICE is the device of P's
    of an incremental dump.  PARENT_DEVICE is the device of P's
    parent directory; it is examined only if TOP_LEVEL is zero.
    parent directory; it is examined only if TOP_LEVEL is zero.
 
 
-   Set global CURRENT_STAT to stat output for this file.  */
+   Set global CURRENT_STAT_INFO to stat output for this file.  */
 
 
 /* FIXME: One should make sure that for *every* path leading to setting
 /* FIXME: One should make sure that for *every* path leading to setting
    exit_status to failure, a clear diagnostic has been issued.  */
    exit_status to failure, a clear diagnostic has been issued.  */
@@ -881,7 +881,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
   if (interactive_option && !confirm ("add", p))
   if (interactive_option && !confirm ("add", p))
     return;
     return;
 
 
-  if (deref_stat (dereference_option, p, &current_stat) != 0)
+  if (deref_stat (dereference_option, p, &current_stat_info.stat) != 0)
     {
     {
       if (ignore_failed_read_option)
       if (ignore_failed_read_option)
 	stat_warn (p);
 	stat_warn (p);
@@ -890,12 +890,12 @@ dump_file (char *p, int top_level, dev_t parent_device)
       return;
       return;
     }
     }
 
 
-  original_ctime = current_stat.st_ctime;
-  restore_times.actime = current_stat.st_atime;
-  restore_times.modtime = current_stat.st_mtime;
+  original_ctime = current_stat_info.stat.st_ctime;
+  restore_times.actime = current_stat_info.stat.st_atime;
+  restore_times.modtime = current_stat_info.stat.st_mtime;
 
 
 #ifdef S_ISHIDDEN
 #ifdef S_ISHIDDEN
-  if (S_ISHIDDEN (current_stat.st_mode))
+  if (S_ISHIDDEN (current_stat_info.stat.st_mode))
     {
     {
       char *new = (char *) alloca (strlen (p) + 2);
       char *new = (char *) alloca (strlen (p) + 2);
       if (new)
       if (new)
@@ -911,9 +911,9 @@ dump_file (char *p, int top_level, dev_t parent_device)
      put in the archive.  */
      put in the archive.  */
 
 
   if ((0 < top_level || !incremental_option)
   if ((0 < top_level || !incremental_option)
-      && !S_ISDIR (current_stat.st_mode)
-      && current_stat.st_mtime < newer_mtime_option
-      && (!after_date_option || current_stat.st_ctime < newer_ctime_option))
+      && !S_ISDIR (current_stat_info.stat.st_mode)
+      && current_stat_info.stat.st_mtime < newer_mtime_option
+      && (!after_date_option || current_stat_info.stat.st_ctime < newer_ctime_option))
     {
     {
       if (0 < top_level)
       if (0 < top_level)
 	WARN ((0, 0, _("%s: file is unchanged; not dumped"),
 	WARN ((0, 0, _("%s: file is unchanged; not dumped"),
@@ -925,7 +925,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
 #if !MSDOS
 #if !MSDOS
   /* See if we are trying to dump the archive.  */
   /* See if we are trying to dump the archive.  */
 
 
-  if (ar_dev && current_stat.st_dev == ar_dev && current_stat.st_ino == ar_ino)
+  if (ar_dev && current_stat_info.stat.st_dev == ar_dev && current_stat_info.stat.st_ino == ar_ino)
     {
     {
       WARN ((0, 0, _("%s: file is the archive; not dumped"),
       WARN ((0, 0, _("%s: file is the archive; not dumped"),
 	     quotearg_colon (p)));
 	     quotearg_colon (p)));
@@ -933,7 +933,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
     }
     }
 #endif
 #endif
 
 
-  if (S_ISDIR (current_stat.st_mode))
+  if (S_ISDIR (current_stat_info.stat.st_mode))
     {
     {
       char *directory;
       char *directory;
       char const *entry;
       char const *entry;
@@ -941,7 +941,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
       char *namebuf;
       char *namebuf;
       size_t buflen;
       size_t buflen;
       size_t len;
       size_t len;
-      dev_t our_device = current_stat.st_dev;
+      dev_t our_device = current_stat_info.stat.st_dev;
 
 
       errno = 0;
       errno = 0;
 
 
@@ -979,18 +979,18 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	     directory blocks to be written even with old archives.  */
 	     directory blocks to be written even with old archives.  */
 
 
 	  block_ordinal = current_block_ordinal ();
 	  block_ordinal = current_block_ordinal ();
-	  current_stat.st_size = 0;	/* force 0 size on dir */
+	  current_stat_info.stat.st_size = 0;	/* force 0 size on dir */
 
 
 	  /* FIXME: If people could really read standard archives, this
 	  /* FIXME: If people could really read standard archives, this
 	     should be:
 	     should be:
 
 
 	     header
 	     header
-	       = start_header (standard_option ? p : namebuf, &current_stat);
+	       = start_header (standard_option ? p : namebuf, &current_stat_info);
 
 
 	     but since they'd interpret DIRTYPE blocks as regular
 	     but since they'd interpret DIRTYPE blocks as regular
 	     files, we'd better put the / on the name.  */
 	     files, we'd better put the / on the name.  */
 
 
-	  header = start_header (namebuf, &current_stat);
+	  header = start_header (namebuf, &current_stat_info);
 
 
 	  if (incremental_option)
 	  if (incremental_option)
 	    header->header.typeflag = GNUTYPE_DUMPDIR;
 	    header->header.typeflag = GNUTYPE_DUMPDIR;
@@ -1063,7 +1063,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	 avoid doing so if the user only wants to dump one file system.  */
 	 avoid doing so if the user only wants to dump one file system.  */
 
 
       if (one_file_system_option && !top_level
       if (one_file_system_option && !top_level
-	  && parent_device != current_stat.st_dev)
+	  && parent_device != current_stat_info.stat.st_dev)
 	{
 	{
 	  if (verbose_option)
 	  if (verbose_option)
 	    WARN ((0, 0,
 	    WARN ((0, 0,
@@ -1104,12 +1104,12 @@ dump_file (char *p, int top_level, dev_t parent_device)
     {
     {
       /* Check for multiple links.  */
       /* Check for multiple links.  */
 
 
-      if (1 < current_stat.st_nlink && link_table)
+      if (1 < current_stat_info.stat.st_nlink && link_table)
 	{
 	{
 	  struct link lp;
 	  struct link lp;
 	  struct link *dup;
 	  struct link *dup;
-	  lp.ino = current_stat.st_ino;
-	  lp.dev = current_stat.st_dev;
+	  lp.ino = current_stat_info.stat.st_ino;
+	  lp.dev = current_stat_info.stat.st_dev;
 
 
 	  if ((dup = hash_lookup (link_table, &lp)))
 	  if ((dup = hash_lookup (link_table, &lp)))
 	    {
 	    {
@@ -1121,10 +1121,10 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	      block_ordinal = current_block_ordinal ();
 	      block_ordinal = current_block_ordinal ();
 	      if (NAME_FIELD_SIZE <= strlen (link_name))
 	      if (NAME_FIELD_SIZE <= strlen (link_name))
 		write_long (link_name, GNUTYPE_LONGLINK);
 		write_long (link_name, GNUTYPE_LONGLINK);
-	      assign_string (&current_link_name, link_name);
+	      assign_string (&current_stat_info.link_name, link_name);
 
 
-	      current_stat.st_size = 0;
-	      header = start_header (p, &current_stat);
+	      current_stat_info.stat.st_size = 0;
+	      header = start_header (p, &current_stat_info);
 	      strncpy (header->header.linkname, link_name, NAME_FIELD_SIZE);
 	      strncpy (header->header.linkname, link_name, NAME_FIELD_SIZE);
 
 
 	      /* Force null termination.  */
 	      /* Force null termination.  */
@@ -1146,8 +1146,8 @@ dump_file (char *p, int top_level, dev_t parent_device)
 
 
       /* This is not a link to a previously dumped file, so dump it.  */
       /* This is not a link to a previously dumped file, so dump it.  */
 
 
-      if (S_ISREG (current_stat.st_mode)
-	  || S_ISCTG (current_stat.st_mode))
+      if (S_ISREG (current_stat_info.stat.st_mode)
+	  || S_ISCTG (current_stat_info.stat.st_mode))
 	{
 	{
 	  int f;			/* file descriptor */
 	  int f;			/* file descriptor */
 	  size_t bufsize;
 	  size_t bufsize;
@@ -1193,14 +1193,14 @@ dump_file (char *p, int top_level, dev_t parent_device)
 		 st_blocks, so `du' and `ls -s' give wrong results.  So, the
 		 st_blocks, so `du' and `ls -s' give wrong results.  So, the
 		 --sparse option would not work on a minix filesystem.  */
 		 --sparse option would not work on a minix filesystem.  */
 
 
-	      if (ST_NBLOCKS (current_stat)
-		  < (current_stat.st_size / ST_NBLOCKSIZE
-		     + (current_stat.st_size % ST_NBLOCKSIZE != 0)))
+	      if (ST_NBLOCKS (current_stat_info.stat)
+		  < (current_stat_info.stat.st_size / ST_NBLOCKSIZE
+		     + (current_stat_info.stat.st_size % ST_NBLOCKSIZE != 0)))
 		{
 		{
 		  int counter;
 		  int counter;
 
 
 		  block_ordinal = current_block_ordinal ();
 		  block_ordinal = current_block_ordinal ();
-		  header = start_header (p, &current_stat);
+		  header = start_header (p, &current_stat_info);
 		  header->header.typeflag = GNUTYPE_SPARSE;
 		  header->header.typeflag = GNUTYPE_SPARSE;
 		  header_moved = 1;
 		  header_moved = 1;
 
 
@@ -1221,15 +1221,15 @@ dump_file (char *p, int top_level, dev_t parent_device)
 		     <file>.  It might be kind of disconcerting if the
 		     <file>.  It might be kind of disconcerting if the
 		     shrunken file size was the one that showed up.  */
 		     shrunken file size was the one that showed up.  */
 
 
-		  OFF_TO_CHARS (current_stat.st_size,
+		  OFF_TO_CHARS (current_stat_info.stat.st_size,
 				header->oldgnu_header.realsize);
 				header->oldgnu_header.realsize);
 
 
 		  /* This will be the new "size" of the file, i.e., the size
 		  /* This will be the new "size" of the file, i.e., the size
 		     of the file minus the blocks of holes that we're
 		     of the file minus the blocks of holes that we're
 		     skipping over.  */
 		     skipping over.  */
 
 
-		  current_stat.st_size = find_new_file_size (sparses);
-		  OFF_TO_CHARS (current_stat.st_size, header->header.size);
+		  current_stat_info.stat.st_size = find_new_file_size (sparses);
+		  OFF_TO_CHARS (current_stat_info.stat.st_size, header->header.size);
 
 
 		  for (counter = 0;
 		  for (counter = 0;
 		       counter < sparses && counter < SPARSES_IN_OLDGNU_HEADER;
 		       counter < sparses && counter < SPARSES_IN_OLDGNU_HEADER;
@@ -1243,14 +1243,14 @@ dump_file (char *p, int top_level, dev_t parent_device)
 		}
 		}
 	    }
 	    }
 
 
-	  sizeleft = current_stat.st_size;
+	  sizeleft = current_stat_info.stat.st_size;
 
 
 	  /* Don't bother opening empty, world readable files.  Also do not open
 	  /* Don't bother opening empty, world readable files.  Also do not open
 	     files when archive is meant for /dev/null.  */
 	     files when archive is meant for /dev/null.  */
 
 
 	  if (dev_null_output
 	  if (dev_null_output
 	      || (sizeleft == 0
 	      || (sizeleft == 0
-		  && MODE_R == (MODE_R & current_stat.st_mode)))
+		  && MODE_R == (MODE_R & current_stat_info.stat.st_mode)))
 	    f = -1;
 	    f = -1;
 	  else
 	  else
 	    {
 	    {
@@ -1271,12 +1271,12 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	  if (!header_moved)
 	  if (!header_moved)
 	    {
 	    {
 	      block_ordinal = current_block_ordinal ();
 	      block_ordinal = current_block_ordinal ();
-	      header = start_header (p, &current_stat);
+	      header = start_header (p, &current_stat_info);
 	    }
 	    }
 
 
 	  /* Mark contiguous files, if we support them.  */
 	  /* Mark contiguous files, if we support them.  */
 
 
-	  if (archive_format != V7_FORMAT && S_ISCTG (current_stat.st_mode))
+	  if (archive_format != V7_FORMAT && S_ISCTG (current_stat_info.stat.st_mode))
 	    header->header.typeflag = CONTTYPE;
 	    header->header.typeflag = CONTTYPE;
 
 
 	  isextended = header->oldgnu_header.isextended;
 	  isextended = header->oldgnu_header.isextended;
@@ -1312,7 +1312,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	    {
 	    {
 	      if (f < 0
 	      if (f < 0
 		  || finish_sparse_file (f, &sizeleft,
 		  || finish_sparse_file (f, &sizeleft,
-					 current_stat.st_size, p))
+					 current_stat_info.stat.st_size, p))
 		goto padit;
 		goto padit;
 	    }
 	    }
 	  else
 	  else
@@ -1322,7 +1322,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
 		  {
 		  {
 		    assign_string (&save_name, p);
 		    assign_string (&save_name, p);
 		    save_sizeleft = sizeleft;
 		    save_sizeleft = sizeleft;
-		    save_totsize = current_stat.st_size;
+		    save_totsize = current_stat_info.stat.st_size;
 		  }
 		  }
 		start = find_next_block ();
 		start = find_next_block ();
 
 
@@ -1346,7 +1346,7 @@ dump_file (char *p, int top_level, dev_t parent_device)
 		    (ignore_failed_read_option
 		    (ignore_failed_read_option
 		     ? read_warn_details
 		     ? read_warn_details
 		     : read_error_details)
 		     : read_error_details)
-		      (p, current_stat.st_size - sizeleft, bufsize);
+		      (p, current_stat_info.stat.st_size - sizeleft, bufsize);
 		    goto padit;
 		    goto padit;
 		  }
 		  }
 		sizeleft -= count;
 		sizeleft -= count;
@@ -1428,12 +1428,12 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	  goto file_was_dumped;
 	  goto file_was_dumped;
 	}
 	}
 #ifdef HAVE_READLINK
 #ifdef HAVE_READLINK
-      else if (S_ISLNK (current_stat.st_mode))
+      else if (S_ISLNK (current_stat_info.stat.st_mode))
 	{
 	{
 	  char *buffer;
 	  char *buffer;
 	  int size;
 	  int size;
-	  size_t linklen = current_stat.st_size;
-	  if (linklen != current_stat.st_size || linklen + 1 == 0)
+	  size_t linklen = current_stat_info.stat.st_size;
+	  if (linklen != current_stat_info.stat.st_size || linklen + 1 == 0)
 	    xalloc_die ();
 	    xalloc_die ();
 	  buffer = (char *) alloca (linklen + 1);
 	  buffer = (char *) alloca (linklen + 1);
 	  size = readlink (p, buffer, linklen + 1);
 	  size = readlink (p, buffer, linklen + 1);
@@ -1448,11 +1448,11 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	  buffer[size] = '\0';
 	  buffer[size] = '\0';
 	  if (size >= NAME_FIELD_SIZE)
 	  if (size >= NAME_FIELD_SIZE)
 	    write_long (buffer, GNUTYPE_LONGLINK);
 	    write_long (buffer, GNUTYPE_LONGLINK);
-	  assign_string (&current_link_name, buffer);
+	  assign_string (&current_stat_info.link_name, buffer);
 
 
 	  block_ordinal = current_block_ordinal ();
 	  block_ordinal = current_block_ordinal ();
-	  current_stat.st_size = 0;	/* force 0 size on symlink */
-	  header = start_header (p, &current_stat);
+	  current_stat_info.stat.st_size = 0;	/* force 0 size on symlink */
+	  header = start_header (p, &current_stat_info);
 	  strncpy (header->header.linkname, buffer, NAME_FIELD_SIZE);
 	  strncpy (header->header.linkname, buffer, NAME_FIELD_SIZE);
 	  header->header.linkname[NAME_FIELD_SIZE - 1] = '\0';
 	  header->header.linkname[NAME_FIELD_SIZE - 1] = '\0';
 	  header->header.typeflag = SYMTYPE;
 	  header->header.typeflag = SYMTYPE;
@@ -1467,18 +1467,18 @@ dump_file (char *p, int top_level, dev_t parent_device)
 	  goto file_was_dumped;
 	  goto file_was_dumped;
 	}
 	}
 #endif
 #endif
-      else if (S_ISCHR (current_stat.st_mode))
+      else if (S_ISCHR (current_stat_info.stat.st_mode))
 	type = CHRTYPE;
 	type = CHRTYPE;
-      else if (S_ISBLK (current_stat.st_mode))
+      else if (S_ISBLK (current_stat_info.stat.st_mode))
 	type = BLKTYPE;
 	type = BLKTYPE;
-      else if (S_ISFIFO (current_stat.st_mode))
+      else if (S_ISFIFO (current_stat_info.stat.st_mode))
 	type = FIFOTYPE;
 	type = FIFOTYPE;
-      else if (S_ISSOCK (current_stat.st_mode))
+      else if (S_ISSOCK (current_stat_info.stat.st_mode))
 	{
 	{
 	  WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p)));
 	  WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p)));
 	  return;
 	  return;
 	}
 	}
-      else if (S_ISDOOR (current_stat.st_mode))
+      else if (S_ISDOOR (current_stat_info.stat.st_mode))
 	{
 	{
 	  WARN ((0, 0, _("%s: door ignored"), quotearg_colon (p)));
 	  WARN ((0, 0, _("%s: door ignored"), quotearg_colon (p)));
 	  return;
 	  return;
@@ -1491,14 +1491,14 @@ dump_file (char *p, int top_level, dev_t parent_device)
     goto unknown;
     goto unknown;
 
 
   block_ordinal = current_block_ordinal ();
   block_ordinal = current_block_ordinal ();
-  current_stat.st_size = 0;	/* force 0 size */
-  header = start_header (p, &current_stat);
+  current_stat_info.stat.st_size = 0;	/* force 0 size */
+  header = start_header (p, &current_stat_info);
   header->header.typeflag = type;
   header->header.typeflag = type;
 
 
   if (type != FIFOTYPE)
   if (type != FIFOTYPE)
     {
     {
-      MAJOR_TO_CHARS (major (current_stat.st_rdev), header->header.devmajor);
-      MINOR_TO_CHARS (minor (current_stat.st_rdev), header->header.devminor);
+      MAJOR_TO_CHARS (major (current_stat_info.stat.st_rdev), header->header.devmajor);
+      MINOR_TO_CHARS (minor (current_stat_info.stat.st_rdev), header->header.devminor);
     }
     }
 
 
   finish_header (header, block_ordinal);
   finish_header (header, block_ordinal);
@@ -1517,14 +1517,14 @@ unknown:
   return;
   return;
 
 
 file_was_dumped:
 file_was_dumped:
-  if (1 < current_stat.st_nlink)
+  if (1 < current_stat_info.stat.st_nlink)
     {
     {
       struct link *dup;
       struct link *dup;
       struct link *lp = xmalloc (offsetof (struct link, name)
       struct link *lp = xmalloc (offsetof (struct link, name)
 				 + strlen (p) + 1);
 				 + strlen (p) + 1);
-      lp->ino = current_stat.st_ino;
-      lp->dev = current_stat.st_dev;
-      lp->nlink = current_stat.st_nlink;
+      lp->ino = current_stat_info.stat.st_ino;
+      lp->dev = current_stat_info.stat.st_dev;
+      lp->nlink = current_stat_info.stat.st_nlink;
       strcpy (lp->name, p);
       strcpy (lp->name, p);
 
 
       if (! ((link_table
       if (! ((link_table

+ 210 - 184
src/delete.c

@@ -1,5 +1,7 @@
 /* Delete entries from a tar archive.
 /* Delete entries from a tar archive.
-   Copyright (C) 1988, 1992, 1994, 1996, 1997 Free Software Foundation, Inc.
+
+   Copyright (C) 1988, 1992, 1994, 1996, 1997, 2000, 2001, 2003 Free
+   Software Foundation, Inc.
 
 
    This program is free software; you can redistribute it and/or modify it
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
    under the terms of the GNU General Public License as published by the
@@ -13,97 +15,95 @@
 
 
    You should have received a copy of the GNU General Public License along
    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    with this program; if not, write to the Free Software Foundation, Inc.,
-   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 
 #include "system.h"
 #include "system.h"
 
 
-#define STDIN 0
-#define STDOUT 1
-
 #include "common.h"
 #include "common.h"
 #include "rmt.h"
 #include "rmt.h"
 
 
-static union block *new_record = NULL;
-static union block *save_record = NULL;
-static int records_read = 0;
-static int new_blocks = 0;
-static int blocks_needed = 0;
+static union block *new_record;
+static int new_blocks;
+static bool acting_as_filter;
 
 
-/* FIXME: This module should not directly handle the following three
-   variables, instead, this should be done in buffer.c only.  */
+/* FIXME: This module should not directly handle the following
+   variables, instead, the interface should be cleaned up.  */
 extern union block *record_start;
 extern union block *record_start;
 extern union block *record_end;
 extern union block *record_end;
 extern union block *current_block;
 extern union block *current_block;
-
-/*-------------------------------------------------------------------------.
-| Move archive descriptor by COUNT records worth.  If COUNT is positive we |
-| move forward, else we move negative.  If its a tape, MTIOCTOP had better |
-| work.  If its something else, we try to seek on it.  If we can't seek,   |
-| we loose!								   |
-`-------------------------------------------------------------------------*/
-
+extern union block *recent_long_name;
+extern union block *recent_long_link;
+extern off_t records_read; 
+extern off_t records_written;
+
+/* The number of records skipped at the start of the archive, when
+   passing over members that are not deleted.  */
+static off_t records_skipped;
+
+/* Move archive descriptor by COUNT records worth.  If COUNT is
+   positive we move forward, else we move negative.  If it's a tape,
+   MTIOCTOP had better work.  If it's something else, we try to seek
+   on it.  If we can't seek, we lose!  */
 static void
 static void
-move_archive (int count)
+move_archive (off_t count)
 {
 {
+  if (count == 0)
+    return;
+
 #ifdef MTIOCTOP
 #ifdef MTIOCTOP
   {
   {
     struct mtop operation;
     struct mtop operation;
-    int status;
 
 
-    if (count > 0)
-      {
-	operation.mt_op = MTFSR;
-	operation.mt_count = count;
-      }
-    else
+    if (count < 0
+	? (operation.mt_op = MTBSR,
+	   operation.mt_count = -count,
+	   operation.mt_count == -count)
+	: (operation.mt_op = MTFSR,
+	   operation.mt_count = count,
+	   operation.mt_count == count))
       {
       {
-	operation.mt_op = MTBSR;
-	operation.mt_count = -count;
-      }
-
-    if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
-	status >= 0)
-      return;
+	if (0 <= rmtioctl (archive, MTIOCTOP, (char *) &operation))
+	  return;
 
 
-    if (errno == EIO)
-      if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
-	  status >= 0)
-      return;
+	if (errno == EIO
+	    && 0 <= rmtioctl (archive, MTIOCTOP, (char *) &operation))
+	  return;
+      }
   }
   }
 #endif /* MTIOCTOP */
 #endif /* MTIOCTOP */
 
 
   {
   {
-    off_t position = rmtlseek (archive, 0L, 1);
-
-    position += record_size * count;
+    off_t position0 = rmtlseek (archive, (off_t) 0, SEEK_CUR);
+    off_t increment = record_size * (off_t) count;
+    off_t position = position0 + increment;
 
 
-    if (rmtlseek (archive, position, 0) != position)
-      FATAL_ERROR ((0, 0, _("Could not re-position archive file")));
+    if (increment / count != record_size
+	|| (position < position0) != (increment < 0)
+	|| (position = position < 0 ? 0 : position,
+	    rmtlseek (archive, position, SEEK_SET) != position))
+      seek_error_details (archive_name_array[0], position);
 
 
     return;
     return;
   }
   }
 }
 }
 
 
-/*----------------------------------------------------------------.
-| Write out the record which has been filled.  If MOVE_BACK_FLAG, |
-| backspace to where we started.                                  |
-`----------------------------------------------------------------*/
-
+/* Write out the record which has been filled.  If MOVE_BACK_FLAG,
+   backspace to where we started.  */
 static void
 static void
 write_record (int move_back_flag)
 write_record (int move_back_flag)
 {
 {
-  save_record = record_start;
+  union block *save_record = record_start;
   record_start = new_record;
   record_start = new_record;
 
 
-  if (archive == STDIN)
+  if (acting_as_filter)
     {
     {
-      archive = STDOUT;
+      archive = STDOUT_FILENO;
       flush_write ();
       flush_write ();
-      archive = STDIN;
+      archive = STDIN_FILENO;
     }
     }
   else
   else
     {
     {
-      move_archive (-(records_read + 1));
+      move_archive ((records_written + records_skipped) - records_read);
       flush_write ();
       flush_write ();
     }
     }
 
 
@@ -113,19 +113,24 @@ write_record (int move_back_flag)
     {
     {
       /* Move the tape head back to where we were.  */
       /* Move the tape head back to where we were.  */
 
 
-      if (archive != STDIN)
-	move_archive (records_read);
-
-      records_read--;
+      if (! acting_as_filter)
+	move_archive (records_read - (records_written + records_skipped));
     }
     }
 
 
-  blocks_needed = blocking_factor;
   new_blocks = 0;
   new_blocks = 0;
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
+static void
+write_recent_blocks (union block *h, size_t blocks)
+{
+  size_t i;
+  for (i = 0; i < blocks; i++)
+    {
+      new_record[new_blocks++] = h[i];
+      if (new_blocks == blocking_factor)
+	write_record (1);
+    }
+}
 
 
 void
 void
 delete_archive_members (void)
 delete_archive_members (void)
@@ -135,16 +140,17 @@ delete_archive_members (void)
 
 
   /* FIXME: Should clean the routine before cleaning these variables :-( */
   /* FIXME: Should clean the routine before cleaning these variables :-( */
   struct name *name;
   struct name *name;
-  int blocks_to_skip = 0;
-  int blocks_to_keep = 0;
+  off_t blocks_to_skip = 0;
+  off_t blocks_to_keep = 0;
   int kept_blocks_in_record;
   int kept_blocks_in_record;
 
 
   name_gather ();
   name_gather ();
   open_archive (ACCESS_UPDATE);
   open_archive (ACCESS_UPDATE);
+  acting_as_filter = strcmp (archive_name_array[0], "-") == 0;
 
 
-  while (logical_status == HEADER_STILL_UNREAD)
+  do
     {
     {
-      enum read_header status = read_header ();
+      enum read_header status = read_header (1);
 
 
       switch (status)
       switch (status)
 	{
 	{
@@ -152,19 +158,24 @@ delete_archive_members (void)
 	  abort ();
 	  abort ();
 
 
 	case HEADER_SUCCESS:
 	case HEADER_SUCCESS:
-	  if (name = name_scan (current_file_name), !name)
+	  if (name = name_scan (current_stat_info.file_name), !name)
 	    {
 	    {
-	      set_next_block_after (current_header);
-	      if (current_header->oldgnu_header.isextended)
-		skip_extended_headers ();
-	      skip_file ((long) (current_stat.st_size));
+	      skip_member ();
 	      break;
 	      break;
 	    }
 	    }
 	  name->found = 1;
 	  name->found = 1;
-	  logical_status = HEADER_SUCCESS;
+	  /* Fall through.  */
+	case HEADER_SUCCESS_EXTENDED:
+	  logical_status = status;
 	  break;
 	  break;
 
 
 	case HEADER_ZERO_BLOCK:
 	case HEADER_ZERO_BLOCK:
+	  if (ignore_zeros_option)
+	    {
+	      set_next_block_after (current_header);
+	      break;
+	    }
+	  /* Fall through.  */
 	case HEADER_END_OF_FILE:
 	case HEADER_END_OF_FILE:
 	  logical_status = HEADER_END_OF_FILE;
 	  logical_status = HEADER_END_OF_FILE;
 	  break;
 	  break;
@@ -193,144 +204,159 @@ delete_archive_members (void)
 
 
       previous_status = status;
       previous_status = status;
     }
     }
+  while (logical_status == HEADER_STILL_UNREAD);
 
 
-  if (logical_status != HEADER_SUCCESS)
-    {
-      write_eot ();
-      close_archive ();
-      names_notfound ();
-      return;
-    }
-
-  write_archive_to_stdout = 0;
-  new_record = (union block *) xmalloc ((size_t) record_size);
-
-  /* Save away blocks before this one in this record.  */
+  records_skipped = records_read - 1;
+  new_record = xmalloc (record_size);
 
 
-  new_blocks = current_block - record_start;
-  blocks_needed = blocking_factor - new_blocks;
-  if (new_blocks)
-    memcpy ((void *) new_record, (void *) record_start,
-	   (size_t) (new_blocks * BLOCKSIZE));
-
-#if 0
-  /* FIXME: Old code, before the goto was inserted.  To be redesigned.  */
-  set_next_block_after (current_header);
-  if (current_header->oldgnu_header.isextended)
-    skip_extended_headers ();
-  skip_file ((long) (current_stat.st_size));
-#endif
-  logical_status = HEADER_STILL_UNREAD;
-  goto flush_file;
-
-  /* FIXME: Solaris 2.4 Sun cc (the ANSI one, not the old K&R) says:
-       "delete.c", line 223: warning: loop not entered at top
-     Reported by Bruno Haible.  */
-  while (1)
+  if (logical_status == HEADER_SUCCESS
+      || logical_status == HEADER_SUCCESS_EXTENDED)
     {
     {
-      enum read_header status;
+      write_archive_to_stdout = 0;
 
 
-      /* Fill in a record.  */
+      /* Save away blocks before this one in this record.  */
 
 
-      if (current_block == record_end)
-	{
-	  flush_archive ();
-	  records_read++;
-	}
-      status = read_header ();
+      new_blocks = current_block - record_start;
+      if (new_blocks)
+	memcpy (new_record, record_start, new_blocks * BLOCKSIZE);
 
 
-      if (status == HEADER_ZERO_BLOCK && ignore_zeros_option)
+      if (logical_status == HEADER_SUCCESS)
 	{
 	{
-	  set_next_block_after (current_header);
-	  continue;
-	}
-      if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK)
-	{
-	  logical_status = HEADER_END_OF_FILE;
-	  memset (new_record[new_blocks].buffer, 0,
-		 (size_t) (BLOCKSIZE * blocks_needed));
-	  new_blocks += blocks_needed;
-	  blocks_needed = 0;
-	  write_record (0);
-	  break;
+	  /* FIXME: Pheew!  This is crufty code!  */
+	  logical_status = HEADER_STILL_UNREAD;
+	  goto flush_file;
 	}
 	}
 
 
-      if (status == HEADER_FAILURE)
+      /* FIXME: Solaris 2.4 Sun cc (the ANSI one, not the old K&R) says:
+	 "delete.c", line 223: warning: loop not entered at top
+	 Reported by Bruno Haible.  */
+      while (1)
 	{
 	{
-	  ERROR ((0, 0, _("Deleting non-header from archive")));
-	  set_next_block_after (current_header);
-	  continue;
-	}
+	  enum read_header status;
 
 
-      /* Found another header.  */
+	  /* Fill in a record.  */
 
 
-      if (name = name_scan (current_file_name), name)
-	{
-	  name->found = 1;
-	flush_file:
-	  set_next_block_after (current_header);
-	  blocks_to_skip = (current_stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+	  if (current_block == record_end)
+	    flush_archive ();
+	  status = read_header (0);
 
 
-	  while (record_end - current_block <= blocks_to_skip)
+	  if (status == HEADER_ZERO_BLOCK && ignore_zeros_option)
 	    {
 	    {
-	      blocks_to_skip -= (record_end - current_block);
-	      flush_archive ();
-	      records_read++;
+	      set_next_block_after (current_header);
+	      continue;
+	    }
+	  if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK)
+	    {
+	      logical_status = HEADER_END_OF_FILE;
+	      break;
 	    }
 	    }
-	  current_block += blocks_to_skip;
-	  blocks_to_skip = 0;
-	  continue;
-	}
 
 
-      /* Copy header.  */
+	  if (status == HEADER_FAILURE)
+	    {
+	      ERROR ((0, 0, _("Deleting non-header from archive")));
+	      set_next_block_after (current_header);
+	      continue;
+	    }
 
 
-      new_record[new_blocks] = *current_header;
-      new_blocks++;
-      blocks_needed--;
-      blocks_to_keep
-	= (current_stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
-      set_next_block_after (current_header);
-      if (blocks_needed == 0)
-	write_record (1);
+	  /* Found another header.  */
+
+	  if (name = name_scan (current_stat_info.file_name), name)
+	    {
+	      name->found = 1;
+	    flush_file:
+	      set_next_block_after (current_header);
+	      blocks_to_skip = (current_stat_info.stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+
+	      while (record_end - current_block <= blocks_to_skip)
+		{
+		  blocks_to_skip -= (record_end - current_block);
+		  flush_archive ();
+		}
+	      current_block += blocks_to_skip;
+	      blocks_to_skip = 0;
+	      continue;
+	    }
 
 
-      /* Copy data.  */
+	  /* Copy header.  */
 
 
-      kept_blocks_in_record = record_end - current_block;
-      if (kept_blocks_in_record > blocks_to_keep)
-	kept_blocks_in_record = blocks_to_keep;
+	  write_recent_blocks (recent_long_name, recent_long_name_blocks);
+	  write_recent_blocks (recent_long_link, recent_long_link_blocks);
+	  new_record[new_blocks] = *current_header;
+	  new_blocks++;
+	  blocks_to_keep
+	    = (current_stat_info.stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+	  set_next_block_after (current_header);
+	  if (new_blocks == blocking_factor)
+	    write_record (1);
 
 
-      while (blocks_to_keep)
-	{
-	  int count;
+	  /* Copy data.  */
 
 
-	  if (current_block == record_end)
+	  kept_blocks_in_record = record_end - current_block;
+	  if (kept_blocks_in_record > blocks_to_keep)
+	    kept_blocks_in_record = blocks_to_keep;
+
+	  while (blocks_to_keep)
 	    {
 	    {
-	      flush_read ();
-	      records_read++;
-	      current_block = record_start;
-	      kept_blocks_in_record = blocking_factor;
-	      if (kept_blocks_in_record > blocks_to_keep)
-		kept_blocks_in_record = blocks_to_keep;
+	      int count;
+
+	      if (current_block == record_end)
+		{
+		  flush_read ();
+		  current_block = record_start;
+		  kept_blocks_in_record = blocking_factor;
+		  if (kept_blocks_in_record > blocks_to_keep)
+		    kept_blocks_in_record = blocks_to_keep;
+		}
+	      count = kept_blocks_in_record;
+	      if (blocking_factor - new_blocks < count)
+		count = blocking_factor - new_blocks;
+
+	      if (! count)
+		abort ();
+
+	      memcpy (new_record + new_blocks, current_block, count * BLOCKSIZE);
+	      new_blocks += count;
+	      current_block += count;
+	      blocks_to_keep -= count;
+	      kept_blocks_in_record -= count;
+
+	      if (new_blocks == blocking_factor)
+		write_record (1);
 	    }
 	    }
-	  count = kept_blocks_in_record;
-	  if (count > blocks_needed)
-	    count = blocks_needed;
-
-	  memcpy ((void *) (new_record + new_blocks),
-		  (void *) current_block,
-		  (size_t) (count * BLOCKSIZE));
-	  new_blocks += count;
-	  blocks_needed -= count;
-	  current_block += count;
-	  blocks_to_keep -= count;
-	  kept_blocks_in_record -= count;
-
-	  if (blocks_needed == 0)
-	    write_record (1);
 	}
 	}
     }
     }
 
 
-  write_eot ();
+  if (logical_status == HEADER_END_OF_FILE)
+    {
+      /* Write the end of tape.  FIXME: we can't use write_eot here,
+	 as it gets confused when the input is at end of file.  */
+
+      int total_zero_blocks = 0;
+
+      do
+	{
+	  int zero_blocks = blocking_factor - new_blocks;
+	  memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks);
+	  total_zero_blocks += zero_blocks;
+	  write_record (total_zero_blocks < 2);
+	}
+      while (total_zero_blocks < 2);
+    }
+
+  free (new_record);
+
+  if (! acting_as_filter && ! _isrmt (archive))
+    {
+#if MSDOS
+      int status = write (archive, "", 0);
+#else
+      off_t pos = lseek (archive, (off_t) 0, SEEK_CUR);
+      int status = pos < 0 ? -1 : ftruncate (archive, pos);
+#endif
+      if (status != 0)
+	truncate_warn (archive_name_array[0]);
+    }
+
   close_archive ();
   close_archive ();
   names_notfound ();
   names_notfound ();
 }
 }

+ 65 - 60
src/extract.c

@@ -129,14 +129,15 @@ extr_init (void)
 }
 }
 
 
 /* If restoring permissions, restore the mode for FILE_NAME from
 /* If restoring permissions, restore the mode for FILE_NAME from
-   information given in *STAT_INFO (where *CURRENT_STAT_INFO gives
-   the current status if CURRENT_STAT_INFO is nonzero); otherwise invert the
+   information given in *STAT_INFO (where *CUR_INFO gives
+   the current status if CUR_INFO is nonzero); otherwise invert the
    INVERT_PERMISSIONS bits from the file's current permissions.
    INVERT_PERMISSIONS bits from the file's current permissions.
    PERMSTATUS specifies the status of the file's permissions.
    PERMSTATUS specifies the status of the file's permissions.
    TYPEFLAG specifies the type of the file.  */
    TYPEFLAG specifies the type of the file.  */
 static void
 static void
-set_mode (char const *file_name, struct stat const *stat_info,
-	  struct stat const *current_stat_info,
+set_mode (char const *file_name,
+	  struct stat const *stat_info,
+	  struct stat const *cur_info,
 	  mode_t invert_permissions, enum permstatus permstatus,
 	  mode_t invert_permissions, enum permstatus permstatus,
 	  char typeflag)
 	  char typeflag)
 {
 {
@@ -168,16 +169,16 @@ set_mode (char const *file_name, struct stat const *stat_info,
 	 that we created, so there's no point optimizing this code for
 	 that we created, so there's no point optimizing this code for
 	 other cases.  */
 	 other cases.  */
       struct stat st;
       struct stat st;
-      if (! current_stat_info)
+      if (! cur_info)
 	{
 	{
 	  if (stat (file_name, &st) != 0)
 	  if (stat (file_name, &st) != 0)
 	    {
 	    {
 	      stat_error (file_name);
 	      stat_error (file_name);
 	      return;
 	      return;
 	    }
 	    }
-	  current_stat_info = &st;
+	  cur_info = &st;
 	}
 	}
-      mode = current_stat_info->st_mode ^ invert_permissions;
+      mode = cur_info->st_mode ^ invert_permissions;
     }
     }
 
 
   if (chmod (file_name, mode) != 0)
   if (chmod (file_name, mode) != 0)
@@ -199,7 +200,7 @@ check_time (char const *file_name, time_t t)
 
 
 /* Restore stat attributes (owner, group, mode and times) for
 /* Restore stat attributes (owner, group, mode and times) for
    FILE_NAME, using information given in *STAT_INFO.
    FILE_NAME, using information given in *STAT_INFO.
-   If CURRENT_STAT_INFO is nonzero, *CURRENT_STAT_INFO is the
+   If CUR_INFO is nonzero, *CUR_INFO is the
    file's currernt status.
    file's currernt status.
    If not restoring permissions, invert the
    If not restoring permissions, invert the
    INVERT_PERMISSIONS bits from the file's current permissions.
    INVERT_PERMISSIONS bits from the file's current permissions.
@@ -212,8 +213,9 @@ check_time (char const *file_name, time_t t)
    punt for the rest.  Sigh!  */
    punt for the rest.  Sigh!  */
 
 
 static void
 static void
-set_stat (char const *file_name, struct stat const *stat_info,
-	  struct stat const *current_stat_info,
+set_stat (char const *file_name,
+	  struct stat const *stat_info,
+	  struct stat const *cur_info,
 	  mode_t invert_permissions, enum permstatus permstatus,
 	  mode_t invert_permissions, enum permstatus permstatus,
 	  char typeflag)
 	  char typeflag)
 {
 {
@@ -252,7 +254,7 @@ set_stat (char const *file_name, struct stat const *stat_info,
 	 done, it is not possible anymore to change file permissions, so we
 	 done, it is not possible anymore to change file permissions, so we
 	 have to set permissions prior to possibly giving files away.  */
 	 have to set permissions prior to possibly giving files away.  */
 
 
-      set_mode (file_name, stat_info, current_stat_info,
+      set_mode (file_name, stat_info, cur_info,
 		invert_permissions, permstatus, typeflag);
 		invert_permissions, permstatus, typeflag);
     }
     }
 
 
@@ -332,9 +334,9 @@ repair_delayed_set_stat (char const *dir_name,
       if (st.st_dev == dir_stat_info->st_dev
       if (st.st_dev == dir_stat_info->st_dev
 	  && st.st_ino == dir_stat_info->st_ino)
 	  && st.st_ino == dir_stat_info->st_ino)
 	{
 	{
-	  data->stat_info = current_stat;
-	  data->invert_permissions = (MODE_RWX
-				      & (current_stat.st_mode ^ st.st_mode));
+	  data->stat_info = current_stat_info.stat;
+	  data->invert_permissions =
+	    (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode));
 	  data->permstatus = ARCHIVED_PERMSTATUS;
 	  data->permstatus = ARCHIVED_PERMSTATUS;
 	  return;
 	  return;
 	}
 	}
@@ -388,7 +390,7 @@ make_directories (char *file_name)
 	     invert_permissions is zero, because
 	     invert_permissions is zero, because
 	     repair_delayed_set_stat may need to update the struct.  */
 	     repair_delayed_set_stat may need to update the struct.  */
 	  delay_set_stat (file_name,
 	  delay_set_stat (file_name,
-			  &current_stat /* ignored */,
+			  &current_stat_info.stat /* ignored */,
 			  invert_permissions, INTERDIR_PERMSTATUS);
 			  invert_permissions, INTERDIR_PERMSTATUS);
 
 
 	  print_for_mkdir (file_name, cursor - file_name, mode);
 	  print_for_mkdir (file_name, cursor - file_name, mode);
@@ -400,13 +402,16 @@ make_directories (char *file_name)
 
 
       *cursor = '/';
       *cursor = '/';
 
 
-      if (errno == EEXIST
+      if (errno == EEXIST)
+	continue;	        /* Directory already exists.  */
+      else if ((errno == ENOSYS /* Automounted dirs on Solaris return
+				   this. Reported by Warren Hyde
+				   <Warren.Hyde@motorola.com> */
 #if MSDOS
 #if MSDOS
-	  /* Turbo C mkdir gives a funny errno.  */
-	  || errno == EACCES
+	       || errno == EACCES  /* Turbo C mkdir gives a funny errno.  */
 #endif
 #endif
-	  )
-	/* Directory already exists.  */
+	       )
+	       && access (file_name, W_OK) == 0)
 	continue;
 	continue;
 
 
       /* Some other error in the mkdir.  We return to the caller.  */
       /* Some other error in the mkdir.  We return to the caller.  */
@@ -493,7 +498,7 @@ maybe_recoverable (char *file_name, int *interdir_made)
 bool
 bool
 fill_in_sparse_array (void)
 fill_in_sparse_array (void)
 {
 {
-  off_t sparse_data_size = current_stat.st_size;
+  off_t sparse_data_size = current_stat_info.stat.st_size;
   off_t file_size = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
   off_t file_size = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
   int sparses;
   int sparses;
   int counter;
   int counter;
@@ -560,7 +565,8 @@ fill_in_sparse_array (void)
   return 1;
   return 1;
 
 
  invalid_member:
  invalid_member:
-  ERROR ((0, 0, "%s: invalid sparse archive member", current_file_name));
+  ERROR ((0, 0, _("%s: invalid sparse archive member"),
+	  current_stat_info.file_name));
   return 0;
   return 0;
 }
 }
 
 
@@ -640,7 +646,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
       struct delayed_set_stat *data = delayed_set_stat_head;
       struct delayed_set_stat *data = delayed_set_stat_head;
       bool skip_this_one = 0;
       bool skip_this_one = 0;
       struct stat st;
       struct stat st;
-      struct stat const *current_stat_info = 0;
+      struct stat const *cur_info = 0;
 
 
       check_for_renamed_directories |= data->after_symlinks;
       check_for_renamed_directories |= data->after_symlinks;
 
 
@@ -654,7 +660,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
 
 
       if (check_for_renamed_directories)
       if (check_for_renamed_directories)
 	{
 	{
-	  current_stat_info = &st;
+	  cur_info = &st;
 	  if (stat (data->file_name, &st) != 0)
 	  if (stat (data->file_name, &st) != 0)
 	    {
 	    {
 	      stat_error (data->file_name);
 	      stat_error (data->file_name);
@@ -671,7 +677,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
 	}
 	}
 
 
       if (! skip_this_one)
       if (! skip_this_one)
-	set_stat (data->file_name, &data->stat_info, current_stat_info,
+	set_stat (data->file_name, &data->stat_info, cur_info,
 		  data->invert_permissions, data->permstatus, DIRTYPE);
 		  data->invert_permissions, data->permstatus, DIRTYPE);
 
 
       delayed_set_stat_head = data->next;
       delayed_set_stat_head = data->next;
@@ -694,13 +700,12 @@ extract_archive (void)
   off_t file_size;
   off_t file_size;
   int interdir_made = 0;
   int interdir_made = 0;
   char typeflag;
   char typeflag;
-  union block *exhdr;
   char *file_name;
   char *file_name;
 
 
   set_next_block_after (current_header);
   set_next_block_after (current_header);
-  decode_header (current_header, &current_stat, &current_format, 1);
+  decode_header (current_header, &current_stat_info, &current_format, 1);
 
 
-  if (interactive_option && !confirm ("extract", current_file_name))
+  if (interactive_option && !confirm ("extract", current_stat_info.file_name))
     {
     {
       skip_member ();
       skip_member ();
       return;
       return;
@@ -711,7 +716,7 @@ extract_archive (void)
   if (verbose_option)
   if (verbose_option)
     print_header (-1);
     print_header (-1);
 
 
-  file_name = safer_name_suffix (current_file_name, 0);
+  file_name = safer_name_suffix (current_stat_info.file_name, 0);
 
 
   apply_nonancestor_delayed_set_stat (file_name, 0);
   apply_nonancestor_delayed_set_stat (file_name, 0);
 
 
@@ -745,7 +750,7 @@ extract_archive (void)
       /* Appears to be a file.  But BSD tar uses the convention that a slash
       /* Appears to be a file.  But BSD tar uses the convention that a slash
 	 suffix means a directory.  */
 	 suffix means a directory.  */
 
 
-      if (current_trailing_slash)
+      if (current_stat_info.had_trailing_slash)
 	goto really_dir;
 	goto really_dir;
 
 
       /* FIXME: deal with protection issues.  */
       /* FIXME: deal with protection issues.  */
@@ -755,7 +760,7 @@ extract_archive (void)
 		  | (old_files_option == OVERWRITE_OLD_FILES
 		  | (old_files_option == OVERWRITE_OLD_FILES
 		     ? O_TRUNC
 		     ? O_TRUNC
 		     : O_EXCL));
 		     : O_EXCL));
-      mode = current_stat.st_mode & MODE_RWX & ~ current_umask;
+      mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
 
 
       if (to_stdout_option)
       if (to_stdout_option)
 	{
 	{
@@ -776,7 +781,7 @@ extract_archive (void)
 	 the open call that creates them.  */
 	 the open call that creates them.  */
 
 
       if (typeflag == CONTTYPE)
       if (typeflag == CONTTYPE)
-	fd = open (file_name, openflag | O_CTG, mode, current_stat.st_size);
+	fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size);
       else
       else
 	fd = open (file_name, openflag, mode);
 	fd = open (file_name, openflag, mode);
 
 
@@ -823,16 +828,16 @@ extract_archive (void)
 	  name = xmalloc (name_length_bis);
 	  name = xmalloc (name_length_bis);
 	  memcpy (name, file_name, name_length_bis);
 	  memcpy (name, file_name, name_length_bis);
 	  size = extract_sparse_file (fd, name,
 	  size = extract_sparse_file (fd, name,
-				      current_stat.st_size, file_size);
+				      current_stat_info.stat.st_size, file_size);
 	  free (sparsearray);
 	  free (sparsearray);
 	}
 	}
       else
       else
-	for (size = current_stat.st_size; size > 0; )
+	for (size = current_stat_info.stat.st_size; size > 0; )
 	  {
 	  {
 	    if (multi_volume_option)
 	    if (multi_volume_option)
 	      {
 	      {
-		assign_string (&save_name, current_file_name);
-		save_totsize = current_stat.st_size;
+		assign_string (&save_name, current_stat_info.file_name);
+		save_totsize = current_stat_info.stat.st_size;
 		save_sizeleft = size;
 		save_sizeleft = size;
 	      }
 	      }
 
 
@@ -883,7 +888,7 @@ extract_archive (void)
 	    undo_last_backup ();
 	    undo_last_backup ();
 	}
 	}
 
 
-      set_stat (file_name, &current_stat, 0, 0,
+      set_stat (file_name, &current_stat_info.stat, 0, 0,
 		(old_files_option == OVERWRITE_OLD_FILES
 		(old_files_option == OVERWRITE_OLD_FILES
 		 ? UNKNOWN_PERMSTATUS
 		 ? UNKNOWN_PERMSTATUS
 		 : ARCHIVED_PERMSTATUS),
 		 : ARCHIVED_PERMSTATUS),
@@ -896,19 +901,19 @@ extract_archive (void)
 	break;
 	break;
 
 
       if (absolute_names_option
       if (absolute_names_option
-	  || ! (ISSLASH (current_link_name
-			 [FILESYSTEM_PREFIX_LEN (current_link_name)])
-		|| contains_dot_dot (current_link_name)))
+	  || ! (ISSLASH (current_stat_info.link_name
+			 [FILESYSTEM_PREFIX_LEN (current_stat_info.link_name)])
+		|| contains_dot_dot (current_stat_info.link_name)))
 	{
 	{
-	  while (status = symlink (current_link_name, file_name),
+	  while (status = symlink (current_stat_info.link_name, file_name),
 		 status != 0)
 		 status != 0)
 	    if (!maybe_recoverable (file_name, &interdir_made))
 	    if (!maybe_recoverable (file_name, &interdir_made))
 	      break;
 	      break;
 
 
 	  if (status == 0)
 	  if (status == 0)
-	    set_stat (file_name, &current_stat, 0, 0, 0, SYMTYPE);
+	    set_stat (file_name, &current_stat_info.stat, 0, 0, 0, SYMTYPE);
 	  else
 	  else
-	    symlink_error (current_link_name, file_name);
+	    symlink_error (current_stat_info.link_name, file_name);
 	}
 	}
       else
       else
 	{
 	{
@@ -937,19 +942,19 @@ extract_archive (void)
 	      struct delayed_set_stat *h;
 	      struct delayed_set_stat *h;
 	      struct delayed_symlink *p =
 	      struct delayed_symlink *p =
 		xmalloc (offsetof (struct delayed_symlink, target)
 		xmalloc (offsetof (struct delayed_symlink, target)
-			 + strlen (current_link_name) + 1);
+			 + strlen (current_stat_info.link_name) + 1);
 	      p->next = delayed_symlink_head;
 	      p->next = delayed_symlink_head;
 	      delayed_symlink_head = p;
 	      delayed_symlink_head = p;
 	      p->dev = st.st_dev;
 	      p->dev = st.st_dev;
 	      p->ino = st.st_ino;
 	      p->ino = st.st_ino;
 	      p->mtime = st.st_mtime;
 	      p->mtime = st.st_mtime;
-	      p->uid = current_stat.st_uid;
-	      p->gid = current_stat.st_gid;
+	      p->uid = current_stat_info.stat.st_uid;
+	      p->gid = current_stat_info.stat.st_gid;
 	      p->sources = xmalloc (offsetof (struct string_list, string)
 	      p->sources = xmalloc (offsetof (struct string_list, string)
 				    + strlen (file_name) + 1);
 				    + strlen (file_name) + 1);
 	      p->sources->next = 0;
 	      p->sources->next = 0;
 	      strcpy (p->sources->string, file_name);
 	      strcpy (p->sources->string, file_name);
-	      strcpy (p->target, current_link_name);
+	      strcpy (p->target, current_stat_info.link_name);
 
 
 	      h = delayed_set_stat_head;
 	      h = delayed_set_stat_head;
 	      if (h && ! h->after_symlinks
 	      if (h && ! h->after_symlinks
@@ -1002,7 +1007,7 @@ extract_archive (void)
 
 
     again_link:
     again_link:
       {
       {
-	char const *link_name = safer_name_suffix (current_link_name, 1);
+	char const *link_name = safer_name_suffix (current_stat_info.link_name, 1);
 	struct stat st1, st2;
 	struct stat st1, st2;
 	int e;
 	int e;
 
 
@@ -1049,13 +1054,13 @@ extract_archive (void)
 
 
 #if S_IFCHR
 #if S_IFCHR
     case CHRTYPE:
     case CHRTYPE:
-      current_stat.st_mode |= S_IFCHR;
+      current_stat_info.stat.st_mode |= S_IFCHR;
       goto make_node;
       goto make_node;
 #endif
 #endif
 
 
 #if S_IFBLK
 #if S_IFBLK
     case BLKTYPE:
     case BLKTYPE:
-      current_stat.st_mode |= S_IFBLK;
+      current_stat_info.stat.st_mode |= S_IFBLK;
 #endif
 #endif
 
 
 #if S_IFCHR || S_IFBLK
 #if S_IFCHR || S_IFBLK
@@ -1063,8 +1068,8 @@ extract_archive (void)
       if (! prepare_to_extract (file_name, 0))
       if (! prepare_to_extract (file_name, 0))
 	break;
 	break;
 
 
-      status = mknod (file_name, current_stat.st_mode,
-		      current_stat.st_rdev);
+      status = mknod (file_name, current_stat_info.stat.st_mode,
+		      current_stat_info.stat.st_rdev);
       if (status != 0)
       if (status != 0)
 	{
 	{
 	  if (maybe_recoverable (file_name, &interdir_made))
 	  if (maybe_recoverable (file_name, &interdir_made))
@@ -1074,7 +1079,7 @@ extract_archive (void)
 	    undo_last_backup ();
 	    undo_last_backup ();
 	  break;
 	  break;
 	};
 	};
-      set_stat (file_name, &current_stat, 0, 0,
+      set_stat (file_name, &current_stat_info.stat, 0, 0,
 		ARCHIVED_PERMSTATUS, typeflag);
 		ARCHIVED_PERMSTATUS, typeflag);
       break;
       break;
 #endif
 #endif
@@ -1084,13 +1089,13 @@ extract_archive (void)
       if (! prepare_to_extract (file_name, 0))
       if (! prepare_to_extract (file_name, 0))
 	break;
 	break;
 
 
-      while (status = mkfifo (file_name, current_stat.st_mode),
+      while (status = mkfifo (file_name, current_stat_info.stat.st_mode),
 	     status != 0)
 	     status != 0)
 	if (!maybe_recoverable (file_name, &interdir_made))
 	if (!maybe_recoverable (file_name, &interdir_made))
 	  break;
 	  break;
 
 
       if (status == 0)
       if (status == 0)
-	set_stat (file_name, &current_stat, 0, 0,
+	set_stat (file_name, &current_stat_info.stat, NULL, 0,
 		  ARCHIVED_PERMSTATUS, typeflag);
 		  ARCHIVED_PERMSTATUS, typeflag);
       else
       else
 	{
 	{
@@ -1114,7 +1119,7 @@ extract_archive (void)
       else if (typeflag == GNUTYPE_DUMPDIR)
       else if (typeflag == GNUTYPE_DUMPDIR)
 	skip_member ();
 	skip_member ();
 
 
-      mode = ((current_stat.st_mode
+      mode = ((current_stat_info.stat.st_mode
 	       | (we_are_root ? 0 : MODE_WXUSR))
 	       | (we_are_root ? 0 : MODE_WXUSR))
 	      & MODE_RWX);
 	      & MODE_RWX);
 
 
@@ -1167,8 +1172,8 @@ extract_archive (void)
       if (status == 0
       if (status == 0
 	  || old_files_option == DEFAULT_OLD_FILES
 	  || old_files_option == DEFAULT_OLD_FILES
 	  || old_files_option == OVERWRITE_OLD_FILES)
 	  || old_files_option == OVERWRITE_OLD_FILES)
-	delay_set_stat (file_name, &current_stat,
-			MODE_RWX & (mode ^ current_stat.st_mode),
+	delay_set_stat (file_name, &current_stat_info.stat,
+			MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
 			(status == 0
 			(status == 0
 			 ? ARCHIVED_PERMSTATUS
 			 ? ARCHIVED_PERMSTATUS
 			 : UNKNOWN_PERMSTATUS));
 			 : UNKNOWN_PERMSTATUS));
@@ -1176,7 +1181,7 @@ extract_archive (void)
 
 
     case GNUTYPE_VOLHDR:
     case GNUTYPE_VOLHDR:
       if (verbose_option)
       if (verbose_option)
-	fprintf (stdlis, _("Reading %s\n"), quote (current_file_name));
+	fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name));
       break;
       break;
 
 
     case GNUTYPE_NAMES:
     case GNUTYPE_NAMES:
@@ -1186,7 +1191,7 @@ extract_archive (void)
     case GNUTYPE_MULTIVOL:
     case GNUTYPE_MULTIVOL:
       ERROR ((0, 0,
       ERROR ((0, 0,
 	      _("%s: Cannot extract -- file is continued from another volume"),
 	      _("%s: Cannot extract -- file is continued from another volume"),
-	      quotearg_colon (current_file_name)));
+	      quotearg_colon (current_stat_info.file_name)));
       skip_member ();
       skip_member ();
       if (backup_option)
       if (backup_option)
 	undo_last_backup ();
 	undo_last_backup ();

+ 344 - 473
src/incremen.c

@@ -1,5 +1,7 @@
 /* GNU dump extensions to tar.
 /* GNU dump extensions to tar.
-   Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc.
+
+   Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+   2003 Free Software Foundation, Inc.
 
 
    This program is free software; you can redistribute it and/or modify it
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
    under the terms of the GNU General Public License as published by the
@@ -13,60 +15,47 @@
 
 
    You should have received a copy of the GNU General Public License along
    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    with this program; if not, write to the Free Software Foundation, Inc.,
-   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 
 #include "system.h"
 #include "system.h"
-
-#include <time.h>
-time_t time ();
-
-#define ISDIGIT(Char) (ISASCII (Char) && isdigit (Char))
-#define ISSPACE(Char) (ISASCII (Char) && isspace (Char))
-
+#include <getline.h>
+#include <hash.h>
+#include <quotearg.h>
 #include "common.h"
 #include "common.h"
 
 
 /* Variable sized generic character buffers.  */
 /* Variable sized generic character buffers.  */
 
 
 struct accumulator
 struct accumulator
 {
 {
-  int allocated;
-  int length;
+  size_t allocated;
+  size_t length;
   char *pointer;
   char *pointer;
 };
 };
 
 
 /* Amount of space guaranteed just after a reallocation.  */
 /* Amount of space guaranteed just after a reallocation.  */
 #define ACCUMULATOR_SLACK 50
 #define ACCUMULATOR_SLACK 50
 
 
-/*---------------------------------------------------------.
-| Return the accumulated data from an ACCUMULATOR buffer.  |
-`---------------------------------------------------------*/
-
+/* Return the accumulated data from an ACCUMULATOR buffer.  */
 static char *
 static char *
 get_accumulator (struct accumulator *accumulator)
 get_accumulator (struct accumulator *accumulator)
 {
 {
   return accumulator->pointer;
   return accumulator->pointer;
 }
 }
 
 
-/*-----------------------------------------------.
-| Allocate and return a new accumulator buffer.	 |
-`-----------------------------------------------*/
-
+/* Allocate and return a new accumulator buffer.  */
 static struct accumulator *
 static struct accumulator *
 new_accumulator (void)
 new_accumulator (void)
 {
 {
   struct accumulator *accumulator
   struct accumulator *accumulator
-    = (struct accumulator *) xmalloc (sizeof (struct accumulator));
+    = xmalloc (sizeof (struct accumulator));
 
 
   accumulator->allocated = ACCUMULATOR_SLACK;
   accumulator->allocated = ACCUMULATOR_SLACK;
-  accumulator->pointer = (char *) xmalloc (ACCUMULATOR_SLACK);
+  accumulator->pointer = xmalloc (ACCUMULATOR_SLACK);
   accumulator->length = 0;
   accumulator->length = 0;
   return accumulator;
   return accumulator;
 }
 }
 
 
-/*-----------------------------------.
-| Deallocate an ACCUMULATOR buffer.  |
-`-----------------------------------*/
-
+/* Deallocate an ACCUMULATOR buffer.  */
 static void
 static void
 delete_accumulator (struct accumulator *accumulator)
 delete_accumulator (struct accumulator *accumulator)
 {
 {
@@ -74,236 +63,262 @@ delete_accumulator (struct accumulator *accumulator)
   free (accumulator);
   free (accumulator);
 }
 }
 
 
-/*----------------------------------------------------------------------.
-| At the end of an ACCUMULATOR buffer, add a DATA block of SIZE bytes.  |
-`----------------------------------------------------------------------*/
-
+/* At the end of an ACCUMULATOR buffer, add a DATA block of SIZE bytes.  */
 static void
 static void
 add_to_accumulator (struct accumulator *accumulator,
 add_to_accumulator (struct accumulator *accumulator,
-		    const char *data, int size)
+		    const char *data, size_t size)
 {
 {
   if (accumulator->length + size > accumulator->allocated)
   if (accumulator->length + size > accumulator->allocated)
     {
     {
       accumulator->allocated = accumulator->length + size + ACCUMULATOR_SLACK;
       accumulator->allocated = accumulator->length + size + ACCUMULATOR_SLACK;
-      accumulator->pointer = (char *)
-	xrealloc (accumulator->pointer, (size_t) accumulator->allocated);
+      accumulator->pointer =
+	xrealloc (accumulator->pointer, accumulator->allocated);
     }
     }
-  memcpy (accumulator->pointer + accumulator->length, data, (size_t) size);
+  memcpy (accumulator->pointer + accumulator->length, data, size);
   accumulator->length += size;
   accumulator->length += size;
 }
 }
 
 
 /* Incremental dump specialities.  */
 /* Incremental dump specialities.  */
 
 
-/* Current time.  */
-static time_t time_now;
+/* Which child files to save under a directory.  */
+enum children {NO_CHILDREN, CHANGED_CHILDREN, ALL_CHILDREN};
 
 
-/* List of directory names.  */
+/* Directory attributes.  */
 struct directory
 struct directory
   {
   {
-    struct directory *next;	/* next entry in list */
-    const char *name;		/* path name of directory */
-    int device_number;		/* device number for directory */
-    int inode_number;		/* inode number for directory */
-    int allnew;
-    const char *dir_text;
+    dev_t device_number;	/* device number for directory */
+    ino_t inode_number;		/* inode number for directory */
+    enum children children;
+    bool nfs;
+    bool found;
+    char name[1];		/* path name of directory */
   };
   };
-static struct directory *directory_list = NULL;
 
 
-/*-------------------------------------------------------------------.
-| Create and link a new directory entry for directory NAME, having a |
-| DEVICE_NUMBER and a INODE_NUMBER, with some TEXT.		     |
-`-------------------------------------------------------------------*/
+static Hash_table *directory_table;
 
 
-static void
-note_directory (char *name, dev_t device_number, ino_t inode_number,
-		const char *text)
-{
-  struct directory *directory
-    = (struct directory *) xmalloc (sizeof (struct directory));
+#if HAVE_ST_FSTYPE_STRING
+  static char const nfs_string[] = "nfs";
+# define NFS_FILE_STAT(st) (strcmp ((st).st_fstype, nfs_string) == 0)
+#else
+# define ST_DEV_MSB(st) (~ (dev_t) 0 << (sizeof (st).st_dev * CHAR_BIT - 1))
+# define NFS_FILE_STAT(st) (((st).st_dev & ST_DEV_MSB (st)) != 0)
+#endif
 
 
-  directory->next = directory_list;
-  directory_list = directory;
+/* Calculate the hash of a directory.  */
+static unsigned
+hash_directory (void const *entry, unsigned n_buckets)
+{
+  struct directory const *directory = entry;
+  return hash_string (directory->name, n_buckets);
+}
 
 
-  directory->device_number = device_number;
-  directory->inode_number = inode_number;
-  directory->name = xstrdup (name);
-  directory->dir_text = text;
-  directory->allnew = 0;
+/* Compare two directories for equality.  */
+static bool
+compare_directories (void const *entry1, void const *entry2)
+{
+  struct directory const *directory1 = entry1;
+  struct directory const *directory2 = entry2;
+  return strcmp (directory1->name, directory2->name) == 0;
 }
 }
 
 
-/*------------------------------------------------------------------------.
-| Return a directory entry for a given path NAME, or NULL if none found.  |
-`------------------------------------------------------------------------*/
+/* Create and link a new directory entry for directory NAME, having a
+   device number DEV and an inode number INO, with NFS indicating
+   whether it is an NFS device and FOUND indicating whether we have
+   found that the directory exists.  */
+static struct directory *
+note_directory (char const *name, dev_t dev, ino_t ino, bool nfs, bool found)
+{
+  size_t size = offsetof (struct directory, name) + strlen (name) + 1;
+  struct directory *directory = xmalloc (size);
+
+  directory->device_number = dev;
+  directory->inode_number = ino;
+  directory->children = CHANGED_CHILDREN;
+  directory->nfs = nfs;
+  directory->found = found;
+  strcpy (directory->name, name);
+
+  if (! ((directory_table
+	  || (directory_table = hash_initialize (0, 0, hash_directory,
+						 compare_directories, 0)))
+	 && hash_insert (directory_table, directory)))
+    xalloc_die ();
+
+  return directory;
+}
 
 
+/* Return a directory entry for a given path NAME, or zero if none found.  */
 static struct directory *
 static struct directory *
 find_directory (char *name)
 find_directory (char *name)
 {
 {
-  struct directory *directory;
-
-  for (directory = directory_list;
-       directory;
-       directory = directory->next)
+  if (! directory_table)
+    return 0;
+  else
     {
     {
-      if (!strcmp (directory->name, name))
-	return directory;
+      size_t size = offsetof (struct directory, name) + strlen (name) + 1;
+      struct directory *dir = alloca (size);
+      strcpy (dir->name, name);
+      return hash_lookup (directory_table, dir);
     }
     }
-  return NULL;
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
-
 static int
 static int
-compare_dirents (const voidstar first, const voidstar second)
+compare_dirents (const void *first, const void *second)
 {
 {
   return strcmp ((*(char *const *) first) + 1,
   return strcmp ((*(char *const *) first) + 1,
 		 (*(char *const *) second) + 1);
 		 (*(char *const *) second) + 1);
 }
 }
 
 
-/*---.
-| ?  |
-`---*/
-
 char *
 char *
-get_directory_contents (char *path, int device)
+get_directory_contents (char *path, dev_t device)
 {
 {
   struct accumulator *accumulator;
   struct accumulator *accumulator;
 
 
   /* Recursively scan the given PATH.  */
   /* Recursively scan the given PATH.  */
 
 
   {
   {
-    DIR *dirp = opendir (path);	/* for scanning directory */
-    struct dirent *entry;	/* directory entry being scanned */
+    char *dirp = savedir (path);	/* for scanning directory */
+    char const *entry;	/* directory entry being scanned */
+    size_t entrylen;	/* length of directory entry */
     char *name_buffer;		/* directory, `/', and directory member */
     char *name_buffer;		/* directory, `/', and directory member */
-    int name_buffer_size;	/* allocated size of name_buffer, minus 2 */
-    int name_length;		/* used length in name_buffer */
+    size_t name_buffer_size;	/* allocated size of name_buffer, minus 2 */
+    size_t name_length;		/* used length in name_buffer */
     struct directory *directory; /* for checking if already already seen */
     struct directory *directory; /* for checking if already already seen */
-    int all_children;
+    enum children children;
 
 
-    if (dirp == NULL)
+    if (! dirp)
       {
       {
-	ERROR ((0, errno, _("Cannot open directory %s"), path));
-	return NULL;
+	if (ignore_failed_read_option)
+	  savedir_warn (path);
+	else
+	  savedir_error (path);
       }
       }
-    errno = 0;			/* FIXME: errno should be read-only */
+    errno = 0;
 
 
     name_buffer_size = strlen (path) + NAME_FIELD_SIZE;
     name_buffer_size = strlen (path) + NAME_FIELD_SIZE;
-    name_buffer = xmalloc ((size_t) (name_buffer_size + 2));
+    name_buffer = xmalloc (name_buffer_size + 2);
     strcpy (name_buffer, path);
     strcpy (name_buffer, path);
-    if (path[strlen (path) - 1] != '/')
+    if (! ISSLASH (path[strlen (path) - 1]))
       strcat (name_buffer, "/");
       strcat (name_buffer, "/");
     name_length = strlen (name_buffer);
     name_length = strlen (name_buffer);
 
 
     directory = find_directory (path);
     directory = find_directory (path);
-    all_children = directory ? directory->allnew : 0;
+    children = directory ? directory->children : CHANGED_CHILDREN;
 
 
     accumulator = new_accumulator ();
     accumulator = new_accumulator ();
 
 
-    while (entry = readdir (dirp), entry)
-      {
-	struct stat stat_data;
+    if (dirp && children != NO_CHILDREN)
+      for (entry = dirp;
+	   (entrylen = strlen (entry)) != 0;
+	   entry += entrylen + 1)
+	{
+	  if (name_buffer_size <= entrylen + name_length)
+	    {
+	      do
+		name_buffer_size += NAME_FIELD_SIZE;
+	      while (name_buffer_size <= entrylen + name_length);
+	      name_buffer = xrealloc (name_buffer, name_buffer_size + 2);
+	    }
+	  strcpy (name_buffer + name_length, entry);
 
 
-	/* Skip `.' and `..'.  */
+	  if (excluded_name (name_buffer))
+	    add_to_accumulator (accumulator, "N", 1);
+	  else
+	    {
+	      struct stat stat_data;
 
 
-	if (is_dot_or_dotdot (entry->d_name))
-	  continue;
+	      if (deref_stat (dereference_option, name_buffer, &stat_data))
+		{
+		  if (ignore_failed_read_option)
+		    stat_warn (name_buffer);
+		  else
+		    stat_error (name_buffer);
+		  continue;
+		}
 
 
-	if ((int) NAMLEN (entry) + name_length >= name_buffer_size)
-	  {
-	    while ((int) NAMLEN (entry) + name_length >= name_buffer_size)
-	      name_buffer_size += NAME_FIELD_SIZE;
-	    name_buffer = (char *)
-	      xrealloc (name_buffer, (size_t) (name_buffer_size + 2));
-	  }
-	strcpy (name_buffer + name_length, entry->d_name);
-
-	if (dereference_option
-#ifdef AIX
-	    ? statx (name_buffer, &stat_data, STATSIZE, STX_HIDDEN)
-	    : statx (name_buffer, &stat_data, STATSIZE, STX_HIDDEN | STX_LINK)
-#else
-	    ? stat (name_buffer, &stat_data)
-	    : lstat (name_buffer, &stat_data)
-#endif
-	    )
-	  {
-	    ERROR ((0, errno, _("Cannot stat %s"), name_buffer));
-	    continue;
-	  }
-
-	if ((one_file_system_option && device != stat_data.st_dev)
-	    || (exclude_option && check_exclude (name_buffer)))
-	  add_to_accumulator (accumulator, "N", 1);
-
-#ifdef AIX
-	else if (S_ISHIDDEN (stat_data.st_mode))
-	  {
-	    add_to_accumulator (accumulator, "D", 1);
-	    strcat (entry->d_name, "A");
-	    entry->d_namlen++;
-	  }
+	      if (S_ISDIR (stat_data.st_mode))
+		{
+		  bool nfs = NFS_FILE_STAT (stat_data);
+
+		  if (directory = find_directory (name_buffer), directory)
+		    {
+		      /* With NFS, the same file can have two different devices
+			 if an NFS directory is mounted in multiple locations,
+			 which is relatively common when automounting.
+			 To avoid spurious incremental redumping of
+			 directories, consider all NFS devices as equal,
+			 relying on the i-node to establish differences.  */
+
+		      if (! (((directory->nfs & nfs)
+			      || directory->device_number == stat_data.st_dev)
+			     && directory->inode_number == stat_data.st_ino))
+			{
+			  if (verbose_option)
+			    WARN ((0, 0, _("%s: Directory has been renamed"),
+				   quotearg_colon (name_buffer)));
+			  directory->children = ALL_CHILDREN;
+			  directory->nfs = nfs;
+			  directory->device_number = stat_data.st_dev;
+			  directory->inode_number = stat_data.st_ino;
+			}
+		      directory->found = 1;
+		    }
+		  else
+		    {
+		      if (verbose_option)
+			WARN ((0, 0, _("%s: Directory is new"),
+			       quotearg_colon (name_buffer)));
+		      directory = note_directory (name_buffer,
+						  stat_data.st_dev,
+						  stat_data.st_ino, nfs, 1);
+		      directory->children =
+			((listed_incremental_option
+			  || newer_mtime_option <= stat_data.st_mtime
+			  || (after_date_option &&
+			      newer_ctime_option <= stat_data.st_ctime))
+			 ? ALL_CHILDREN
+			 : CHANGED_CHILDREN);
+		    }
+
+		  if (one_file_system_option && device != stat_data.st_dev)
+		    directory->children = NO_CHILDREN;
+		  else if (children == ALL_CHILDREN)
+		    directory->children = ALL_CHILDREN;
+
+		  add_to_accumulator (accumulator, "D", 1);
+		}
+
+	      else if (one_file_system_option && device != stat_data.st_dev)
+		add_to_accumulator (accumulator, "N", 1);
+
+#ifdef S_ISHIDDEN
+	      else if (S_ISHIDDEN (stat_data.st_mode))
+		{
+		  add_to_accumulator (accumulator, "D", 1);
+		  add_to_accumulator (accumulator, entry, entrylen);
+		  add_to_accumulator (accumulator, "A", 2);
+		  continue;
+		}
 #endif
 #endif
 
 
-	else if (S_ISDIR (stat_data.st_mode))
-	  {
-	    if (directory = find_directory (name_buffer), directory)
-	      {
-		/* Devices having the high bit set are NFS devices, which are
-		   attributed somewhat randomly in automounting situations.
-		   For avoiding spurious incremental redumping of directories,
-		   we have to plainly consider all NFS devices as equal,
-		   relying on the i-node only to establish differences.  */
-
-		/* FIXME: Göran Uddeborg <goeran@uddeborg.pp.se> says, on
-		   1996-09-20, that SunOS 5/Solaris 2 uses unsigned long for
-		   the device number type.  */
-
-		if ((((short) directory->device_number >= 0
-		      || (short) stat_data.st_dev >= 0)
-		     && directory->device_number != stat_data.st_dev)
-		    || directory->inode_number != stat_data.st_ino)
-		  {
-		    if (verbose_option)
-		      WARN ((0, 0, _("Directory %s has been renamed"),
-			     name_buffer));
-		    directory->allnew = 1;
-		    directory->device_number = stat_data.st_dev;
-		    directory->inode_number = stat_data.st_ino;
-		  }
-		directory->dir_text = "";
-	      }
-	    else
-	      {
-		if (verbose_option)
-		  WARN ((0, 0, _("Directory %s is new"), name_buffer));
-		note_directory (name_buffer, stat_data.st_dev, stat_data.st_ino,
-				"");
-		directory = find_directory (name_buffer);
-		directory->allnew = 1;
-	      }
-	    if (all_children && directory)
-	      directory->allnew = 1;
-
-	    add_to_accumulator (accumulator, "D", 1);
-	  }
+	      else
+		if (children == CHANGED_CHILDREN
+		    && stat_data.st_mtime < newer_mtime_option
+		    && (!after_date_option
+			|| stat_data.st_ctime < newer_ctime_option))
+		  add_to_accumulator (accumulator, "N", 1);
+		else
+		  add_to_accumulator (accumulator, "Y", 1);
+	    }
 
 
-	else
-	  if (!all_children
-	      && stat_data.st_mtime < newer_mtime_option
-	      && (!after_date_option
-		  || stat_data.st_ctime < newer_ctime_option))
-	    add_to_accumulator (accumulator, "N", 1);
-	  else
-	    add_to_accumulator (accumulator, "Y", 1);
+	  add_to_accumulator (accumulator, entry, entrylen + 1);
+	}
 
 
-	add_to_accumulator (accumulator,
-			    entry->d_name, (int) (NAMLEN (entry) + 1));
-      }
     add_to_accumulator (accumulator, "\000\000", 2);
     add_to_accumulator (accumulator, "\000\000", 2);
 
 
     free (name_buffer);
     free (name_buffer);
-    closedir (dirp);
+    if (dirp)
+      free (dirp);
   }
   }
 
 
   /* Sort the contents of the directory, now that we have it all.  */
   /* Sort the contents of the directory, now that we have it all.  */
@@ -320,22 +335,22 @@ get_directory_contents (char *path, int device)
     for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
     for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
       counter++;
       counter++;
 
 
-    if (counter == 0)
+    if (! counter)
       {
       {
 	delete_accumulator (accumulator);
 	delete_accumulator (accumulator);
-	return NULL;
+	return 0;
       }
       }
 
 
-    array = (char **) xmalloc (sizeof (char *) * (counter + 1));
+    array = xmalloc (sizeof (char *) * (counter + 1));
 
 
     array_cursor = array;
     array_cursor = array;
     for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
     for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
       *array_cursor++ = cursor;
       *array_cursor++ = cursor;
-    *array_cursor = NULL;
+    *array_cursor = 0;
 
 
-    qsort ((voidstar) array, counter, sizeof (char *), compare_dirents);
+    qsort (array, counter, sizeof (char *), compare_dirents);
 
 
-    buffer = (char *) xmalloc ((size_t) (cursor - pointer + 2));
+    buffer = xmalloc (cursor - pointer + 2);
 
 
     cursor = buffer;
     cursor = buffer;
     for (array_cursor = array; *array_cursor; array_cursor++)
     for (array_cursor = array; *array_cursor; array_cursor++)
@@ -352,322 +367,178 @@ get_directory_contents (char *path, int device)
     return buffer;
     return buffer;
   }
   }
 }
 }
-
-/*----------------------------------------------------------------------.
-| Add all the files in PATH, which is a directory, to the namelist.  If |
-| any of the files is a directory, recurse on the subdirectory.	        |
-`----------------------------------------------------------------------*/
-
-static void
-add_hierarchy_to_namelist (char *path, int device)
-{
-  char *buffer = get_directory_contents (path, device);
-
-  {
-    struct name *name;
-
-    for (name = namelist; name; name = name->next)
-      if (strcmp (name->name, path) == 0)
-	  break;
-    if (name)
-      name->dir_contents = buffer ? buffer : "\0\0\0\0";
-  }
-
-  if (buffer)
-    {
-      int name_length = strlen (path);
-      int allocated_length = (name_length >= NAME_FIELD_SIZE
-			      ? name_length + NAME_FIELD_SIZE
-			      : NAME_FIELD_SIZE);
-      char *name_buffer = xmalloc ((size_t) (allocated_length + 1));
-				/* FIXME: + 2 above?  */
-      char *string;
-      int string_length;
-
-      strcpy (name_buffer, path);
-      if (name_buffer[name_length - 1] != '/')
-	{
-	  name_buffer[name_length++] = '/';
-	  name_buffer[name_length] = '\0';
-	}
-
-      for (string = buffer; *string; string += string_length + 1)
-	{
-	  string_length = strlen (string);
-	  if (*string == 'D')
-	    {
-	      if (name_length + string_length >= allocated_length)
-		{
-		  while (name_length + string_length >= allocated_length)
-		    allocated_length += NAME_FIELD_SIZE;
-		  name_buffer = (char *)
-		    xrealloc (name_buffer, (size_t) (allocated_length + 1));
-		}
-	      strcpy (name_buffer + name_length, string + 1);
-	      addname (name_buffer);
-	      add_hierarchy_to_namelist (name_buffer, device);
-	    }
-	}
-
-      free (name_buffer);
-    }
-}
 
 
-/*---.
-| ?  |
-`---*/
+static FILE *listed_incremental_stream;
 
 
-static void
+void
 read_directory_file (void)
 read_directory_file (void)
 {
 {
-  dev_t device_number;
-  ino_t inode_number;
-  char *strp;
+  int fd;
   FILE *fp;
   FILE *fp;
-  char buf[512];
-  static char *path = NULL;
-
-  if (path == NULL)
-    path = xmalloc (PATH_MAX);
-  time (&time_now);
-  if (listed_incremental_option[0] != '/')
-    {
-#if HAVE_GETCWD
-      if (!getcwd (path, PATH_MAX))
-	FATAL_ERROR ((0, 0, _("Could not get current directory")));
-#else
-      char *getwd ();
-
-      if (!getwd (path))
-	FATAL_ERROR ((0, 0, _("Could not get current directory: %s"), path));
-#endif
-
-      if (strlen (path) + 1 + strlen (listed_incremental_option) + 1 > PATH_MAX)
-	ERROR ((TAREXIT_FAILURE, 0, _("File name %s/%s too long"),
-		path, listed_incremental_option));
-
-      strcat (path, "/");
-      strcat (path, listed_incremental_option);
-      listed_incremental_option = path;
-    }
-  fp = fopen (listed_incremental_option, "r");
-  if (fp == 0 && errno != ENOENT)
+  char *buf = 0;
+  size_t bufsize;
+
+  /* Open the file for both read and write.  That way, we can write
+     it later without having to reopen it, and don't have to worry if
+     we chdir in the meantime.  */
+  fd = open (listed_incremental_option, O_RDWR | O_CREAT, MODE_RW);
+  if (fd < 0)
     {
     {
-      ERROR ((0, errno, _("Cannot open %s"), listed_incremental_option));
+      open_error (listed_incremental_option);
       return;
       return;
     }
     }
-  if (!fp)
-    return;
-  fgets (buf, sizeof (buf), fp);
-
-  /* FIXME: Using after_date_option as a first time flag looks fairly
-     dubious to me!  So, using -N with incremental might be buggy just
-     because of the next few lines.  I saw a few unexplained, almost harsh
-     advices, from other GNU people, about *not* using -N with incremental
-     dumps, and here might lie (part of) the reason.  */
-  if (!after_date_option)
-    {
-      newer_mtime_option = atol (buf);
-      after_date_option = 1;
-    }
 
 
-  while (fgets (buf, sizeof (buf), fp))
+  fp = fdopen (fd, "r+");
+  if (! fp)
     {
     {
-      strp = &buf[strlen (buf)];
-      if (strp[-1] == '\n')
-	strp[-1] = '\0';
-      /* FIXME: For files ending with an incomplete line, maybe a NUL might
-	 be missing, here...  */
-
-      strp = buf;
-      device_number = atol (strp);
-      while (ISDIGIT (*strp))
-	strp++;
-      inode_number = atol (strp);
-      while (ISSPACE (*strp))
-	strp++;
-      while (ISDIGIT (*strp))
-	strp++;
-      strp++;
-      unquote_string (strp);
-      note_directory (strp, device_number, inode_number, NULL);
+      open_error (listed_incremental_option);
+      close (fd);
+      return;
     }
     }
-  if (fclose (fp) == EOF)
-    ERROR ((0, errno, "%s", listed_incremental_option));
-}
 
 
-/*---.
-| ?  |
-`---*/
-
-void
-write_dir_file (void)
-{
-  FILE *fp;
-  struct directory *directory;
-  char *str;
+  listed_incremental_stream = fp;
 
 
-  fp = fopen (listed_incremental_option, "w");
-  if (fp == 0)
-    {
-      ERROR ((0, errno, _("Cannot write to %s"), listed_incremental_option));
-      return;
-    }
-  fprintf (fp, "%lu\n", time_now);
-  for (directory = directory_list; directory; directory = directory->next)
+  if (0 < getline (&buf, &bufsize, fp))
     {
     {
-      if (!directory->dir_text)
-	continue;
-      str = quote_copy_string (directory->name);
-      if (str)
+      char *ebuf;
+      int n;
+      long lineno = 1;
+      unsigned long u = (errno = 0, strtoul (buf, &ebuf, 10));
+      time_t t = u;
+      if (buf == ebuf || (u == 0 && errno == EINVAL))
+	ERROR ((0, 0, "%s:1: %s", quotearg_colon (listed_incremental_option),
+		_("Invalid time stamp")));
+      else if (t != u || (u == -1 && errno == ERANGE))
+	ERROR ((0, 0, "%s:1: %s", quotearg_colon (listed_incremental_option),
+		_("Time stamp out of range")));
+      else
+	newer_mtime_option = t;
+
+      while (0 < (n = getline (&buf, &bufsize, fp)))
 	{
 	{
-	  fprintf (fp, "%u %u %s\n", directory->device_number,
-		   directory->inode_number, str);
-	  free (str);
+	  dev_t dev;
+	  ino_t ino;
+	  bool nfs = buf[0] == '+';
+	  char *strp = buf + nfs;
+
+	  lineno++;
+
+	  if (buf[n - 1] == '\n')
+	    buf[n - 1] = '\0';
+
+	  errno = 0;
+	  dev = u = strtoul (strp, &ebuf, 10);
+	  if (strp == ebuf || (u == 0 && errno == EINVAL))
+	    ERROR ((0, 0, "%s:%ld: %s",
+		    quotearg_colon (listed_incremental_option), lineno,
+		    _("Invalid device number")));
+	  else if (dev != u || (u == -1 && errno == ERANGE))
+	    ERROR ((0, 0, "%s:%ld: %s",
+		    quotearg_colon (listed_incremental_option), lineno,
+		    _("Device number out of range")));
+	  strp = ebuf;
+
+	  errno = 0;
+	  ino = u = strtoul (strp, &ebuf, 10);
+	  if (strp == ebuf || (u == 0 && errno == EINVAL))
+	    ERROR ((0, 0, "%s:%ld: %s",
+		    quotearg_colon (listed_incremental_option), lineno,
+		    _("Invalid inode number")));
+	  else if (ino != u || (u == -1 && errno == ERANGE))
+	    ERROR ((0, 0, "%s:%ld: %s",
+		    quotearg_colon (listed_incremental_option), lineno,
+		    _("Inode number out of range")));
+	  strp = ebuf;
+
+	  strp++;
+	  unquote_string (strp);
+	  note_directory (strp, dev, ino, nfs, 0);
 	}
 	}
-      else
-	fprintf (fp, "%u %u %s\n", directory->device_number,
-		 directory->inode_number, directory->name);
     }
     }
-  if (fclose (fp) == EOF)
-    ERROR ((0, errno, "%s", listed_incremental_option));
-}
 
 
-/*---.
-| ?  |
-`---*/
+  if (ferror (fp))
+    read_error (listed_incremental_option);
+  if (buf)
+    free (buf);
+}
 
 
-static int
-compare_names (char *param1, char *param2)
+/* Output incremental data for the directory ENTRY to the file DATA.
+   Return nonzero if successful, preserving errno on write failure.  */
+static bool
+write_directory_file_entry (void *entry, void *data)
 {
 {
-  struct name *n1 = (struct name *) param1;
-  struct name *n2 = (struct name *) param2;
+  struct directory const *directory = entry;
+  FILE *fp = data;
 
 
-  if (n1->found)
-    return n2->found ? strcmp (n1->name, n2->name) : -1;
-
-  if (n2->found)
-    return 1;
+  if (directory->found)
+    {
+      int e;
+      char *str = quote_copy_string (directory->name);
+      fprintf (fp, "+%lu %lu %s\n" + ! directory->nfs,
+	       (unsigned long) directory->device_number,
+	       (unsigned long) directory->inode_number,
+	       str ? str : directory->name);
+      e = errno;
+      if (str)
+	free (str);
+      errno = e;
+    }
 
 
-  return strcmp (n1->name, n2->name);
+  return ! ferror (fp);
 }
 }
 
 
-/*-------------------------------------------------------------------------.
-| Collect all the names from argv[] (or whatever), then expand them into a |
-| directory tree, and put all the directories at the beginning.		   |
-`-------------------------------------------------------------------------*/
-
 void
 void
-collect_and_sort_names (void)
+write_directory_file (void)
 {
 {
-  struct name *name;
-  struct name *next_name;
-  int num_names;
-  struct stat statbuf;
-
-  name_gather ();
-
-  if (listed_incremental_option)
-    read_directory_file ();
-
-  if (!namelist)
-    addname (".");
-
-  for (name = namelist; name; name = next_name)
-    {
-      next_name = name->next;
-      if (name->found || name->dir_contents)
-	continue;
-      if (name->regexp)		/* FIXME: just skip regexps for now */
-	continue;
-      if (name->change_dir)
-	if (chdir (name->change_dir) < 0)
-	  {
-	    ERROR ((0, errno, _("Cannot chdir to %s"), name->change_dir));
-	    continue;
-	  }
-
-      if (
-#ifdef AIX
-	  statx (name->name, &statbuf, STATSIZE, STX_HIDDEN | STX_LINK)
-#else
-	  lstat (name->name, &statbuf) < 0
-#endif
-	  )
-	{
-	  ERROR ((0, errno, _("Cannot stat %s"), name->name));
-	  continue;
-	}
-      if (S_ISDIR (statbuf.st_mode))
-	{
-	  name->found = 1;
-	  add_hierarchy_to_namelist (name->name, statbuf.st_dev);
-	}
-    }
+  FILE *fp = listed_incremental_stream;
 
 
-  num_names = 0;
-  for (name = namelist; name; name = name->next)
-    num_names++;
-  namelist = (struct name *)
-    merge_sort ((voidstar) namelist, num_names,
-		(char *) (&(namelist->next)) - (char *) namelist,
-		compare_names);
-
-  for (name = namelist; name; name = name->next)
-    name->found = 0;
+  if (! fp)
+    return;
 
 
-  if (listed_incremental_option)
-    write_dir_file ();
+  if (fseek (fp, 0L, SEEK_SET) != 0)
+    seek_error (listed_incremental_option);
+  if (ftruncate (fileno (fp), (off_t) 0) != 0)
+    truncate_error (listed_incremental_option);
+
+  fprintf (fp, "%lu\n", (unsigned long) start_time);
+  if (! ferror (fp) && directory_table)
+    hash_do_for_each (directory_table, write_directory_file_entry, fp);
+  if (ferror (fp))
+    write_error (listed_incremental_option);
+  if (fclose (fp) != 0)
+    close_error (listed_incremental_option);
 }
 }
 
 
 /* Restoration of incremental dumps.  */
 /* Restoration of incremental dumps.  */
 
 
-/*---.
-| ?  |
-`---*/
-
 void
 void
-gnu_restore (int skipcrud)
+gnu_restore (char const *directory_name)
 {
 {
-  char *current_dir;
   char *archive_dir;
   char *archive_dir;
-  struct accumulator *accumulator;
-  char *p;
-  DIR *dirp;
-  struct dirent *d;
+  char *current_dir;
   char *cur, *arc;
   char *cur, *arc;
-  long size, copied;
+  size_t size;
+  size_t copied;
   union block *data_block;
   union block *data_block;
   char *to;
   char *to;
 
 
-#define CURRENT_FILE_NAME (skipcrud + current_file_name)
+  current_dir = savedir (directory_name);
 
 
-  dirp = opendir (CURRENT_FILE_NAME);
-
-  if (!dirp)
+  if (!current_dir)
     {
     {
       /* The directory doesn't exist now.  It'll be created.  In any
       /* The directory doesn't exist now.  It'll be created.  In any
 	 case, we don't have to delete any files out of it.  */
 	 case, we don't have to delete any files out of it.  */
 
 
-      skip_file ((long) current_stat.st_size);
+      skip_member ();
       return;
       return;
     }
     }
 
 
-  accumulator = new_accumulator ();
-  while (d = readdir (dirp), d)
-    {
-      if (is_dot_or_dotdot (d->d_name))
-	continue;
-
-      add_to_accumulator (accumulator, d->d_name, (int) (NAMLEN (d) + 1));
-    }
-  closedir (dirp);
-  add_to_accumulator (accumulator, "", 1);
-
-  current_dir = get_accumulator (accumulator);
-  archive_dir = (char *) xmalloc ((size_t) current_stat.st_size);
+  size = current_stat_info.stat.st_size;
+  if (size != current_stat_info.stat.st_size)
+    xalloc_die ();
+  archive_dir = xmalloc (size);
   to = archive_dir;
   to = archive_dir;
-  for (size = current_stat.st_size; size > 0; size -= copied)
+  for (; size > 0; size -= copied)
     {
     {
       data_block = find_next_block ();
       data_block = find_next_block ();
       if (!data_block)
       if (!data_block)
@@ -678,7 +549,7 @@ gnu_restore (int skipcrud)
       copied = available_space_after (data_block);
       copied = available_space_after (data_block);
       if (copied > size)
       if (copied > size)
 	copied = size;
 	copied = size;
-      memcpy (to, data_block->buffer, (size_t) copied);
+      memcpy (to, data_block->buffer, copied);
       to += copied;
       to += copied;
       set_next_block_after ((union block *)
       set_next_block_after ((union block *)
 			    (data_block->buffer + copied - 1));
 			    (data_block->buffer + copied - 1));
@@ -694,22 +565,22 @@ gnu_restore (int skipcrud)
 	}
 	}
       if (*arc == '\0')
       if (*arc == '\0')
 	{
 	{
-	  p = new_name (CURRENT_FILE_NAME, cur);
-	  if (interactive_option && !confirm ("delete", p))
+	  char *p = new_name (directory_name, cur);
+	  if (! interactive_option || confirm ("delete", p))
 	    {
 	    {
-	      free (p);
-	      continue;
+	      if (verbose_option)
+		fprintf (stdlis, _("%s: Deleting %s\n"),
+			 program_name, quote (p));
+	      if (! remove_any_file (p, 1))
+		{
+		  int e = errno;
+		  ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p)));
+		}
 	    }
 	    }
-	  if (verbose_option)
-	    fprintf (stdlis, _("%s: Deleting %s\n"), program_name, p);
-	  if (!remove_any_file (p, 1))
-	    ERROR ((0, errno, _("Error while deleting %s"), p));
 	  free (p);
 	  free (p);
 	}
 	}
 
 
     }
     }
-  delete_accumulator (accumulator);
+  free (current_dir);
   free (archive_dir);
   free (archive_dir);
-
-#undef CURRENT_FILE_NAME
 }
 }