]> Dogcows Code - chaz/tar/commitdiff
GNU tar 1.12
authorPaul Eggert <eggert@cs.ucla.edu>
Fri, 25 Apr 1997 13:48:46 +0000 (13:48 +0000)
committerPaul Eggert <eggert@cs.ucla.edu>
Fri, 25 Apr 1997 13:48:46 +0000 (13:48 +0000)
src/buffer.c
src/create.c

index e0ffc2d28654d7cd4d6dc9782370b9fe41c3ab3b..067339b835287692528cf8137ac5c80be78b9259 100644 (file)
 /* Buffer management for tar.
-   Copyright (C) 1988, 1992, 1993 Free Software Foundation
-
-This file is part of GNU Tar.
-
-GNU Tar is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
-
-GNU Tar is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU Tar; see the file COPYING.  If not, write to
-the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
-
-/*
- * Buffer management for tar.
- *
- * Written by John Gilmore, ihnp4!hoptoad!gnu, on 25 August 1985.
- */
-
-#include <stdio.h>
-#include <errno.h>
-#ifndef STDC_HEADERS
-extern int errno;
-#endif
-#include <sys/types.h>         /* For non-Berkeley systems */
+   Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc.
+   Written by John Gilmore, on 1985-08-25.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any later
+   version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+   Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation, Inc.,
+   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#include "system.h"
+
 #include <signal.h>
 #include <time.h>
 time_t time ();
 
-#ifdef HAVE_SYS_MTIO_H
-#include <sys/ioctl.h>
-#include <sys/mtio.h>
+#if MSDOS
+# include <process.h>
 #endif
 
-#ifdef BSD42
-#include <sys/file.h>
-#else
-#ifndef V7
-#include <fcntl.h>
-#endif
+#if XENIX
+# include <sys/inode.h>
 #endif
 
-#ifdef __MSDOS__
-#include <process.h>
+#ifndef FNM_LEADING_DIR
+# include <fnmatch.h>
 #endif
 
-#ifdef XENIX
-#include <sys/inode.h>
-#endif
-
-#include "tar.h"
-#include "port.h"
+#include "common.h"
 #include "rmt.h"
-#include "regex.h"
 
-/* Either stdout or stderr:  The thing we write messages (standard msgs, not
-   errors) to.  Stdout unless we're writing a pipe, in which case stderr */
-FILE *msg_file = stdout;
+#define DEBUG_FORK 0           /* if nonzero, childs are born stopped */
+
+#define        STDIN 0                 /* standard input  file descriptor */
+#define        STDOUT 1                /* standard output file descriptor */
+
+#define        PREAD 0                 /* read file descriptor from pipe() */
+#define        PWRITE 1                /* write file descriptor from pipe() */
 
-#define        STDIN   0               /* Standard input  file descriptor */
-#define        STDOUT  1               /* Standard output file descriptor */
+/* Number of retries before giving up on read.  */
+#define        READ_ERROR_MAX 10
 
-#define        PREAD   0               /* Read  file descriptor from pipe() */
-#define        PWRITE  1               /* Write file descriptor from pipe() */
+/* Globbing pattern to append to volume label if initial match failed.  */
+#define VOLUME_LABEL_APPEND " Volume [1-9]*"
+\f
+/* Variables.  */
 
-#define        MAGIC_STAT      105     /* Magic status returned by child, if
-                                  it can't exec.  We hope compress/sh
-                                  never return this status! */
+static tarlong total_written;  /* bytes written on all volumes */
+static tarlong bytes_written;  /* bytes written on this volume */
 
-void *valloc ();
+/* FIXME: The following four variables should ideally be static to this
+   module.  However, this cannot be done yet, as update.c uses the first
+   three a lot, and compare.c uses the fourth.  The cleanup continues!  */
 
-void writeerror ();
-void readerror ();
+union block *record_start;     /* start of record of archive */
+union block *record_end;       /* last+1 block of archive record */
+union block *current_block;    /* current block of archive */
+enum access_mode access_mode;  /* how do we handle the archive */
+static struct stat archive_stat; /* stat block for archive file */
 
-void ck_pipe ();
-void ck_close ();
+static long record_start_block; /* block ordinal at record_start */
 
-int backspace_output ();
-extern void finish_header ();
-void flush_archive ();
-int isfile ();
-int new_volume ();
-void verify_volume ();
-extern void to_oct ();
+/* Where we write list messages (not errors, not interactions) to.  Stdout
+   unless we're writing a pipe, in which case stderr.  */
+FILE *stdlis;
 
-#ifndef __MSDOS__
-/* Obnoxious test to see if dimwit is trying to dump the archive */
+static void backspace_output PARAMS ((void));
+static int new_volume PARAMS ((enum access_mode));
+static void write_error PARAMS ((int));
+static void read_error PARAMS ((void));
+
+#if !MSDOS
+/* Obnoxious test to see if dimwit is trying to dump the archive.  */
 dev_t ar_dev;
 ino_t ar_ino;
 #endif
 
-/*
- * The record pointed to by save_rec should not be overlaid
- * when reading in a new tape block.  Copy it to record_save_area first, and
- * change the pointer in *save_rec to point to record_save_area.
- * Saved_recno records the record number at the time of the save.
- * This is used by annofile() to print the record number of a file's
- * header record.
- */
-static union record **save_rec;
-union record record_save_area;
-static long saved_recno;
-
-/*
- * PID of child program, if f_compress or remote archive access.
- */
-static int childpid = 0;
-
-/*
- * Record number of the start of this block of records
- */
-long baserec;
-
-/*
- * Error recovery stuff
- */
-static int r_error_count;
-
-/*
- * Have we hit EOF yet?
- */
+/* PID of child program, if compress_option or remote archive access.  */
+static int child_pid = 0;
+
+/* Error recovery stuff  */
+static int read_error_count;
+
+/* Have we hit EOF yet?  */
 static int hit_eof;
 
 /* Checkpointing counter */
 static int checkpoint;
 
-/* JF we're reading, but we just read the last record and its time to update */
-extern time_to_start_writing;
-int file_to_switch_to = -1;    /* If remote update, close archive, and use
+/* We're reading, but we just read the last block and its time to update.  */
+/* As least EXTERN like this one as possible.  FIXME!  */
+extern int time_to_start_writing;
+
+int file_to_switch_to = -1;    /* if remote update, close archive, and use
                                   this descriptor to write to */
 
-static int volno = 1;          /* JF which volume of a multi-volume tape
-                                  we're on */
-static int global_volno = 1;   /* Volume number to print in external messages. */
+static int volno = 1;          /* which volume of a multi-volume tape we're
+                                  on */
+static int global_volno = 1;   /* volume number to print in external
+                                  messages */
+
+/* The pointer save_name, which is set in function dump_file() of module
+   create.c, points to the original long filename instead of the new,
+   shorter mangled name that is set in start_header() of module create.c.
+   The pointer save_name is only used in multi-volume mode when the file
+   being processed is non-sparse; if a file is split between volumes, the
+   save_name is used in generating the LF_MULTIVOL record on the second
+   volume.  (From Pierce Cantrell, 1991-08-13.)  */
+
+char *save_name;               /* name of the file we are currently writing */
+long save_totsize;             /* total size of file we are writing, only
+                                  valid if save_name is non NULL */
+long save_sizeleft;            /* where we are in the file we are writing,
+                                  only valid if save_name is nonzero */
+
+int write_archive_to_stdout = 0;
+
+/* Used by flush_read and flush_write to store the real info about saved
+   names.  */
+static char *real_s_name = NULL;
+static long real_s_totsize;
+static long real_s_sizeleft;
+\f
+/* Functions.  */
 
-char *save_name = 0;           /* Name of the file we are currently writing */
-long save_totsize;             /* total size of file we are writing.  Only
-                                  valid if save_name is non_zero */
-long save_sizeleft;            /* Where we are in the file we are writing.
-                                  Only valid if save_name is non-zero */
+#if DEBUG_FORK
 
-int write_archive_to_stdout;
+static pid_t
+myfork (void)
+{
+  pid_t result = fork();
 
-/* Used by fl_read and fl_write to store the real info about saved names */
-static char real_s_name[NAMSIZ];
-static long real_s_totsize;
-static long real_s_sizeleft;
+  if (result == 0)
+    kill (getpid (), SIGSTOP);
+  return result;
+}
+
+# define fork myfork
+
+#endif /* DEBUG FORK */
+
+void
+init_total_written (void)
+{
+  clear_tarlong (total_written);
+  clear_tarlong (bytes_written);
+}
+
+void
+print_total_written (void)
+{
+  fprintf (stderr, _("Total bytes written: "));
+  print_tarlong (total_written, stderr);
+  fprintf (stderr, "\n");
+}
+
+/*--------------------------------------------------------.
+| Compute and return the block ordinal at current_block.  |
+`--------------------------------------------------------*/
 
-/* Reset the EOF flag (if set), and re-set ar_record, etc */
+long
+current_block_ordinal (void)
+{
+  return record_start_block + (current_block - record_start);
+}
+
+/*------------------------------------------------------------------.
+| If the EOF flag is set, reset it, as well as current_block, etc.  |
+`------------------------------------------------------------------*/
 
 void
-reset_eof ()
+reset_eof (void)
 {
   if (hit_eof)
     {
       hit_eof = 0;
-      ar_record = ar_block;
-      ar_last = ar_block + blocking;
-      ar_reading = 0;
+      current_block = record_start;
+      record_end = record_start + blocking_factor;
+      access_mode = ACCESS_WRITE;
     }
 }
 
-/*
- * Return the location of the next available input or output record.
- * Return NULL for EOF.  Once we have returned NULL, we just keep returning
- * it, to avoid accidentally going on to the next file on the "tape".
- */
-union record *
-findrec ()
+/*-------------------------------------------------------------------------.
+| Return the location of the next available input or output block.        |
+| Return NULL for EOF.  Once we have returned NULL, we just keep returning |
+| it, to avoid accidentally going on to the next file on the tape.        |
+`-------------------------------------------------------------------------*/
+
+union block *
+find_next_block (void)
 {
-  if (ar_record == ar_last)
+  if (current_block == record_end)
     {
       if (hit_eof)
-       return (union record *) NULL;   /* EOF */
+       return NULL;
       flush_archive ();
-      if (ar_record == ar_last)
+      if (current_block == record_end)
        {
-         hit_eof++;
-         return (union record *) NULL; /* EOF */
+         hit_eof = 1;
+         return NULL;
        }
     }
-  return ar_record;
+  return current_block;
 }
 
+/*------------------------------------------------------.
+| Indicate that we have used all blocks up thru BLOCK.  |
+|                                                      |
+| FIXME: should the arg have an off-by-1?              |
+`------------------------------------------------------*/
 
-/*
- * Indicate that we have used all records up thru the argument.
- * (should the arg have an off-by-1? XXX FIXME)
- */
 void
-userec (rec)
-     union record *rec;
+set_next_block_after (union block *block)
 {
-  while (rec >= ar_record)
-    ar_record++;
-  /*
-        * Do NOT flush the archive here.  If we do, the same
-        * argument to userec() could mean the next record (if the
-        * input block is exactly one record long), which is not what
-        * is intended.
-        */
-  if (ar_record > ar_last)
+  while (block >= current_block)
+    current_block++;
+
+  /* Do *not* flush the archive here.  If we do, the same argument to
+     set_next_block_after could mean the next block (if the input record
+     is exactly one block long), which is not what is intended.  */
+
+  if (current_block > record_end)
     abort ();
 }
 
+/*------------------------------------------------------------------------.
+| Return the number of bytes comprising the space between POINTER through |
+| the end of the current buffer of blocks.  This space is available for          |
+| filling with data, or taking data from.  POINTER is usually (but not   |
+| always) the result previous find_next_block call.                      |
+`------------------------------------------------------------------------*/
 
-/*
- * Return a pointer to the end of the current records buffer.
- * All the space between findrec() and endofrecs() is available
- * for filling with data, or taking data from.
- */
-union record *
-endofrecs ()
+int
+available_space_after (union block *pointer)
 {
-  return ar_last;
+  return (int) (record_end->buffer - pointer->buffer);
 }
 
+/*------------------------------------------------------------------.
+| Close file having descriptor FD, and abort if close unsucessful.  |
+`------------------------------------------------------------------*/
 
-/*
- * Duplicate a file descriptor into a certain slot.
- * Equivalent to BSD "dup2" with error reporting.
- */
-void
-dupto (from, to, msg)
-     int from, to;
-     char *msg;
+static void
+xclose (int fd)
 {
-  int err;
+  if (close (fd) < 0)
+    FATAL_ERROR ((0, errno, _("Cannot close file #%d"), fd));
+}
+
+/*-----------------------------------------------------------------------.
+| Duplicate file descriptor FROM into becoming INTO, or else, issue     |
+| MESSAGE.  INTO is closed first and has to be the next available slot.         |
+`-----------------------------------------------------------------------*/
 
-  if (from != to)
+static void
+xdup2 (int from, int into, const char *message)
+{
+  if (from != into)
     {
-      err = close (to);
-      if (err < 0 && errno != EBADF)
-       {
-         msg_perror ("Cannot close descriptor %d", to);
-         exit (EX_SYSTEM);
-       }
-      err = dup (from);
-      if (err != to)
-       {
-         msg_perror ("cannot dup %s", msg);
-         exit (EX_SYSTEM);
-       }
-      ck_close (from);
+      int status = close (into);
+
+      if (status < 0 && errno != EBADF)
+       FATAL_ERROR ((0, errno, _("Cannot close descriptor %d"), into));
+      status = dup (from);
+      if (status != into)
+       FATAL_ERROR ((0, errno, _("Cannot properly duplicate %s"), message));
+      xclose (from);
     }
 }
 
-#ifdef __MSDOS__
-void
-child_open ()
+#if MSDOS
+
+/*-------------------------------------------------------.
+| Set ARCHIVE for writing, then compressing an archive.         |
+`-------------------------------------------------------*/
+
+static void
+child_open_for_compress (void)
 {
-  fprintf (stderr, "MS-DOS %s can't use compressed or remote archives\n", tar);
-  exit (EX_ARGSBAD);
+  FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
 }
 
-#else
-void
-child_open ()
+/*---------------------------------------------------------.
+| Set ARCHIVE for uncompressing, then reading an archive.  |
+`---------------------------------------------------------*/
+
+static void
+child_open_for_uncompress (void)
 {
-  int pipe[2];
-  int err = 0;
+  FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
+}
 
-  int kidpipe[2];
-  int kidchildpid;
+#else /* not MSDOS */
 
-#define READ   0
-#define WRITE  1
+/*---------------------------------------------------------------------.
+| Return nonzero if NAME is the name of a regular file, or if the file |
+| does not exist (so it would be created as a regular file).          |
+`---------------------------------------------------------------------*/
 
-  ck_pipe (pipe);
+static int
+is_regular_file (const char *name)
+{
+  struct stat stbuf;
 
-  childpid = fork ();
-  if (childpid < 0)
+  if (stat (name, &stbuf) < 0)
+    return 1;
+
+  if (S_ISREG (stbuf.st_mode))
+    return 1;
+
+  return 0;
+}
+
+/*-------------------------------------------------------.
+| Set ARCHIVE for writing, then compressing an archive.         |
+`-------------------------------------------------------*/
+
+static void
+child_open_for_compress (void)
+{
+  int parent_pipe[2];
+  int child_pipe[2];
+  int grandchild_pid;
+
+  if (pipe (parent_pipe) < 0)
+    FATAL_ERROR ((0, errno, _("Cannot open pipe")));
+
+  child_pid = fork ();
+  if (child_pid < 0)
+    FATAL_ERROR ((0, errno, _("Cannot fork")));
+
+  if (child_pid > 0)
     {
-      msg_perror ("cannot fork");
-      exit (EX_SYSTEM);
+      /* The parent tar is still here!  Just clean up.  */
+
+      archive = parent_pipe[PWRITE];
+      xclose (parent_pipe[PREAD]);
+      return;
     }
-  if (childpid > 0)
+
+  /* The new born child tar is here!  */
+
+  program_name = _("tar (child)");
+
+  xdup2 (parent_pipe[PREAD], STDIN, _("(child) Pipe to stdin"));
+  xclose (parent_pipe[PWRITE]);
+
+  /* Check if we need a grandchild tar.  This happens only if either:
+     a) we are writing stdout: to force reblocking;
+     b) the file is to be accessed by rmt: compressor doesn't know how;
+     c) the file is not a plain file.  */
+
+  if (strcmp (archive_name_array[0], "-") != 0
+      && !_remdev (archive_name_array[0])
+      && is_regular_file (archive_name_array[0]))
     {
-      /* We're the parent.  Clean up and be happy */
-      /* This, at least, is easy */
+      if (backup_option)
+       maybe_backup_file (archive_name_array[0], 1);
 
-      if (ar_reading)
-       {
-         f_reblock++;
-         archive = pipe[READ];
-         ck_close (pipe[WRITE]);
-       }
-      else
+      /* We don't need a grandchild tar.  Open the archive and launch the
+        compressor.  */
+
+      archive = creat (archive_name_array[0], 0666);
+      if (archive < 0)
        {
-         archive = pipe[WRITE];
-         ck_close (pipe[READ]);
+         int saved_errno = errno;
+
+         if (backup_option)
+           undo_last_backup ();
+         FATAL_ERROR ((0, saved_errno, _("Cannot open archive %s"),
+                       archive_name_array[0]));
        }
-      return;
+      xdup2 (archive, STDOUT, _("Archive to stdout"));
+      execlp (use_compress_program_option, use_compress_program_option,
+             (char *) 0);
+      FATAL_ERROR ((0, errno, _("Cannot exec %s"),
+                   use_compress_program_option));
     }
 
-  /* We're the kid */
-  if (ar_reading)
+  /* We do need a grandchild tar.  */
+
+  if (pipe (child_pipe) < 0)
+    FATAL_ERROR ((0, errno, _("Cannot open pipe")));
+
+  grandchild_pid = fork ();
+  if (grandchild_pid < 0)
+    FATAL_ERROR ((0, errno, _("Child cannot fork")));
+
+  if (grandchild_pid > 0)
     {
-      dupto (pipe[WRITE], STDOUT, "(child) pipe to stdout");
-      ck_close (pipe[READ]);
+      /* The child tar is still here!  Launch the compressor.  */
+
+      xdup2 (child_pipe[PWRITE], STDOUT, _("((child)) Pipe to stdout"));
+      xclose (child_pipe[PREAD]);
+      execlp (use_compress_program_option, use_compress_program_option,
+             (char *) 0);
+      FATAL_ERROR ((0, errno, _("Cannot exec %s"),
+                   use_compress_program_option));
     }
+
+  /* The new born grandchild tar is here!  */
+
+  program_name = _("tar (grandchild)");
+
+  /* Prepare for reblocking the data from the compressor into the archive.  */
+
+  xdup2 (child_pipe[PREAD], STDIN, _("(grandchild) Pipe to stdin"));
+  xclose (child_pipe[PWRITE]);
+
+  if (strcmp (archive_name_array[0], "-") == 0)
+    archive = STDOUT;
   else
-    {
-      dupto (pipe[READ], STDIN, "(child) pipe to stdin");
-      ck_close (pipe[WRITE]);
-    }
+    archive = rmtcreat (archive_name_array[0], 0666, rsh_command_option);
+  if (archive < 0)
+    FATAL_ERROR ((0, errno, _("Cannot open archive %s"),
+                 archive_name_array[0]));
 
-  /* We need a child tar only if
-          1: we're reading/writing stdin/out (to force reblocking)
-          2: the file is to be accessed by rmt (compress doesn't know how)
-          3: the file is not a plain file */
-#ifdef NO_REMOTE
-  if (!(ar_files[0][0] == '-' && ar_files[0][1] == '\0') && isfile (ar_files[0]))
-#else
-  if (!(ar_files[0][0] == '-' && ar_files[0][1] == '\0') && !_remdev (ar_files[0]) && isfile (ar_files[0]))
-#endif
+  /* Let's read out of the stdin pipe and write an archive.  */
+
+  while (1)
     {
-      /* We don't need a child tar.  Open the archive */
-      if (ar_reading)
+      int status = 0;
+      char *cursor;
+      int length;
+
+      /* Assemble a record.  */
+
+      for (length = 0, cursor = record_start->buffer;
+          length < record_size;
+          length += status, cursor += status)
        {
-         archive = open (ar_files[0], O_RDONLY | O_BINARY, 0666);
-         if (archive < 0)
-           {
-             msg_perror ("can't open archive %s", ar_files[0]);
-             exit (EX_BADARCH);
-           }
-         dupto (archive, STDIN, "archive to stdin");
-         /* close(archive); */
+         int size = record_size - length;
+
+         if (size < BLOCKSIZE)
+           size = BLOCKSIZE;
+         status = read (STDIN, cursor, (size_t) size);
+         if (status <= 0)
+           break;
        }
-      else
+
+      if (status < 0)
+       FATAL_ERROR ((0, errno, _("Cannot read from compression program")));
+
+      /* Copy the record.  */
+
+      if (status == 0)
        {
-         archive = creat (ar_files[0], 0666);
-         if (archive < 0)
+         /* We hit the end of the file.  Write last record at
+            full length, as the only role of the grandchild is
+            doing proper reblocking.  */
+
+         if (length > 0)
            {
-             msg_perror ("can't open archive %s", ar_files[0]);
-             exit (EX_BADARCH);
+             memset (record_start->buffer + length, 0,
+                     (size_t) record_size - length);
+             status = rmtwrite (archive, record_start->buffer,
+                                (unsigned int) record_size);
+             if (status != record_size)
+               write_error (status);
            }
-         dupto (archive, STDOUT, "archive to stdout");
-         /* close(archive); */
+
+         /* There is nothing else to read, break out.  */
+         break;
        }
+
+      status = rmtwrite (archive, record_start->buffer,
+                        (unsigned int) record_size);
+      if (status != record_size)
+       write_error (status);
+    }
+
+#if 0
+  close_archive ();
+#endif
+  exit (exit_status);
+}
+
+/*---------------------------------------------------------.
+| Set ARCHIVE for uncompressing, then reading an archive.  |
+`---------------------------------------------------------*/
+
+static void
+child_open_for_uncompress (void)
+{
+  int parent_pipe[2];
+  int child_pipe[2];
+  int grandchild_pid;
+
+  if (pipe (parent_pipe) < 0)
+    FATAL_ERROR ((0, errno, _("Cannot open pipe")));
+
+  child_pid = fork ();
+  if (child_pid < 0)
+    FATAL_ERROR ((0, errno, _("Cannot fork")));
+
+  if (child_pid > 0)
+    {
+      /* The parent tar is still here!  Just clean up.  */
+
+      read_full_records_option = 1;
+      archive = parent_pipe[PREAD];
+      xclose (parent_pipe[PWRITE]);
+      return;
+    }
+
+  /* The new born child tar is here!  */
+
+  program_name = _("tar (child)");
+
+  xdup2 (parent_pipe[PWRITE], STDOUT, _("(child) Pipe to stdout"));
+  xclose (parent_pipe[PREAD]);
+
+  /* Check if we need a grandchild tar.  This happens only if either:
+     a) we're reading stdin: to force unblocking;
+     b) the file is to be accessed by rmt: compressor doesn't know how;
+     c) the file is not a plain file.  */
+
+  if (strcmp (archive_name_array[0], "-") != 0
+      && !_remdev (archive_name_array[0])
+      && is_regular_file (archive_name_array[0]))
+    {
+      /* We don't need a grandchild tar.  Open the archive and lauch the
+        uncompressor.  */
+
+      archive = open (archive_name_array[0], O_RDONLY | O_BINARY, 0666);
+      if (archive < 0)
+       FATAL_ERROR ((0, errno, _("Cannot open archive %s"),
+                     archive_name_array[0]));
+      xdup2 (archive, STDIN, _("Archive to stdin"));
+      execlp (use_compress_program_option, use_compress_program_option,
+             "-d", (char *) 0);
+      FATAL_ERROR ((0, errno, _("Cannot exec %s"),
+                   use_compress_program_option));
     }
+
+  /* We do need a grandchild tar.  */
+
+  if (pipe (child_pipe) < 0)
+    FATAL_ERROR ((0, errno, _("Cannot open pipe")));
+
+  grandchild_pid = fork ();
+  if (grandchild_pid < 0)
+    FATAL_ERROR ((0, errno, _("Child cannot fork")));
+
+  if (grandchild_pid > 0)
+    {
+      /* The child tar is still here!  Launch the uncompressor.  */
+
+      xdup2 (child_pipe[PREAD], STDIN, _("((child)) Pipe to stdin"));
+      xclose (child_pipe[PWRITE]);
+      execlp (use_compress_program_option, use_compress_program_option,
+             "-d", (char *) 0);
+      FATAL_ERROR ((0, errno, _("Cannot exec %s"),
+                   use_compress_program_option));
+    }
+
+  /* The new born grandchild tar is here!  */
+
+  program_name = _("tar (grandchild)");
+
+  /* Prepare for unblocking the data from the archive into the uncompressor.  */
+
+  xdup2 (child_pipe[PWRITE], STDOUT, _("(grandchild) Pipe to stdout"));
+  xclose (child_pipe[PREAD]);
+
+  if (strcmp (archive_name_array[0], "-") == 0)
+    archive = STDIN;
   else
+    archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY,
+                      0666, rsh_command_option);
+  if (archive < 0)
+    FATAL_ERROR ((0, errno, _("Cannot open archive %s"),
+                 archive_name_array[0]));
+
+  /* Let's read the archive and pipe it into stdout.  */
+
+  while (1)
     {
-      /* We need a child tar */
-      ck_pipe (kidpipe);
+      char *cursor;
+      int maximum;
+      int count;
+      int status;
 
-      kidchildpid = fork ();
-      if (kidchildpid < 0)
-       {
-         msg_perror ("child can't fork");
-         exit (EX_SYSTEM);
-       }
+      read_error_count = 0;
 
-      if (kidchildpid > 0)
+    error_loop:
+      status = rmtread (archive, record_start->buffer,
+                       (unsigned int) (record_size));
+      if (status < 0)
        {
-         /* About to exec compress:  set up the files */
-         if (ar_reading)
-           {
-             dupto (kidpipe[READ], STDIN, "((child)) pipe to stdin");
-             ck_close (kidpipe[WRITE]);
-             /* dup2(pipe[WRITE],STDOUT); */
-           }
-         else
-           {
-             /* dup2(pipe[READ],STDIN); */
-             dupto (kidpipe[WRITE], STDOUT, "((child)) pipe to stdout");
-             ck_close (kidpipe[READ]);
-           }
-         /* ck_close(pipe[READ]); */
-         /* ck_close(pipe[WRITE]); */
-         /* ck_close(kidpipe[READ]);
-                       ck_close(kidpipe[WRITE]); */
+         read_error ();
+         goto error_loop;
        }
-      else
+      if (status == 0)
+       break;
+      cursor = record_start->buffer;
+      maximum = status;
+      while (maximum)
        {
-         /* Grandchild.  Do the right thing, namely sit here and
-                  read/write the archive, and feed stuff back to compress */
-         tar = "tar (child)";
-         if (ar_reading)
-           {
-             dupto (kidpipe[WRITE], STDOUT, "[child] pipe to stdout");
-             ck_close (kidpipe[READ]);
-           }
-         else
-           {
-             dupto (kidpipe[READ], STDIN, "[child] pipe to stdin");
-             ck_close (kidpipe[WRITE]);
-           }
+         count = maximum < BLOCKSIZE ? maximum : BLOCKSIZE;
+         status = write (STDOUT, cursor, (size_t) count);
+         if (status < 0)
+           FATAL_ERROR ((0, errno, _("\
+Cannot write to compression program")));
 
-         if (ar_files[0][0] == '-' && ar_files[0][1] == '\0')
+         if (status != count)
            {
-             if (ar_reading)
-               archive = STDIN;
-             else
-               archive = STDOUT;
+             ERROR ((0, 0, _("\
+Write to compression program short %d bytes"),
+                     count - status));
+             count = status;
            }
-         else                  /* This can't happen if (ar_reading==2)
-                               archive = rmtopen(ar_files[0], O_RDWR|O_CREAT|O_BINARY, 0666);
-                                       else */ if (ar_reading)
-           archive = rmtopen (ar_files[0], O_RDONLY | O_BINARY, 0666);
-         else
-           archive = rmtcreat (ar_files[0], 0666);
 
-         if (archive < 0)
-           {
-             msg_perror ("can't open archive %s", ar_files[0]);
-             exit (EX_BADARCH);
-           }
-
-         if (ar_reading)
-           {
-             for (;;)
-               {
-                 char *ptr;
-                 int max, count;
-
-                 r_error_count = 0;
-               error_loop:
-                 err = rmtread (archive, ar_block->charptr, (int) (blocksize));
-                 if (err < 0)
-                   {
-                     readerror ();
-                     goto error_loop;
-                   }
-                 if (err == 0)
-                   break;
-                 ptr = ar_block->charptr;
-                 max = err;
-                 while (max)
-                   {
-                     count = (max < RECORDSIZE) ? max : RECORDSIZE;
-                     err = write (STDOUT, ptr, count);
-                     if (err != count)
-                       {
-                         if (err < 0)
-                           {
-                             msg_perror ("can't write to compression program");
-                             exit (EX_SYSTEM);
-                           }
-                         else
-                           msg ("write to compression program short %d bytes",
-                                count - err);
-                         count = (err < 0) ? 0 : err;
-                       }
-                     ptr += count;
-                     max -= count;
-                   }
-               }
-           }
-         else
-           {
-             for (;;)
-               {
-                 int n;
-                 char *ptr;
-
-                 n = blocksize;
-                 ptr = ar_block->charptr;
-                 while (n)
-                   {
-                     err = read (STDIN, ptr, (n < RECORDSIZE) ? n : RECORDSIZE);
-                     if (err <= 0)
-                       break;
-                     n -= err;
-                     ptr += err;
-                   }
-                 /* EOF */
-                 if (err == 0)
-                   {
-                     if (!f_compress_block)
-                       blocksize -= n;
-                     else
-                       bzero (ar_block->charptr + blocksize - n, n);
-                     err = rmtwrite (archive, ar_block->charptr, blocksize);
-                     if (err != (blocksize))
-                       writeerror (err);
-                     if (!f_compress_block)
-                       blocksize += n;
-                     break;
-                   }
-                 if (n)
-                   {
-                     msg_perror ("can't read from compression program");
-                     exit (EX_SYSTEM);
-                   }
-                 err = rmtwrite (archive, ar_block->charptr, (int) blocksize);
-                 if (err != blocksize)
-                   writeerror (err);
-               }
-           }
-
-         /* close_archive(); */
-         exit (0);
+         cursor += count;
+         maximum -= count;
        }
     }
-  /* So we should exec compress (-d) */
-  if (ar_reading)
-    execlp (f_compressprog, f_compressprog, "-d", (char *) 0);
-  else
-    execlp (f_compressprog, f_compressprog, (char *) 0);
-  msg_perror ("can't exec %s", f_compressprog);
-  _exit (EX_SYSTEM);
+
+#if 0
+  close_archive ();
+#endif
+  exit (exit_status);
 }
 
+#endif /* not MSDOS */
 
-/* return non-zero if p is the name of a directory */
-int
-isfile (p)
-     char *p;
+/*--------------------------------------------------------------------------.
+| Check the LABEL block against the volume label, seen as a globbing       |
+| pattern.  Return true if the pattern matches.  In case of failure, retry  |
+| matching a volume sequence number before giving up in multi-volume mode.  |
+`--------------------------------------------------------------------------*/
+
+static int
+check_label_pattern (union block *label)
 {
-  struct stat stbuf;
+  char *string;
+  int result;
 
-  if (stat (p, &stbuf) < 0)
+  if (fnmatch (volume_label_option, label->header.name, 0) == 0)
     return 1;
-  if (S_ISREG (stbuf.st_mode))
-    return 1;
-  return 0;
+
+  if (!multi_volume_option)
+    return 0;
+
+  string = xmalloc (strlen (volume_label_option)
+                   + sizeof VOLUME_LABEL_APPEND + 1);
+  strcpy (string, volume_label_option);
+  strcat (string, VOLUME_LABEL_APPEND);
+  result = fnmatch (string, label->header.name, 0) == 0;
+  free (string);
+  return result;
 }
 
-#endif
+/*------------------------------------------------------------------------.
+| Open an archive file.  The argument specifies whether we are reading or |
+| writing, or both.                                                      |
+`------------------------------------------------------------------------*/
 
-/*
- * Open an archive file.  The argument specifies whether we are
- * reading or writing.
- */
-/* JF if the arg is 2, open for reading and writing. */
 void
-open_archive (reading)
-     int reading;
+open_archive (enum access_mode access)
 {
-  msg_file = f_exstdout ? stderr : stdout;
+  int backed_up_flag = 0;
 
-  if (blocksize == 0)
-    {
-      msg ("invalid value for blocksize");
-      exit (EX_ARGSBAD);
-    }
+  stdlis = to_stdout_option ? stderr : stdout;
 
-  if (n_ar_files == 0)
-    {
-      msg ("No archive name given, what should I do?");
-      exit (EX_BADARCH);
-    }
+  if (record_size == 0)
+    FATAL_ERROR ((0, 0, _("Invalid value for record_size")));
+
+  if (archive_names == 0)
+    FATAL_ERROR ((0, 0, _("No archive name given")));
 
-  /*NOSTRICT*/
-  if (f_multivol)
+  current_file_name = NULL;
+  current_link_name = NULL;
+
+  /* FIXME: According to POSIX.1, PATH_MAX may well not be a compile-time
+     constant, and the value from sysconf (_SC_PATH_MAX) may well not be any
+     size that is reasonable to allocate a buffer.  In the GNU system, there
+     is no fixed limit.  The only correct thing to do is to use dynamic
+     allocation.  (Roland McGrath)  */
+
+  if (!real_s_name)
+    real_s_name = (char *) xmalloc (PATH_MAX);
+  /* FIXME: real_s_name is never freed.  */
+
+  save_name = NULL;
+
+  if (multi_volume_option)
     {
-      ar_block = (union record *) valloc ((unsigned) (blocksize + (2 * RECORDSIZE)));
-      if (ar_block)
-       ar_block += 2;
+      record_start
+       = (union block *) valloc ((unsigned) (record_size + (2 * BLOCKSIZE)));
+      if (record_start)
+       record_start += 2;
     }
   else
-    ar_block = (union record *) valloc ((unsigned) blocksize);
-  if (!ar_block)
-    {
-      msg ("could not allocate memory for blocking factor %d",
-          blocking);
-      exit (EX_ARGSBAD);
-    }
+    record_start = (union block *) valloc ((unsigned) record_size);
+  if (!record_start)
+    FATAL_ERROR ((0, 0, _("Could not allocate memory for blocking factor %d"),
+                 blocking_factor));
 
-  ar_record = ar_block;
-  ar_last = ar_block + blocking;
-  ar_reading = reading;
+  current_block = record_start;
+  record_end = record_start + blocking_factor;
+  /* When updating the archive, we start with reading.  */
+  access_mode = access == ACCESS_UPDATE ? ACCESS_READ : access;
 
-  if (f_multivol && f_verify)
-    {
-      msg ("cannot verify multi-volume archives");
-      exit (EX_ARGSBAD);
-    }
+  if (multi_volume_option && verify_option)
+    FATAL_ERROR ((0, 0, _("Cannot verify multi-volume archives")));
 
-  if (f_compressprog)
+  if (use_compress_program_option)
     {
-      if (reading == 2 || f_verify)
-       {
-         msg ("cannot update or verify compressed archives");
-         exit (EX_ARGSBAD);
-       }
-      if (f_multivol)
+      if (multi_volume_option)
+       FATAL_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
+      if (verify_option)
+       FATAL_ERROR ((0, 0, _("Cannot verify compressed archives")));
+
+      switch (access)
        {
-         msg ("cannot use multi-volume compressed archives");
-         exit (EX_ARGSBAD);
+       case ACCESS_READ:
+         child_open_for_uncompress ();
+         break;
+
+       case ACCESS_WRITE:
+         child_open_for_compress ();
+         break;
+
+       case ACCESS_UPDATE:
+         FATAL_ERROR ((0, 0, _("Cannot update compressed archives")));
+         break;
        }
-      child_open ();
-      if (!reading && ar_files[0][0] == '-' && ar_files[0][1] == '\0')
-       msg_file = stderr;
-      /* child_open(rem_host, rem_file); */
+
+      if (access == ACCESS_WRITE && strcmp (archive_name_array[0], "-") == 0)
+       stdlis = stderr;
     }
-  else if (ar_files[0][0] == '-' && ar_files[0][1] == '\0')
+  else if (strcmp (archive_name_array[0], "-") == 0)
     {
-      f_reblock++;             /* Could be a pipe, be safe */
-      if (f_verify)
-       {
-         msg ("can't verify stdin/stdout archive");
-         exit (EX_ARGSBAD);
-       }
-      if (reading == 2)
+      read_full_records_option = 1; /* could be a pipe, be safe */
+      if (verify_option)
+       FATAL_ERROR ((0, 0, _("Cannot verify stdin/stdout archive")));
+
+      switch (access)
        {
+       case ACCESS_READ:
          archive = STDIN;
-         msg_file = stderr;
-         write_archive_to_stdout++;
-       }
-      else if (reading)
-       archive = STDIN;
-      else
-       {
+         break;
+
+       case ACCESS_WRITE:
          archive = STDOUT;
-         msg_file = stderr;
+         stdlis = stderr;
+         break;
+
+       case ACCESS_UPDATE:
+         archive = STDIN;
+         stdlis = stderr;
+         write_archive_to_stdout = 1;
+         break;
        }
     }
-  else if (reading == 2 || f_verify)
-    {
-      archive = rmtopen (ar_files[0], O_RDWR | O_CREAT | O_BINARY, 0666);
-    }
-  else if (reading)
-    {
-      archive = rmtopen (ar_files[0], O_RDONLY | O_BINARY, 0666);
-    }
+  else if (verify_option)
+    archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+                      0666, rsh_command_option);
   else
-    {
-      archive = rmtcreat (ar_files[0], 0666);
-    }
+    switch (access)
+      {
+      case ACCESS_READ:
+       archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY, 0666,
+                          rsh_command_option);
+       break;
+
+      case ACCESS_WRITE:
+       if (backup_option)
+         {
+           maybe_backup_file (archive_name_array[0], 1);
+           backed_up_flag = 1;
+         }
+       archive = rmtcreat (archive_name_array[0], 0666, rsh_command_option);
+       break;
+
+      case ACCESS_UPDATE:
+       archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+                          0666, rsh_command_option);
+       break;
+      }
+
   if (archive < 0)
     {
-      msg_perror ("can't open %s", ar_files[0]);
-      exit (EX_BADARCH);
+      int saved_errno = errno;
+
+      if (backed_up_flag)
+       undo_last_backup ();
+      FATAL_ERROR ((0, saved_errno, _("Cannot open %s"),
+                   archive_name_array[0]));
     }
-#ifndef __MSDOS__
-  if (!_isrmt (archive))
-    {
-      struct stat tmp_stat;
 
-      fstat (archive, &tmp_stat);
-      if (S_ISREG (tmp_stat.st_mode))
-       {
-         ar_dev = tmp_stat.st_dev;
-         ar_ino = tmp_stat.st_ino;
-       }
+#if !MSDOS
+
+  fstat (archive, &archive_stat);
+
+  /* Detect if outputting to "/dev/null".  */
+  {
+    struct stat dev_null_stat;
+
+    stat ("/dev/null", &dev_null_stat);
+    dev_null_output = (S_ISCHR (archive_stat.st_mode)
+                      && archive_stat.st_rdev == dev_null_stat.st_rdev);
+  }
+
+  if (!_isrmt (archive) && S_ISREG (archive_stat.st_mode))
+    {
+      ar_dev = archive_stat.st_dev;
+      ar_ino = archive_stat.st_ino;
     }
-#endif
 
-#ifdef __MSDOS__
+#endif /* not MSDOS */
+
+#if MSDOS
   setmode (archive, O_BINARY);
 #endif
 
-  if (reading)
+  switch (access)
     {
-      ar_last = ar_block;      /* Set up for 1st block = # 0 */
-      (void) findrec ();       /* Read it in, check for EOF */
+    case ACCESS_READ:
+    case ACCESS_UPDATE:
+      record_end = record_start; /* set up for 1st record = # 0 */
+      find_next_block ();      /* read it in, check for EOF */
 
-      if (f_volhdr)
+      if (volume_label_option)
        {
-         union record *head;
-#if 0
-         char *ptr;
+         union block *label = find_next_block ();
+
+         if (!label)
+           FATAL_ERROR ((0, 0, _("Archive not labelled to match `%s'"),
+                         volume_label_option));
+         if (!check_label_pattern (label))
+           FATAL_ERROR ((0, 0, _("Volume `%s' does not match `%s'"),
+                         label->header.name, volume_label_option));
+       }
+      break;
 
-         if (f_multivol)
-           {
-             ptr = malloc (strlen (f_volhdr) + 20);
-             sprintf (ptr, "%s Volume %d", f_volhdr, 1);
-           }
+    case ACCESS_WRITE:
+      if (volume_label_option)
+       {
+         memset ((void *) record_start, 0, BLOCKSIZE);
+         if (multi_volume_option)
+           sprintf (record_start->header.name, "%s Volume 1",
+                    volume_label_option);
          else
-           ptr = f_volhdr;
-#endif
-         head = findrec ();
-         if (!head)
-           {
-             msg ("Archive not labelled to match %s", f_volhdr);
-             exit (EX_BADVOL);
-           }
-         if (re_match (label_pattern, head->header.arch_name,
-                       strlen (head->header.arch_name), 0, 0) < 0)
-           {
-             msg ("Volume mismatch!  %s!=%s", f_volhdr,
-                  head->header.arch_name);
-             exit (EX_BADVOL);
-           }
+           strcpy (record_start->header.name, volume_label_option);
+
+         assign_string (&current_file_name, record_start->header.name);
+
+         record_start->header.typeflag = GNUTYPE_VOLHDR;
+         to_oct (time (0), 1 + 12, record_start->header.mtime);
+         finish_header (record_start);
 #if 0
-         if (strcmp (ptr, head->header.name))
-           {
-             msg ("Volume mismatch!  %s!=%s", ptr, head->header.name);
-             exit (EX_BADVOL);
-           }
-         if (ptr != f_volhdr)
-           free (ptr);
+         current_block++;
 #endif
        }
-    }
-  else if (f_volhdr)
-    {
-      bzero ((void *) ar_block, RECORDSIZE);
-      if (f_multivol)
-       sprintf (ar_block->header.arch_name, "%s Volume 1", f_volhdr);
-      else
-       strcpy (ar_block->header.arch_name, f_volhdr);
-      current_file_name = ar_block->header.arch_name;
-      ar_block->header.linkflag = LF_VOLHDR;
-      to_oct (time (0), 1 + 12, ar_block->header.mtime);
-      finish_header (ar_block);
-      /* ar_record++; */
+      break;
     }
 }
 
+/*--------------------------------------.
+| Perform a write to flush the buffer.  |
+`--------------------------------------*/
 
-/*
- * Remember a union record * as pointing to something that we
- * need to keep when reading onward in the file.  Only one such
- * thing can be remembered at once, and it only works when reading
- * an archive.
- *
- * We calculate "offset" then add it because some compilers end up
- * adding (baserec+ar_record), doing a 9-bit shift of baserec, then
- * subtracting ar_block from that, shifting it back, losing the top 9 bits.
- */
 void
-saverec (pointer)
-     union record **pointer;
+flush_write (void)
 {
-  long offset;
-
-  save_rec = pointer;
-  offset = ar_record - ar_block;
-  saved_recno = baserec + offset;
-}
-
-/*
- * Perform a write to flush the buffer.
- */
-
-/*send_buffer_to_file();
-  if(new_volume) {
-       deal_with_new_volume_stuff();
-       send_buffer_to_file();
-  }
- */
-
-void
-fl_write ()
-{
-  int err;
   int copy_back;
-  static long bytes_written = 0;
+  int status;
+
+  if (checkpoint_option && !(++checkpoint % 10))
+    WARN ((0, 0, _("Write checkpoint %d"), checkpoint));
 
-  if (f_checkpoint && !(++checkpoint % 10))
-    msg ("Write checkpoint %d\n", checkpoint);
-  if (tape_length && bytes_written >= tape_length * 1024)
+  if (!zerop_tarlong (tape_length_option)
+      && !lessp_tarlong (bytes_written, tape_length_option))
     {
-      errno = ENOSPC;
-      err = 0;
+      errno = ENOSPC;          /* FIXME: errno should be read-only */
+      status = 0;
     }
+  else if (dev_null_output)
+    status = record_size;
   else
-    err = rmtwrite (archive, ar_block->charptr, (int) blocksize);
-  if (err != blocksize && !f_multivol)
-    writeerror (err);
-  else if (f_totals)
-    tot_written += blocksize;
-
-  if (err > 0)
-    bytes_written += err;
-  if (err == blocksize)
+    status = rmtwrite (archive, record_start->buffer,
+                      (unsigned int) record_size);
+  if (status != record_size && !multi_volume_option)
+    write_error (status);
+  else if (totals_option)
+    add_to_tarlong (total_written, record_size);
+
+  if (status > 0)
+    add_to_tarlong (bytes_written, status);
+
+  if (status == record_size)
     {
-      if (f_multivol)
+      if (multi_volume_option)
        {
+         char *cursor;
+
          if (!save_name)
            {
              real_s_name[0] = '\0';
@@ -773,101 +911,117 @@ fl_write ()
              real_s_sizeleft = 0;
              return;
            }
-#ifdef __MSDOS__
-         if (save_name[1] == ':')
-           save_name += 2;
+
+         cursor = save_name;
+#if MSDOS
+         if (cursor[1] == ':')
+           cursor += 2;
 #endif
-         while (*save_name == '/')
-           save_name++;
+         while (*cursor == '/')
+           cursor++;
 
-         strcpy (real_s_name, save_name);
+         strcpy (real_s_name, cursor);
          real_s_totsize = save_totsize;
          real_s_sizeleft = save_sizeleft;
        }
       return;
     }
 
-  /* We're multivol  Panic if we didn't get the right kind of response */
-  /* ENXIO is for the UNIX PC */
-  if (err < 0 && errno != ENOSPC && errno != EIO && errno != ENXIO)
-    writeerror (err);
+  /* We're multivol.  Panic if we didn't get the right kind of response.  */
 
-  /* If error indicates a short write, we just move to the next tape. */
+  /* ENXIO is for the UNIX PC.  */
+  if (status < 0 && errno != ENOSPC && errno != EIO && errno != ENXIO)
+    write_error (status);
 
-  if (new_volume (0) < 0)
+  /* If error indicates a short write, we just move to the next tape.  */
+
+  if (!new_volume (ACCESS_WRITE))
     return;
-  bytes_written = 0;
-  if (f_volhdr && real_s_name[0])
+
+  clear_tarlong (bytes_written);
+
+  if (volume_label_option && real_s_name[0])
     {
       copy_back = 2;
-      ar_block -= 2;
+      record_start -= 2;
     }
-  else if (f_volhdr || real_s_name[0])
+  else if (volume_label_option || real_s_name[0])
     {
       copy_back = 1;
-      ar_block--;
+      record_start--;
     }
   else
     copy_back = 0;
-  if (f_volhdr)
+
+  if (volume_label_option)
     {
-      bzero ((void *) ar_block, RECORDSIZE);
-      sprintf (ar_block->header.arch_name, "%s Volume %d", f_volhdr, volno);
-      to_oct (time (0), 1 + 12, ar_block->header.mtime);
-      ar_block->header.linkflag = LF_VOLHDR;
-      finish_header (ar_block);
+      memset ((void *) record_start, 0, BLOCKSIZE);
+      sprintf (record_start->header.name, "%s Volume %d", volume_label_option, volno);
+      to_oct (time (0), 1 + 12, record_start->header.mtime);
+      record_start->header.typeflag = GNUTYPE_VOLHDR;
+      finish_header (record_start);
     }
+
   if (real_s_name[0])
     {
       int tmp;
 
-      if (f_volhdr)
-       ar_block++;
-      bzero ((void *) ar_block, RECORDSIZE);
-      strcpy (ar_block->header.arch_name, real_s_name);
-      ar_block->header.linkflag = LF_MULTIVOL;
+      if (volume_label_option)
+       record_start++;
+
+      memset ((void *) record_start, 0, BLOCKSIZE);
+
+      /* FIXME: Michael P Urban writes: [a long name file] is being written
+        when a new volume rolls around [...]  Looks like the wrong value is
+        being preserved in real_s_name, though.  */
+
+      strcpy (record_start->header.name, real_s_name);
+      record_start->header.typeflag = GNUTYPE_MULTIVOL;
       to_oct ((long) real_s_sizeleft, 1 + 12,
-             ar_block->header.size);
+             record_start->header.size);
       to_oct ((long) real_s_totsize - real_s_sizeleft,
-             1 + 12, ar_block->header.offset);
-      tmp = f_verbose;
-      f_verbose = 0;
-      finish_header (ar_block);
-      f_verbose = tmp;
-      if (f_volhdr)
-       ar_block--;
+             1 + 12, record_start->oldgnu_header.offset);
+      tmp = verbose_option;
+      verbose_option = 0;
+      finish_header (record_start);
+      verbose_option = tmp;
+
+      if (volume_label_option)
+       record_start--;
     }
 
-  err = rmtwrite (archive, ar_block->charptr, (int) blocksize);
-  if (err != blocksize)
-    writeerror (err);
-  else if (f_totals)
-    tot_written += blocksize;
-
+  status = rmtwrite (archive, record_start->buffer,
+                    (unsigned int) record_size);
+  if (status != record_size)
+    write_error (status);
+  else if (totals_option)
+    add_to_tarlong (total_written, record_size);
 
-  bytes_written = blocksize;
+  add_to_tarlong (bytes_written, record_size);
   if (copy_back)
     {
-      ar_block += copy_back;
-      bcopy ((void *) (ar_block + blocking - copy_back),
-            (void *) ar_record,
-            copy_back * RECORDSIZE);
-      ar_record += copy_back;
-
-      if (real_s_sizeleft >= copy_back * RECORDSIZE)
-       real_s_sizeleft -= copy_back * RECORDSIZE;
-      else if ((real_s_sizeleft + RECORDSIZE - 1) / RECORDSIZE <= copy_back)
+      record_start += copy_back;
+      memcpy ((void *) current_block,
+             (void *) (record_start + blocking_factor - copy_back),
+             (size_t) (copy_back * BLOCKSIZE));
+      current_block += copy_back;
+
+      if (real_s_sizeleft >= copy_back * BLOCKSIZE)
+       real_s_sizeleft -= copy_back * BLOCKSIZE;
+      else if ((real_s_sizeleft + BLOCKSIZE - 1) / BLOCKSIZE <= copy_back)
        real_s_name[0] = '\0';
       else
        {
-#ifdef __MSDOS__
-         if (save_name[1] == ':')
-           save_name += 2;
+         char *cursor = save_name;
+
+#if MSDOS
+         if (cursor[1] == ':')
+           cursor += 2;
 #endif
-         while (*save_name == '/')
-           save_name++;
+         while (*cursor == '/')
+           cursor++;
 
-         strcpy (real_s_name, save_name);
+         strcpy (real_s_name, cursor);
          real_s_sizeleft = save_sizeleft;
          real_s_totsize = save_totsize;
        }
@@ -875,710 +1029,656 @@ fl_write ()
     }
 }
 
-/* Handle write errors on the archive.  Write errors are always fatal */
-/* Hitting the end of a volume does not cause a write error unless the write
-*  was the first block of the volume */
+/*---------------------------------------------------------------------.
+| Handle write errors on the archive.  Write errors are always fatal.  |
+| Hitting the end of a volume does not cause a write error unless the  |
+| write was the first record of the volume.                           |
+`---------------------------------------------------------------------*/
 
-void
-writeerror (err)
-     int err;
+static void
+write_error (int status)
 {
-  if (err < 0)
-    {
-      msg_perror ("can't write to %s", ar_files[cur_ar_file]);
-      exit (EX_BADARCH);
-    }
+  int saved_errno = errno;
+
+  /* It might be useful to know how much was written before the error
+     occured.  Beware that mere printing maybe change errno value.  */
+  if (totals_option)
+    print_total_written ();
+
+  if (status < 0)
+    FATAL_ERROR ((0, saved_errno, _("Cannot write to %s"),
+                 *archive_name_cursor));
   else
-    {
-      msg ("only wrote %u of %u bytes to %s", err, blocksize, ar_files[cur_ar_file]);
-      exit (EX_BADARCH);
-    }
+    FATAL_ERROR ((0, 0, _("Only wrote %u of %u bytes to %s"),
+                 status, record_size, *archive_name_cursor));
 }
 
-/*
- * Handle read errors on the archive.
- *
- * If the read should be retried, readerror() returns to the caller.
- */
-void
-readerror ()
-{
-#      define  READ_ERROR_MAX  10
+/*-------------------------------------------------------------------.
+| Handle read errors on the archive.  If the read should be retried, |
+| returns to the caller.                                            |
+`-------------------------------------------------------------------*/
 
-  read_error_flag++;           /* Tell callers */
+static void
+read_error (void)
+{
+  WARN ((0, errno, _("Read error on %s"), *archive_name_cursor));
 
-  msg_perror ("read error on %s", ar_files[cur_ar_file]);
+  if (record_start_block == 0)
+    FATAL_ERROR ((0, 0, _("At beginning of tape, quitting now")));
 
-  if (baserec == 0)
-    {
-      /* First block of tape.  Probably stupidity error */
-      exit (EX_BADARCH);
-    }
+  /* Read error in mid archive.  We retry up to READ_ERROR_MAX times and
+     then give up on reading the archive.  */
 
-  /*
-        * Read error in mid archive.  We retry up to READ_ERROR_MAX times
-        * and then give up on reading the archive.  We set read_error_flag
-        * for our callers, so they can cope if they want.
-        */
-  if (r_error_count++ > READ_ERROR_MAX)
-    {
-      msg ("Too many errors, quitting.");
-      exit (EX_BADARCH);
-    }
+  if (read_error_count++ > READ_ERROR_MAX)
+    FATAL_ERROR ((0, 0, _("Too many errors, quitting")));
   return;
 }
 
+/*-------------------------------------.
+| Perform a read to flush the buffer.  |
+`-------------------------------------*/
 
-/*
- * Perform a read to flush the buffer.
- */
 void
-fl_read ()
+flush_read (void)
 {
-  int err;                     /* Result from system call */
-  int left;                    /* Bytes left */
-  char *more;                  /* Pointer to next byte to read */
-
-  if (f_checkpoint && !(++checkpoint % 10))
-    msg ("Read checkpoint %d\n", checkpoint);
-
-  /*
-        * Clear the count of errors.  This only applies to a single
-        * call to fl_read.  We leave read_error_flag alone; it is
-        * only turned off by higher level software.
-        */
-  r_error_count = 0;           /* Clear error count */
-
-  /*
-        * If we are about to wipe out a record that
-        * somebody needs to keep, copy it out to a holding
-        * area and adjust somebody's pointer to it.
-        */
-  if (save_rec &&
-      *save_rec >= ar_record &&
-      *save_rec < ar_last)
-    {
-      record_save_area = **save_rec;
-      *save_rec = &record_save_area;
-    }
-  if (write_archive_to_stdout && baserec != 0)
-    {
-      err = rmtwrite (1, ar_block->charptr, blocksize);
-      if (err != blocksize)
-       writeerror (err);
-    }
-  if (f_multivol)
-    {
-      if (save_name)
-       {
-         if (save_name != real_s_name)
-           {
-#ifdef __MSDOS__
-             if (save_name[1] == ':')
-               save_name += 2;
-#endif
-             while (*save_name == '/')
-               save_name++;
+  int status;                  /* result from system call */
+  int left;                    /* bytes left */
+  char *more;                  /* pointer to next byte to read */
 
-             strcpy (real_s_name, save_name);
-             save_name = real_s_name;
-           }
-         real_s_totsize = save_totsize;
-         real_s_sizeleft = save_sizeleft;
+  if (checkpoint_option && !(++checkpoint % 10))
+    WARN ((0, 0, _("Read checkpoint %d"), checkpoint));
 
-       }
-      else
-       {
-         real_s_name[0] = '\0';
-         real_s_totsize = 0;
-         real_s_sizeleft = 0;
-       }
+  /* Clear the count of errors.  This only applies to a single call to
+     flush_read.  */
+
+  read_error_count = 0;                /* clear error count */
+
+  if (write_archive_to_stdout && record_start_block != 0)
+    {
+      status = rmtwrite (1, record_start->buffer, (unsigned int) record_size);
+      if (status != record_size)
+       write_error (status);
     }
+  if (multi_volume_option)
+    if (save_name)
+      {
+       char *cursor = save_name;
+
+#if MSDOS
+       if (cursor[1] == ':')
+         cursor += 2;
+#endif
+       while (*cursor == '/')
+         cursor++;
+
+       strcpy (real_s_name, cursor);
+       real_s_sizeleft = save_sizeleft;
+       real_s_totsize = save_totsize;
+      }
+    else
+      {
+       real_s_name[0] = '\0';
+       real_s_totsize = 0;
+       real_s_sizeleft = 0;
+      }
 
 error_loop:
-  err = rmtread (archive, ar_block->charptr, (int) blocksize);
-  if (err == blocksize)
+  status = rmtread (archive, record_start->buffer, (unsigned int) record_size);
+  if (status == record_size)
     return;
 
-  if ((err == 0 || (err < 0 && errno == ENOSPC) || (err > 0 && !f_reblock)) && f_multivol)
+  if ((status == 0
+       || (status < 0 && errno == ENOSPC)
+       || (status > 0 && !read_full_records_option))
+      && multi_volume_option)
     {
-      union record *head;
+      union block *cursor;
 
     try_volume:
-      if (new_volume ((cmd_mode == CMD_APPEND || cmd_mode == CMD_CAT || cmd_mode == CMD_UPDATE) ? 2 : 1) < 0)
-       return;
+      switch (subcommand_option)
+       {
+       case APPEND_SUBCOMMAND:
+       case CAT_SUBCOMMAND:
+       case UPDATE_SUBCOMMAND:
+         if (!new_volume (ACCESS_UPDATE))
+           return;
+         break;
+
+       default:
+         if (!new_volume (ACCESS_READ))
+           return;
+         break;
+       }
+
     vol_error:
-      err = rmtread (archive, ar_block->charptr, (int) blocksize);
-      if (err < 0)
+      status = rmtread (archive, record_start->buffer,
+                       (unsigned int) record_size);
+      if (status < 0)
        {
-         readerror ();
+         read_error ();
          goto vol_error;
        }
-      if (err != blocksize)
+      if (status != record_size)
        goto short_read;
 
-      head = ar_block;
+      cursor = record_start;
 
-      if (head->header.linkflag == LF_VOLHDR)
+      if (cursor->header.typeflag == GNUTYPE_VOLHDR)
        {
-         if (f_volhdr)
+         if (volume_label_option)
            {
-#if 0
-             char *ptr;
-
-             ptr = (char *) malloc (strlen (f_volhdr) + 20);
-             sprintf (ptr, "%s Volume %d", f_volhdr, volno);
-#endif
-             if (re_match (label_pattern, head->header.arch_name,
-                           strlen (head->header.arch_name),
-                           0, 0) < 0)
-               {
-                 msg ("Volume mismatch! %s!=%s", f_volhdr,
-                      head->header.arch_name);
-                 --volno;
-                 --global_volno;
-                 goto try_volume;
-               }
-
-#if 0
-             if (strcmp (ptr, head->header.name))
+             if (!check_label_pattern (cursor))
                {
-                 msg ("Volume mismatch! %s!=%s", ptr, head->header.name);
-                 --volno;
-                 --global_volno;
-                 free (ptr);
+                 WARN ((0, 0, _("Volume `%s' does not match `%s'"),
+                        cursor->header.name, volume_label_option));
+                 volno--;
+                 global_volno--;
                  goto try_volume;
                }
-             free (ptr);
-#endif
            }
-         if (f_verbose)
-           fprintf (msg_file, "Reading %s\n", head->header.arch_name);
-         head++;
-       }
-      else if (f_volhdr)
-       {
-         msg ("Warning:  No volume header!");
+         if (verbose_option)
+           fprintf (stdlis, _("Reading %s\n"), cursor->header.name);
+         cursor++;
        }
+      else if (volume_label_option)
+       WARN ((0, 0, _("WARNING: No volume header")));
 
       if (real_s_name[0])
        {
-         long from_oct ();
-
-         if (head->header.linkflag != LF_MULTIVOL || strcmp (head->header.arch_name, real_s_name))
+         if (cursor->header.typeflag != GNUTYPE_MULTIVOL
+             || strcmp (cursor->header.name, real_s_name))
            {
-             msg ("%s is not continued on this volume!", real_s_name);
-             --volno;
-             --global_volno;
+             WARN ((0, 0, _("%s is not continued on this volume"),
+                    real_s_name));
+             volno--;
+             global_volno--;
              goto try_volume;
            }
-         if (real_s_totsize != from_oct (1 + 12, head->header.size) + from_oct (1 + 12, head->header.offset))
+         if (real_s_totsize
+             != (from_oct (1 + 12, cursor->header.size)
+                 + from_oct (1 + 12, cursor->oldgnu_header.offset)))
            {
-             msg ("%s is the wrong size (%ld!=%ld+%ld)",
-                  head->header.arch_name, save_totsize,
-                  from_oct (1 + 12, head->header.size),
-                  from_oct (1 + 12, head->header.offset));
-             --volno;
-             --global_volno;
+             WARN ((0, 0, _("%s is the wrong size (%ld != %ld + %ld)"),
+                        cursor->header.name, save_totsize,
+                        from_oct (1 + 12, cursor->header.size),
+                        from_oct (1 + 12, cursor->oldgnu_header.offset)));
+             volno--;
+             global_volno--;
              goto try_volume;
            }
-         if (real_s_totsize - real_s_sizeleft != from_oct (1 + 12, head->header.offset))
+         if (real_s_totsize - real_s_sizeleft
+             != from_oct (1 + 12, cursor->oldgnu_header.offset))
            {
-             msg ("This volume is out of sequence");
-             --volno;
-             --global_volno;
+             WARN ((0, 0, _("This volume is out of sequence")));
+             volno--;
+             global_volno--;
              goto try_volume;
            }
-         head++;
+         cursor++;
        }
-      ar_record = head;
+      current_block = cursor;
       return;
     }
-  else if (err < 0)
+  else if (status < 0)
     {
-      readerror ();
-      goto error_loop;         /* Try again */
+      read_error ();
+      goto error_loop;         /* try again */
     }
 
 short_read:
-  more = ar_block->charptr + err;
-  left = blocksize - err;
+  more = record_start->buffer + status;
+  left = record_size - status;
 
 again:
-  if (0 == (((unsigned) left) % RECORDSIZE))
+  if ((unsigned) left % BLOCKSIZE == 0)
     {
-      /* FIXME, for size=0, multi vol support */
-      /* On the first block, warn about the problem */
-      if (!f_reblock && baserec == 0 && f_verbose && err > 0)
-       {
-         /*    msg("Blocksize = %d record%s",
-                               err / RECORDSIZE, (err > RECORDSIZE)? "s": "");*/
-         msg ("Blocksize = %d records", err / RECORDSIZE);
-       }
-      ar_last = ar_block + ((unsigned) (blocksize - left)) / RECORDSIZE;
+      /* FIXME: for size=0, multi-volume support.  On the first record, warn
+        about the problem.  */
+
+      if (!read_full_records_option && verbose_option
+         && record_start_block == 0 && status > 0)
+       WARN ((0, 0, _("Record size = %d blocks"), status / BLOCKSIZE));
+
+      record_end
+       = record_start + ((unsigned) (record_size - left)) / BLOCKSIZE;
+
       return;
     }
-  if (f_reblock)
+  if (read_full_records_option)
     {
-      /*
-                * User warned us about this.  Fix up.
-                */
+      /* User warned us about this.  Fix up.  */
+
       if (left > 0)
        {
        error2loop:
-         err = rmtread (archive, more, (int) left);
-         if (err < 0)
-           {
-             readerror ();
-             goto error2loop;  /* Try again */
-           }
-         if (err == 0)
+         status = rmtread (archive, more, (unsigned int) left);
+         if (status < 0)
            {
-             msg ("archive %s EOF not on block boundary", ar_files[cur_ar_file]);
-             exit (EX_BADARCH);
+             read_error ();
+             goto error2loop;  /* try again */
            }
-         left -= err;
-         more += err;
+         if (status == 0)
+           FATAL_ERROR ((0, 0, _("Archive %s EOF not on block boundary"),
+                         *archive_name_cursor));
+         left -= status;
+         more += status;
          goto again;
        }
     }
   else
-    {
-      msg ("only read %d bytes from archive %s", err, ar_files[cur_ar_file]);
-      exit (EX_BADARCH);
-    }
+    FATAL_ERROR ((0, 0, _("Only read %d bytes from archive %s"),
+                 status, *archive_name_cursor));
 }
 
+/*-----------------------------------------------.
+| Flush the current buffer to/from the archive.         |
+`-----------------------------------------------*/
 
-/*
- * Flush the current buffer to/from the archive.
- */
 void
-flush_archive ()
+flush_archive (void)
 {
-  int c;
+  record_start_block += record_end - record_start;
+  current_block = record_start;
+  record_end = record_start + blocking_factor;
 
-  baserec += ar_last - ar_block;/* Keep track of block #s */
-  ar_record = ar_block;                /* Restore pointer to start */
-  ar_last = ar_block + blocking;/* Restore pointer to end */
-
-  if (ar_reading)
+  if (access_mode == ACCESS_READ && time_to_start_writing)
     {
-      if (time_to_start_writing)
+      access_mode = ACCESS_WRITE;
+      time_to_start_writing = 0;
+
+      if (file_to_switch_to >= 0)
        {
-         time_to_start_writing = 0;
-         ar_reading = 0;
+         int status = rmtclose (archive);
 
-         if (file_to_switch_to >= 0)
-           {
-             if ((c = rmtclose (archive)) < 0)
-               msg_perror ("Warning: can't close %s(%d,%d)", ar_files[cur_ar_file], archive, c);
+         if (status < 0)
+           WARN ((0, errno, _("WARNING: Cannot close %s (%d, %d)"),
+                  *archive_name_cursor, archive, status));
 
-             archive = file_to_switch_to;
-           }
-         else
-           (void) backspace_output ();
-         fl_write ();
+         archive = file_to_switch_to;
        }
       else
-       fl_read ();
+       backspace_output ();
     }
-  else
+
+  switch (access_mode)
     {
-      fl_write ();
+    case ACCESS_READ:
+      flush_read ();
+      break;
+
+    case ACCESS_WRITE:
+      flush_write ();
+      break;
+
+    case ACCESS_UPDATE:
+      abort ();
     }
 }
 
-/* Backspace the archive descriptor by one blocks worth.
-   If its a tape, MTIOCTOP will work.  If its something else,
-   we try to seek on it.  If we can't seek, we lose! */
-int
-backspace_output ()
-{
-  long cur;
-  /* int er; */
-  extern char *output_start;
+/*-------------------------------------------------------------------------.
+| Backspace the archive descriptor by one record worth.  If its a tape,           |
+| MTIOCTOP will work.  If its something else, we try to seek on it.  If we |
+| can't seek, we lose!                                                    |
+`-------------------------------------------------------------------------*/
 
+static void
+backspace_output (void)
+{
 #ifdef MTIOCTOP
-  struct mtop t;
+  {
+    struct mtop operation;
 
-  t.mt_op = MTBSR;
-  t.mt_count = 1;
-  if ((rmtioctl (archive, MTIOCTOP, &t)) >= 0)
-    return 1;
-  if (errno == EIO && (rmtioctl (archive, MTIOCTOP, &t)) >= 0)
-    return 1;
+    operation.mt_op = MTBSR;
+    operation.mt_count = 1;
+    if (rmtioctl (archive, MTIOCTOP, (char *) &operation) >= 0)
+      return;
+    if (errno == EIO && rmtioctl (archive, MTIOCTOP, (char *) &operation) >= 0)
+      return;
+  }
 #endif
 
-  cur = rmtlseek (archive, 0L, 1);
-  cur -= blocksize;
-  /* Seek back to the beginning of this block and
-          start writing there. */
+  {
+    off_t position = rmtlseek (archive, 0L, 1);
 
-  if (rmtlseek (archive, cur, 0) != cur)
-    {
-      /* Lseek failed.  Try a different method */
-      msg ("Couldn't backspace archive file.  It may be unreadable without -i.");
-      /* Replace the first part of the block with nulls */
-      if (ar_block->charptr != output_start)
-       bzero (ar_block->charptr, output_start - ar_block->charptr);
-      return 2;
-    }
-  return 3;
+    /* Seek back to the beginning of this record and start writing there.  */
+
+    position -= record_size;
+    if (rmtlseek (archive, position, 0) != position)
+      {
+       /* Lseek failed.  Try a different method.  */
+
+       WARN ((0, 0, _("\
+Could not backspace archive file; it may be unreadable without -i")));
+
+       /* Replace the first part of the record with NULs.  */
+
+       if (record_start->buffer != output_start)
+         memset (record_start->buffer, 0,
+                 (size_t) (output_start - record_start->buffer));
+      }
+  }
 }
 
+/*-------------------------.
+| Close the archive file.  |
+`-------------------------*/
 
-/*
- * Close the archive file.
- */
 void
-close_archive ()
+close_archive (void)
 {
-  int child;
-  int status;
-  int c;
-
-  if (time_to_start_writing || !ar_reading)
+  if (time_to_start_writing || access_mode == ACCESS_WRITE)
     flush_archive ();
-  if (cmd_mode == CMD_DELETE)
+
+#if !MSDOS
+
+  /* Manage to fully drain a pipe we might be reading, so to not break it on
+     the producer after the EOF block.  FIXME: one of these days, GNU tar
+     might become clever enough to just stop working, once there is no more
+     work to do, we might have to revise this area in such time.  */
+
+  if (access_mode == ACCESS_READ && S_ISFIFO (archive_stat.st_mode))
+    while (rmtread (archive, record_start->buffer, (unsigned int) record_size)
+          > 0)
+      continue;
+#endif
+
+  if (subcommand_option == DELETE_SUBCOMMAND)
     {
       off_t pos;
 
       pos = rmtlseek (archive, 0L, 1);
-#ifndef __MSDOS__
-      (void) ftruncate (archive, pos);
+#if MSDOS
+      rmtwrite (archive, "", 0);
 #else
-      (void) rmtwrite (archive, "", 0);
+      ftruncate (archive, (size_t) pos);
 #endif
     }
-  if (f_verify)
+  if (verify_option)
     verify_volume ();
 
-  if ((c = rmtclose (archive)) < 0)
-    msg_perror ("Warning: can't close %s(%d,%d)", ar_files[cur_ar_file], archive, c);
+  {
+    int status = rmtclose (archive);
 
-#ifndef        __MSDOS__
-  if (childpid)
+    if (status < 0)
+      WARN ((0, errno, _("WARNING: Cannot close %s (%d, %d)"),
+            *archive_name_cursor, archive, status));
+  }
+
+#if !MSDOS
+
+  if (child_pid)
     {
-      /*
-       * Loop waiting for the right child to die, or for
-       * no more kids.
-       */
-      while (((child = wait (&status)) != childpid) && child != -1)
-       ;
+      WAIT_T wait_status;
+      int child;
+
+      /* Loop waiting for the right child to die, or for no more kids.  */
+
+      while ((child = wait (&wait_status), child != child_pid)
+            && child != -1)
+       continue;
 
       if (child != -1)
-       {
-         if (WIFSIGNALED (status))
-           {
-             /* SIGPIPE is OK, everything else is a problem. */
-             if (WTERMSIG (status) != SIGPIPE)
-               msg ("child died with signal %d%s", WTERMSIG (status),
-                    WIFCOREDUMPED (status) ? " (core dumped)" : "");
-           }
-         else
-           {
-             /* Child voluntarily terminated  -- but why? */
-             if (WEXITSTATUS (status) == MAGIC_STAT)
-               {
-                 exit (EX_SYSTEM);     /* Child had trouble */
-               }
-             if (WEXITSTATUS (status) == (SIGPIPE + 128))
-               {
-                 /*
-                  * /bin/sh returns this if its child
-                  * dies with SIGPIPE.  'Sok.
-                  */
-                 /* Do nothing. */
-               }
-             else if (WEXITSTATUS (status))
-               msg ("child returned status %d",
-                    WEXITSTATUS (status));
-           }
-       }
-    }
-#endif /* __MSDOS__ */
-}
+       if (WIFSIGNALED (wait_status)
+#if 0
+           && !WIFSTOPPED (wait_status)
+#endif
+           )
+         {
+           /* SIGPIPE is OK, everything else is a problem.  */
 
+           if (WTERMSIG (wait_status) != SIGPIPE)
+             ERROR ((0, 0, _("Child died with signal %d%s"),
+                     WTERMSIG (wait_status),
+                     WCOREDUMP (wait_status) ? _(" (core dumped)") : ""));
+         }
+       else
+         {
+           /* Child voluntarily terminated -- but why?  /bin/sh returns
+              SIGPIPE + 128 if its child, then do nothing.  */
 
-#ifdef DONTDEF
-/*
- * Message management.
- *
- * anno writes a message prefix on stream (eg stdout, stderr).
- *
- * The specified prefix is normally output followed by a colon and a space.
- * However, if other command line options are set, more output can come
- * out, such as the record # within the archive.
- *
- * If the specified prefix is NULL, no output is produced unless the
- * command line option(s) are set.
- *
- * If the third argument is 1, the "saved" record # is used; if 0, the
- * "current" record # is used.
- */
-void
-anno (stream, prefix, savedp)
-     FILE *stream;
-     char *prefix;
-     int savedp;
-{
-#      define  MAXANNO 50
-  char buffer[MAXANNO];                /* Holds annorecment */
-#      define  ANNOWIDTH 13
-  int space;
-  long offset;
-  int save_e;
-
-  save_e = errno;
-  /* Make sure previous output gets out in sequence */
-  if (stream == stderr)
-    fflush (stdout);
-  if (f_sayblock)
-    {
-      if (prefix)
-       {
-         fputs (prefix, stream);
-         putc (' ', stream);
-       }
-      offset = ar_record - ar_block;
-      (void) sprintf (buffer, "rec %d: ",
-                     savedp ? saved_recno :
-                     baserec + offset);
-      fputs (buffer, stream);
-      space = ANNOWIDTH - strlen (buffer);
-      if (space > 0)
-       {
-         fprintf (stream, "%*s", space, "");
-       }
-    }
-  else if (prefix)
-    {
-      fputs (prefix, stream);
-      fputs (": ", stream);
+           if (WEXITSTATUS (wait_status) != (SIGPIPE + 128)
+               && WEXITSTATUS (wait_status))
+             ERROR ((0, 0, _("Child returned status %d"),
+                     WEXITSTATUS (wait_status)));
+         }
     }
-  errno = save_e;
+#endif /* !MSDOS */
+
+  if (current_file_name)
+    free (current_file_name);
+  if (current_link_name)
+    free (current_link_name);
+  if (save_name)
+    free (save_name);
+  free (multi_volume_option ? record_start - 2 : record_start);
 }
 
-#endif
+/*------------------------------------------------.
+| Called to initialize the global volume number.  |
+`------------------------------------------------*/
 
-/* Called to initialize the global volume number. */
 void
-init_volume_number ()
+init_volume_number (void)
 {
-  FILE *vf;
-
-  vf = fopen (f_volno_file, "r");
-  if (!vf && errno != ENOENT)
-    msg_perror ("%s", f_volno_file);
+  FILE *file = fopen (volno_file_option, "r");
 
-  if (vf)
+  if (file)
     {
-      fscanf (vf, "%d", &global_volno);
-      fclose (vf);
+      fscanf (file, "%d", &global_volno);
+      if (fclose (file) == EOF)
+       ERROR ((0, errno, "%s", volno_file_option));
     }
+  else if (errno != ENOENT)
+    ERROR ((0, errno, "%s", volno_file_option));
 }
 
-/* Called to write out the closing global volume number. */
+/*-------------------------------------------------------.
+| Called to write out the closing global volume number.         |
+`-------------------------------------------------------*/
+
 void
-closeout_volume_number ()
+closeout_volume_number (void)
 {
-  FILE *vf;
+  FILE *file = fopen (volno_file_option, "w");
 
-  vf = fopen (f_volno_file, "w");
-  if (!vf)
-    msg_perror ("%s", f_volno_file);
-  else
+  if (file)
     {
-      fprintf (vf, "%d\n", global_volno);
-      fclose (vf);
+      fprintf (file, "%d\n", global_volno);
+      if (fclose (file) == EOF)
+       ERROR ((0, errno, "%s", volno_file_option));
     }
+  else
+    ERROR ((0, errno, "%s", volno_file_option));
 }
 
-/* We've hit the end of the old volume.  Close it and open the next one */
-/* Values for type:  0: writing  1: reading  2: updating */
-int
-new_volume (type)
-     int type;
+/*-----------------------------------------------------------------------.
+| We've hit the end of the old volume.  Close it and open the next one.         |
+| Return nonzero on success.                                            |
+`-----------------------------------------------------------------------*/
+
+static int
+new_volume (enum access_mode access)
 {
-  int c;
-  char inbuf[80];
-  char *p;
-  static FILE *read_file = 0;
-  extern int now_verifying;
-  extern char TTY_NAME[];
+  static FILE *read_file = NULL;
   static int looped = 0;
 
-  if (!read_file && !f_run_script_at_end)
-    read_file = (archive == 0) ? fopen (TTY_NAME, "r") : stdin;
+  int status;
+
+  if (!read_file && !info_script_option)
+    /* FIXME: if fopen is used, it will never be closed.  */
+    read_file = archive == STDIN ? fopen (TTY_NAME, "r") : stdin;
 
   if (now_verifying)
-    return -1;
-  if (f_verify)
+    return 0;
+  if (verify_option)
     verify_volume ();
-  if ((c = rmtclose (archive)) < 0)
-    msg_perror ("Warning: can't close %s(%d,%d)", ar_files[cur_ar_file], archive, c);
+
+  if (status = rmtclose (archive), status < 0)
+    WARN ((0, errno, _("WARNING: Cannot close %s (%d, %d)"),
+          *archive_name_cursor, archive, status));
 
   global_volno++;
   volno++;
-  cur_ar_file++;
-  if (cur_ar_file == n_ar_files)
+  archive_name_cursor++;
+  if (archive_name_cursor == archive_name_array + archive_names)
     {
-      cur_ar_file = 0;
+      archive_name_cursor = archive_name_array;
       looped = 1;
     }
 
 tryagain:
   if (looped)
     {
-      /* We have to prompt from now on. */
-      if (f_run_script_at_end)
+      /* We have to prompt from now on.  */
+
+      if (info_script_option)
        {
-         closeout_volume_number ();
-         system (info_script);
+         if (volno_file_option)
+           closeout_volume_number ();
+         system (info_script_option);
        }
       else
-       for (;;)
+       while (1)
          {
-           fprintf (msg_file, "\007Prepare volume #%d for %s and hit return: ", global_volno, ar_files[cur_ar_file]);
-           fflush (msg_file);
-           if (fgets (inbuf, sizeof (inbuf), read_file) == 0)
+           char input_buffer[80];
+
+           fprintf (stderr,
+                    _("\007Prepare volume #%d for %s and hit return: "),
+                    global_volno, *archive_name_cursor);
+           fflush (stderr);
+
+           if (fgets (input_buffer, sizeof (input_buffer), read_file) == 0)
              {
-               fprintf (msg_file, "EOF?  What does that mean?");
-               if (cmd_mode != CMD_EXTRACT && cmd_mode != CMD_LIST && cmd_mode != CMD_DIFF)
-                 msg ("Warning:  Archive is INCOMPLETE!");
-               exit (EX_BADARCH);
+               fprintf (stderr, _("EOF where user reply was expected"));
+
+               if (subcommand_option != EXTRACT_SUBCOMMAND
+                   && subcommand_option != LIST_SUBCOMMAND
+                   && subcommand_option != DIFF_SUBCOMMAND)
+                 WARN ((0, 0, _("WARNING: Archive is incomplete")));
+
+               exit (TAREXIT_FAILURE);
              }
-           if (inbuf[0] == '\n' || inbuf[0] == 'y' || inbuf[0] == 'Y')
+           if (input_buffer[0] == '\n'
+               || input_buffer[0] == 'y'
+               || input_buffer[0] == 'Y')
              break;
 
-           switch (inbuf[0])
+           switch (input_buffer[0])
              {
              case '?':
                {
-                 fprintf (msg_file, "\
- n [name]   Give a new filename for the next (and subsequent) volume(s)\n\
+                 fprintf (stderr, _("\
+ n [name]   Give a new file name for the next (and subsequent) volume(s)\n\
  q          Abort tar\n\
  !          Spawn a subshell\n\
- ?          Print this list\n");
+ ?          Print this list\n"));
                }
                break;
 
-             case 'q': /* Quit */
-               fprintf (msg_file, "No new volume; exiting.\n");
-               if (cmd_mode != CMD_EXTRACT && cmd_mode != CMD_LIST && cmd_mode != CMD_DIFF)
-                 msg ("Warning:  Archive is INCOMPLETE!");
-               exit (EX_BADARCH);
+             case 'q':
+               /* Quit.  */
+
+               fprintf (stdlis, _("No new volume; exiting.\n"));
+
+               if (subcommand_option != EXTRACT_SUBCOMMAND
+                   && subcommand_option != LIST_SUBCOMMAND
+                   && subcommand_option != DIFF_SUBCOMMAND)
+                 WARN ((0, 0, _("WARNING: Archive is incomplete")));
+
+               exit (TAREXIT_FAILURE);
+
+             case 'n':
+               /* Get new file name.  */
 
-             case 'n': /* Get new file name */
                {
-                 char *q, *r;
-                 static char *old_name;
-
-                 for (q = &inbuf[1]; *q == ' ' || *q == '\t'; q++)
-                   ;
-                 for (r = q; *r; r++)
-                   if (*r == '\n')
-                     *r = '\0';
-                 old_name = p = (char *) malloc ((unsigned) (strlen (q) + 2));
-                 if (p == 0)
-                   {
-                     msg ("Can't allocate memory for name");
-                     exit (EX_SYSTEM);
-                   }
-                 (void) strcpy (p, q);
-                 ar_files[cur_ar_file] = p;
+                 char *name = &input_buffer[1];
+                 char *cursor;
+
+                 while (*name == ' ' || *name == '\t')
+                   name++;
+                 cursor = name;
+                 while (*cursor && *cursor != '\n')
+                   cursor++;
+                 *cursor = '\0';
+
+                 /* FIXME: the following allocation is never reclaimed.  */
+                 *archive_name_cursor = xstrdup (name);
                }
                break;
 
              case '!':
-#ifdef __MSDOS__
+#if MSDOS
                spawnl (P_WAIT, getenv ("COMSPEC"), "-", 0);
-#else
-               /* JF this needs work! */
+#else /* not MSDOS */
                switch (fork ())
                  {
                  case -1:
-                   msg_perror ("can't fork!");
+                   WARN ((0, errno, _("Cannot fork!")));
                    break;
+
                  case 0:
-                   p = getenv ("SHELL");
-                   if (p == 0)
-                     p = "/bin/sh";
-                   execlp (p, "-sh", "-i", 0);
-                   msg_perror ("can't exec a shell %s", p);
-                   _exit (55);
+                   {
+                     const char *shell = getenv ("SHELL");
+
+                     if (shell == NULL)
+                       shell = "/bin/sh";
+                     execlp (shell, "-sh", "-i", 0);
+                     FATAL_ERROR ((0, errno, _("Cannot exec a shell %s"),
+                                   shell));
+                   }
+
                  default:
-                   wait (0);
+                   {
+                     WAIT_T wait_status;
+
+                     wait (&wait_status);
+                   }
                    break;
                  }
-#endif
+
+               /* FIXME: I'm not sure if that's all that has to be done
+                  here.  (jk)  */
+
+#endif /* not MSDOS */
                break;
              }
          }
     }
 
-
-  if (type == 2 || f_verify)
-    archive = rmtopen (ar_files[cur_ar_file], O_RDWR | O_CREAT, 0666);
-  else if (type == 1)
-    archive = rmtopen (ar_files[cur_ar_file], O_RDONLY, 0666);
-  else if (type == 0)
-    archive = rmtcreat (ar_files[cur_ar_file], 0666);
+  if (verify_option)
+    archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, 0666,
+                      rsh_command_option);
   else
-    archive = -1;
+    switch (access)
+      {
+      case ACCESS_READ:
+       archive = rmtopen (*archive_name_cursor, O_RDONLY, 0666,
+                          rsh_command_option);
+       break;
+
+      case ACCESS_WRITE:
+       if (backup_option)
+         maybe_backup_file (*archive_name_cursor, 1);
+       archive = rmtcreat (*archive_name_cursor, 0666, rsh_command_option);
+       break;
+
+      case ACCESS_UPDATE:
+       archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, 0666,
+                          rsh_command_option);
+       break;
+      }
 
   if (archive < 0)
     {
-      msg_perror ("can't open %s", ar_files[cur_ar_file]);
+      WARN ((0, errno, _("Cannot open %s"), *archive_name_cursor));
+      if (!verify_option && access == ACCESS_WRITE && backup_option)
+       undo_last_backup ();
       goto tryagain;
     }
-#ifdef __MSDOS__
+
+#if MSDOS
   setmode (archive, O_BINARY);
 #endif
-  return 0;
-}
-
-/* this is a useless function that takes a buffer returned by wantbytes
-   and does nothing with it.  If the function called by wantbytes returns
-   an error indicator (non-zero), this function is called for the rest of
-   the file.
- */
-int
-no_op (size, data)
-     int size;
-     char *data;
-{
-  return 0;
-}
 
-/* Some other routine wants SIZE bytes in the archive.  For each chunk of
-   the archive, call FUNC with the size of the chunk, and the address of
-   the chunk it can work with.
- */
-int
-wantbytes (size, func)
-     long size;
-     int (*func) ();
-{
-  char *data;
-  long data_size;
-
-  while (size)
-    {
-      data = findrec ()->charptr;
-      if (data == NULL)
-       {                       /* Check it... */
-         msg ("Unexpected EOF on archive file");
-         return -1;
-       }
-      data_size = endofrecs ()->charptr - data;
-      if (data_size > size)
-       data_size = size;
-      if ((*func) (data_size, data))
-       func = no_op;
-      userec ((union record *) (data + data_size - 1));
-      size -= data_size;
-    }
-  return 0;
+  return 1;
 }
index 62b9c51178f485075fc6e2df2797b2c7c25d4d81..114688fd79d31a73824ee5b984877d72557cf0fb 100644 (file)
 /* Create a tar archive.
-   Copyright (C) 1985, 1992, 1993 Free Software Foundation
+   Copyright (C) 1985, 92, 93, 94, 96, 97 Free Software Foundation, Inc.
+   Written by John Gilmore, on 1985-08-25.
 
-This file is part of GNU Tar.
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any later
+   version.
 
-GNU Tar is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+   Public License for more details.
 
-GNU Tar is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation, Inc.,
+   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
-You should have received a copy of the GNU General Public License
-along with GNU Tar; see the file COPYING.  If not, write to
-the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+#include "system.h"
 
-/*
- * Create a tar archive.
- *
- * Written 25 Aug 1985 by John Gilmore, ihnp4!hoptoad!gnu.
- */
-
-#ifdef _AIX
- #pragma alloca
-#endif
-#include <sys/types.h>
-#include <stdio.h>
-#include <errno.h>
-#ifndef STDC_HEADERS
-extern int errno;
-#endif
-
-#ifdef BSD42
-#include <sys/file.h>
-#else
-#ifndef V7
-#include <fcntl.h>
-#endif
-#endif
-
-#include "tar.h"
-#include "port.h"
-
-#ifndef        __MSDOS__
-#include <pwd.h>
-#include <grp.h>
+#if !MSDOS
+# include <pwd.h>
+# include <grp.h>
 #endif
 
-#if defined (_POSIX_VERSION)
-#include <utime.h>
+#if HAVE_UTIME_H
+# include <utime.h>
 #else
 struct utimbuf
-{
-  long actime;
-  long modtime;
-};
-
+  {
+    long actime;
+    long modtime;
+  };
 #endif
 
-extern struct stat hstat;      /* Stat struct corresponding */
+#include "common.h"
 
-#ifndef __MSDOS__
+#ifndef MSDOS
 extern dev_t ar_dev;
 extern ino_t ar_ino;
 #endif
 
-/* JF */
 extern struct name *gnu_list_name;
 
-/*
- * If there are no symbolic links, there is no lstat().  Use stat().
- */
-#ifndef S_ISLNK
-#define lstat stat
-#endif
-
-extern void print_header ();
-
-union record *start_header ();
-void blank_name_list ();
-int check_exclude ();
-PTR ck_malloc ();
-PTR ck_realloc ();
-void clear_buffer ();
-void close_archive ();
-void collect_and_sort_names ();
-int confirm ();
-int deal_with_sparse ();
-void find_new_file_size ();
-void finish_header ();
-int finish_sparse_file ();
-void finduname ();
-void findgname ();
-int is_dot_or_dotdot ();
-void open_archive ();
-char *name_next ();
-void name_close ();
-void to_oct ();
-void dump_file ();
-void write_dir_file ();
-void write_eot ();
-void write_long ();
-int zero_record ();
-
-/* This code moved from tar.h since create.c is the only file that cares
-   about 'struct link's.  This means that other files might not have to
-   include sys/types.h any more. */
+/* This module is the only one that cares about `struct link's.  */
 
 struct link
   {
@@ -118,122 +53,673 @@ struct link
     char name[1];
   };
 
-struct link *linklist;         /* Points to first link in list */
+struct link *linklist = NULL;  /* points to first link in list */
+\f
+
+/*------------------------------------------------------------------------.
+| Converts long VALUE into a DIGS-digit field at WHERE, including a       |
+| trailing space and room for a NUL.  For example, 3 for DIGS 3 means one |
+| digit, a space, and room for a NUL.                                     |
+|                                                                         |
+| We assume the trailing NUL is already there and don't fill it in.  This |
+| fact is used by start_header and finish_header, so don't change it!     |
+`------------------------------------------------------------------------*/
+
+/* This should be equivalent to: sprintf (WHERE, "%*lo ", DIGS - 2, VALUE);
+   except that sprintf fills in the trailing NUL and we don't.  */
+
+void
+to_oct (long value, int digs, char *where)
+{
+  --digs;                      /* Trailing null slot is left alone */
+  where[--digs] = ' ';         /* put in the space, though */
+
+  /* Produce the digits -- at least one.  */
+
+  do
+    {
+      where[--digs] = '0' + (char) (value & 7);        /* one octal digit */
+      value >>= 3;
+    }
+  while (digs > 0 && value != 0);
+
+  /* Leading spaces, if necessary.  */
+  while (digs > 0)
+    where[--digs] = ' ';
+}
+\f
+/* Writing routines.  */
+
+/*-----------------------------------------------------------------------.
+| Just zeroes out the buffer so we don't confuse ourselves with leftover |
+| data.                                                                         |
+`-----------------------------------------------------------------------*/
+
+static void
+clear_buffer (char *buffer)
+{
+  memset (buffer, 0, BLOCKSIZE);
+}
+
+/*-------------------------------------------------------------------------.
+| Write the EOT block(s).  We actually zero at least one block, through           |
+| the end of the record.  Old tar, as previous versions of GNU tar, writes |
+| garbage after two zeroed blocks.                                        |
+`-------------------------------------------------------------------------*/
+
+void
+write_eot (void)
+{
+  union block *pointer = find_next_block ();
+
+  if (pointer)
+    {
+      int space = available_space_after (pointer);
+
+      memset (pointer->buffer, 0, (size_t) space);
+      set_next_block_after (pointer);
+    }
+}
+
+/*-----------------------------------------------------.
+| Write a GNUTYPE_LONGLINK or GNUTYPE_LONGNAME block.  |
+`-----------------------------------------------------*/
+
+/* FIXME: Cross recursion between start_header and write_long!  */
+
+static union block *start_header PARAMS ((const char *, struct stat *));
+
+static void
+write_long (const char *p, char type)
+{
+  int size = strlen (p) + 1;
+  int bufsize;
+  union block *header;
+  struct stat foo;
+
+  memset (&foo, 0, sizeof foo);
+  foo.st_size = size;
+
+  header = start_header ("././@LongLink", &foo);
+  header->header.typeflag = type;
+  finish_header (header);
+
+  header = find_next_block ();
+
+  bufsize = available_space_after (header);
+
+  while (bufsize < size)
+    {
+      memcpy (header->buffer, p, (size_t) bufsize);
+      p += bufsize;
+      size -= bufsize;
+      set_next_block_after (header + (bufsize - 1) / BLOCKSIZE);
+      header = find_next_block ();
+      bufsize = available_space_after (header);
+    }
+  memcpy (header->buffer, p, (size_t) size);
+  memset (header->buffer + size, 0, (size_t) (bufsize - size));
+  set_next_block_after (header + (size - 1) / BLOCKSIZE);
+}
+\f
+/* Header handling.  */
+
+/*---------------------------------------------------------------------.
+| Make a header block for the file name whose stat info is st.  Return |
+| header pointer for success, NULL if the name is too long.           |
+`---------------------------------------------------------------------*/
+
+static union block *
+start_header (const char *name, struct stat *st)
+{
+  union block *header;
+
+  if (!absolute_names_option)
+    {
+      static int warned_once = 0;
+
+#if MSDOS
+      if (name[1] == ':')
+       {
+         name += 2;
+         if (!warned_once)
+           {
+             warned_once = 1;
+             WARN ((0, 0, _("Removing drive spec from names in the archive")));
+           }
+       }
+#endif
+
+      while (*name == '/')
+       {
+         name++;               /* force relative path */
+         if (!warned_once)
+           {
+             warned_once = 1;
+             WARN ((0, 0, _("\
+Removing leading `/' from absolute path names in the archive")));
+           }
+       }
+    }
+
+  /* Check the file name and put it in the block.  */
+
+  if (strlen (name) >= (size_t) NAME_FIELD_SIZE)
+    write_long (name, GNUTYPE_LONGNAME);
+  header = find_next_block ();
+  memset (header->buffer, 0, sizeof (union block));
+
+  assign_string (&current_file_name, name);
+
+  strncpy (header->header.name, name, NAME_FIELD_SIZE);
+  header->header.name[NAME_FIELD_SIZE - 1] = '\0';
+
+  /* Override some stat fields, if requested to do so.  */
+
+  if (owner_option != (uid_t) -1)
+    st->st_uid = owner_option;
+  if (group_option != (gid_t) -1)
+    st->st_gid = group_option;
+  if (mode_option)
+    st->st_mode = ((st->st_mode & S_IFMT)
+                  | mode_adjust (st->st_mode, mode_option));
+
+  /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
+     for a few tars and came up with the following interoperability
+     matrix:
+
+             WRITER
+       1 2 3 4 5 6 7 8 9   READER
+       . . . . . . . . .   1 = SunOS 4.2 tar
+       # . . # # . . # #   2 = NEC SVR4.0.2 tar
+       . . . # # . . # .   3 = Solaris 2.1 tar
+       . . . . . . . . .   4 = GNU tar 1.11.1
+       . . . . . . . . .   5 = HP-UX 8.07 tar
+       . . . . . . . . .   6 = Ultrix 4.1
+       . . . . . . . . .   7 = AIX 3.2
+       . . . . . . . . .   8 = Hitachi HI-UX 1.03
+       . . . . . . . . .   9 = Omron UNIOS-B 4.3BSD 1.60Beta
+
+            . = works
+            # = ``impossible file type''
+
+     The following mask for old archive removes the `#'s in column 4
+     above, thus making GNU tar both a universal donor and a universal
+     acceptor for Paul's test.  */
+
+  if (archive_format == V7_FORMAT)
+    to_oct ((long) st->st_mode & 07777, 8, header->header.mode);
+  else
+    to_oct ((long) st->st_mode, 8, header->header.mode);
+
+  to_oct ((long) st->st_uid, 8, header->header.uid);
+  to_oct ((long) st->st_gid, 8, header->header.gid);
+  to_oct ((long) st->st_size, 1 + 12, header->header.size);
+  to_oct ((long) st->st_mtime, 1 + 12, header->header.mtime);
+
+  if (incremental_option)
+    if (archive_format == OLDGNU_FORMAT)
+      {
+       to_oct ((long) st->st_atime, 1 + 12, header->oldgnu_header.atime);
+       to_oct ((long) st->st_ctime, 1 + 12, header->oldgnu_header.ctime);
+      }
+
+  header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
+
+  switch (archive_format)
+    {
+    case DEFAULT_FORMAT:
+    case V7_FORMAT:
+      break;
+
+    case OLDGNU_FORMAT:
+      /* Overwrite header->header.magic and header.version in one blow.  */
+      strcpy (header->header.magic, OLDGNU_MAGIC);
+      break;
+
+    case POSIX_FORMAT:
+    case GNU_FORMAT:
+      strncpy (header->header.magic, TMAGIC, TMAGLEN);
+      strncpy (header->header.version, TVERSION, TVERSLEN);
+      break;
+    }
+
+  if (archive_format == V7_FORMAT || numeric_owner_option)
+    {
+      /* header->header.[ug]name are left as the empty string.  */
+    }
+  else
+    {
+      uid_to_uname (st->st_uid, header->header.uname);
+      gid_to_gname (st->st_gid, header->header.gname);
+    }
+
+  return header;
+}
+
+/*-------------------------------------------------------------------------.
+| Finish off a filled-in header block and write it out.  We also print the |
+| file name and/or full info if verbose is on.                            |
+`-------------------------------------------------------------------------*/
+
+void
+finish_header (union block *header)
+{
+  int i, sum;
+  char *p;
+
+  memcpy (header->header.chksum, CHKBLANKS, sizeof (header->header.chksum));
+
+  sum = 0;
+  p = header->buffer;
+  for (i = sizeof (*header); --i >= 0; )
+    /* We can't use unsigned char here because of old compilers, e.g. V7.  */
+    sum += 0xFF & *p++;
+
+  /* Fill in the checksum field.  It's formatted differently from the
+     other fields: it has [6] digits, a null, then a space -- rather than
+     digits, a space, then a null.  We use to_oct then write the null in
+     over to_oct's space.  The final space is already there, from
+     checksumming, and to_oct doesn't modify it.
+
+     This is a fast way to do:
+
+     sprintf(header->header.chksum, "%6o", sum);  */
+
+  to_oct ((long) sum, 8, header->header.chksum);
+  header->header.chksum[6] = '\0';     /* zap the space */
+
+  set_next_block_after (header);
+
+  if (verbose_option
+      && header->header.typeflag != GNUTYPE_LONGLINK
+      && header->header.typeflag != GNUTYPE_LONGNAME)
+    {
+      /* These globals are parameters to print_header, sigh.  */
+
+      current_header = header;
+      /* current_stat is already set up.  */
+      current_format = archive_format;
+      print_header ();
+    }
+}
+\f
+/* Sparse file processing.  */
+
+/*-------------------------------------------------------------------------.
+| Takes a blockful of data and basically cruises through it to see if it's |
+| made *entirely* of zeros, returning a 0 the instant it finds something   |
+| that is a nonzero, i.e., useful data.                                           |
+`-------------------------------------------------------------------------*/
+
+static int
+zero_block_p (char *buffer)
+{
+  int counter;
+
+  for (counter = 0; counter < BLOCKSIZE; counter++)
+    if (buffer[counter] != '\0')
+      return 0;
+  return 1;
+}
+
+/*---.
+| ?  |
+`---*/
+
+static void
+init_sparsearray (void)
+{
+  int counter;
+
+  sp_array_size = 10;
+
+  /* Make room for our scratch space -- initially is 10 elts long.  */
+
+  sparsearray = (struct sp_array *)
+    xmalloc (sp_array_size * sizeof (struct sp_array));
+  for (counter = 0; counter < sp_array_size; counter++)
+    {
+      sparsearray[counter].offset = 0;
+      sparsearray[counter].numbytes = 0;
+    }
+}
+
+/*---.
+| ?  |
+`---*/
+
+static void
+find_new_file_size (int *filesize, int highest_index)
+{
+  int counter;
+
+  *filesize = 0;
+  for (counter = 0;
+       sparsearray[counter].numbytes && counter <= highest_index;
+       counter++)
+    *filesize += sparsearray[counter].numbytes;
+}
+
+/*-----------------------------------------------------------------------.
+| Make one pass over the file NAME, studying where any non-zero data is, |
+| that is, how far into the file each instance of data is, and how many  |
+| bytes are there.  Save this information in the sparsearray, which will |
+| later be translated into header information.                           |
+`-----------------------------------------------------------------------*/
+
+/* There is little point in trimming small amounts of null data at the head
+   and tail of blocks, only avoid dumping full null blocks.  */
+
+/* FIXME: this routine might accept bits of algorithmic cleanup, it is
+   too kludgey for my taste...  */
+
+static int
+deal_with_sparse (char *name, union block *header)
+{
+  long numbytes = 0;
+  long offset = 0;
+  int file;
+  int sparse_index = 0;
+  int count;
+  char buffer[BLOCKSIZE];
+
+  if (archive_format == OLDGNU_FORMAT)
+    header->oldgnu_header.isextended = 0;
+
+  if (file = open (name, O_RDONLY), file < 0)
+    /* This problem will be caught later on, so just return.  */
+    return 0;
+
+  init_sparsearray ();
+  clear_buffer (buffer);
+
+  while (count = read (file, buffer, sizeof buffer), count != 0)
+    {
+      /* Realloc the scratch area as necessary.  FIXME: should reallocate
+        only at beginning of a new instance of non-zero data.  */
+
+      if (sparse_index > sp_array_size - 1)
+       {
+
+         sparsearray = (struct sp_array *)
+           xrealloc (sparsearray,
+                     2 * sp_array_size * sizeof (struct sp_array));
+         sp_array_size *= 2;
+       }
+
+      /* Process one block.  */
+
+      if (count == sizeof buffer)
+
+       if (zero_block_p (buffer))
+         {
+           if (numbytes)
+             {
+               sparsearray[sparse_index++].numbytes = numbytes;
+               numbytes = 0;
+             }
+         }
+       else
+         {
+           if (!numbytes)
+             sparsearray[sparse_index].offset = offset;
+           numbytes += count;
+         }
+
+      else
+
+       /* Since count < sizeof buffer, we have the last bit of the file.  */
+
+       if (!zero_block_p (buffer))
+         {
+           if (!numbytes)
+             sparsearray[sparse_index].offset = offset;
+           numbytes += count;
+         }
+       else
+         /* The next two lines are suggested by Andreas Degert, who says
+            they are required for trailing full blocks to be written to the
+            archive, when all zeroed.  Yet, it seems to me that the case
+            does not apply.  Further, at restore time, the file is not as
+            sparse as it should.  So, some serious cleanup is *also* needed
+            in this area.  Just one more... :-(.  FIXME.  */
+         if (numbytes)
+           numbytes += count;
+
+      /* Prepare for next block.  */
+
+      offset += count;
+      /* FIXME: do not clear unless necessary.  */
+      clear_buffer (buffer);
+    }
+
+  if (numbytes)
+    sparsearray[sparse_index++].numbytes = numbytes;
+  else
+    {
+      sparsearray[sparse_index].offset = offset - 1;
+      sparsearray[sparse_index++].numbytes = 1;
+    }
+
+  close (file);
+  return sparse_index - 1;
+}
+
+/*---.
+| ?  |
+`---*/
+
+static int
+finish_sparse_file (int file, long *sizeleft, long fullsize, char *name)
+{
+  union block *start;
+  int bufsize;
+  int sparse_index = 0;
+  int count;
+  long pos;
+  long nwritten = 0;
+
+  while (*sizeleft > 0)
+    {
+      start = find_next_block ();
+      memset (start->buffer, 0, BLOCKSIZE);
+      bufsize = sparsearray[sparse_index].numbytes;
+      if (!bufsize)
+       {
+         /* We blew it, maybe.  */
+
+         ERROR ((0, 0, _("Wrote %ld of %ld bytes to file %s"),
+                 fullsize - *sizeleft, fullsize, name));
+         break;
+       }
+      pos = lseek (file, sparsearray[sparse_index++].offset, 0);
+
+      /* If the number of bytes to be written here exceeds the size of
+        the temporary buffer, do it in steps.  */
+
+      while (bufsize > BLOCKSIZE)
+       {
+#if 0
+         if (amount_read)
+           {
+             count = read (file, start->buffer + amount_read,
+                           BLOCKSIZE - amount_read);
+             bufsize -= BLOCKSIZE - amount_read;
+             amount_read = 0;
+             set_next_block_after (start);
+             start = find_next_block ();
+             memset (start->buffer, 0, BLOCKSIZE);
+           }
+#endif
+         /* Store the data.  */
+
+         count = read (file, start->buffer, BLOCKSIZE);
+         if (count < 0)
+           {
+             ERROR ((0, errno, _("\
+Read error at byte %ld, reading %d bytes, in file %s"),
+                        fullsize - *sizeleft, bufsize, name));
+             return 1;
+           }
+         bufsize -= count;
+         *sizeleft -= count;
+         set_next_block_after (start);
+         nwritten += BLOCKSIZE;        /* FIXME: ??? */
+         start = find_next_block ();
+         memset (start->buffer, 0, BLOCKSIZE);
+       }
+
+      {
+       char buffer[BLOCKSIZE];
+
+       clear_buffer (buffer);
+       count = read (file, buffer, (size_t) bufsize);
+       memcpy (start->buffer, buffer, BLOCKSIZE);
+      }
 
-static nolinks;                        /* Gets set if we run out of RAM */
+      if (count < 0)
+       {
+         ERROR ((0, errno,
+                 _("Read error at byte %ld, reading %d bytes, in file %s"),
+                 fullsize - *sizeleft, bufsize, name));
+         return 1;
+       }
+#if 0
+      if (amount_read >= BLOCKSIZE)
+       {
+         amount_read = 0;
+         set_next_block_after (start + (count - 1) / BLOCKSIZE);
+         if (count != bufsize)
+           {
+             ERROR ((0, 0,
+                     _("File %s shrunk by %d bytes, padding with zeros"),
+                     name, sizeleft));
+             return 1;
+           }
+         start = find_next_block ();
+       }
+      else
+       amount_read += bufsize;
+#endif
+      nwritten += count;       /* FIXME: ??? */
+      *sizeleft -= count;
+      set_next_block_after (start);
 
-/*
- * "Scratch" space to store the information about a sparse file before
- * writing the info into the header or extended header
- */
-/* struct sp_array      *sparsearray;*/
+    }
+  free (sparsearray);
+#if 0
+  printf (_("Amount actually written is (I hope) %d.\n"), nwritten);
+  set_next_block_after (start + (count - 1) / BLOCKSIZE);
+#endif
+  return 0;
+}
+\f
+/* Main functions of this module.  */
 
-/* number of elts storable in the sparsearray */
-/*int  sparse_array_size = 10;*/
+/*---.
+| ?  |
+`---*/
 
 void
-create_archive ()
+create_archive (void)
 {
-  register char *p;
-  char *name_from_list ();
+  char *p;
 
-  open_archive (0);            /* Open for writing */
+  open_archive (ACCESS_WRITE);
 
-  if (f_gnudump)
+  if (incremental_option)
     {
-      char *buf = ck_malloc (PATH_MAX);
+      char *buffer = xmalloc (PATH_MAX);
       char *q, *bufp;
 
       collect_and_sort_names ();
 
-      while (p = name_from_list ())
+      while (p = name_from_list (), p)
        dump_file (p, -1, 1);
-      /* if(!f_dironly) { */
+
       blank_name_list ();
-      while (p = name_from_list ())
+      while (p = name_from_list (), p)
        {
-         strcpy (buf, p);
+         strcpy (buffer, p);
          if (p[strlen (p) - 1] != '/')
-           strcat (buf, "/");
-         bufp = buf + strlen (buf);
-         for (q = gnu_list_name->dir_contents; q && *q; q += strlen (q) + 1)
+           strcat (buffer, "/");
+         bufp = buffer + strlen (buffer);
+         for (q = gnu_list_name->dir_contents;
+              q && *q;
+              q += strlen (q) + 1)
            {
              if (*q == 'Y')
                {
                  strcpy (bufp, q + 1);
-                 dump_file (buf, -1, 1);
+                 dump_file (buffer, -1, 1);
                }
            }
        }
-      /* } */
-      free (buf);
+      free (buffer);
     }
   else
     {
-      while (p = name_next (1))
+      while (p = name_next (1), p)
        dump_file (p, -1, 1);
     }
 
   write_eot ();
   close_archive ();
-  if (f_gnudump)
+
+  if (listed_incremental_option)
     write_dir_file ();
-  name_close ();
 }
 
-/*
- * Dump a single file.  If it's a directory, recurse.
- * Result is 1 for success, 0 for failure.
- * Sets global "hstat" to stat() output for this file.
- */
+/*----------------------------------------------------------------------.
+| Dump a single file.  Recurse on directories.  Result is nonzero for   |
+| success.  P is file name to dump.  PARENT_DEVICE is device our parent |
+| directory was on.  TOP_LEVEL tells wether we are a toplevel call.     |
+|                                                                       |
+|  Sets global CURRENT_STAT to stat output for this file.               |
+`----------------------------------------------------------------------*/
+
+/* FIXME: One should make sure that for *every* path leading to setting
+   exit_status to failure, a clear diagnostic has been issued.  */
+
 void
-dump_file (p, curdev, toplevel)
-     char *p;                  /* File name to dump */
-     int curdev;               /* Device our parent dir was on */
-     int toplevel;             /* Whether we are a toplevel call */
+dump_file (char *p, int parent_device, int top_level)
 {
-  union record *header;
+  union block *header;
   char type;
-  extern char *save_name;      /* JF for multi-volume support */
-  extern long save_totsize;
-  extern long save_sizeleft;
-  union record *exhdr;
-  char save_linkflag;
-  extern time_t new_time;
-  int critical_error = 0;
+  union block *exhdr;
+  char save_typeflag;
   struct utimbuf restore_times;
-  /*   int sparse_ind = 0;*/
 
+  /* FIXME: `header' and `upperbound' might be used uninitialized in this
+     function.  Reported by Bruno Haible.  */
 
-  if (f_confirm && !confirm ("add", p))
+  if (interactive_option && !confirm ("add", p))
     return;
 
-  /*
-        * Use stat if following (rather than dumping) 4.2BSD's
-        * symbolic links.  Otherwise, use lstat (which, on non-4.2
-        * systems, is #define'd to stat anyway.
-        */
+  /* Use stat if following (rather than dumping) 4.2BSD's symbolic links.
+     Otherwise, use lstat (which falls back to stat if no symbolic links).  */
+
+  if (dereference_option != 0
 #ifdef STX_HIDDEN              /* AIX */
-  if (0 != f_follow_links ?
-      statx (p, &hstat, STATSIZE, STX_HIDDEN) :
-      statx (p, &hstat, STATSIZE, STX_HIDDEN | STX_LINK))
+      ? statx (p, &current_stat, STATSIZE, STX_HIDDEN)
+      : statx (p, &current_stat, STATSIZE, STX_HIDDEN | STX_LINK)
 #else
-  if (0 != f_follow_links ? stat (p, &hstat) : lstat (p, &hstat))
+      ? stat (p, &current_stat) : lstat (p, &current_stat)
 #endif
+      )
     {
-    badperror:
-      msg_perror ("can't add file %s", p);
-    badfile:
-      if (!f_ignore_failed_read || critical_error)
-       errors++;
+      WARN ((0, errno, _("Cannot add file %s"), p));
+      if (!ignore_failed_read_option)
+       exit_status = TAREXIT_FAILURE;
       return;
     }
 
-  restore_times.actime = hstat.st_atime;
-  restore_times.modtime = hstat.st_mtime;
+  restore_times.actime = current_stat.st_atime;
+  restore_times.modtime = current_stat.st_mtime;
 
 #ifdef S_ISHIDDEN
-  if (S_ISHIDDEN (hstat.st_mode))
+  if (S_ISHIDDEN (current_stat.st_mode))
     {
       char *new = (char *) alloca (strlen (p) + 2);
       if (new)
@@ -246,1209 +732,702 @@ dump_file (p, curdev, toplevel)
 #endif
 
   /* See if we only want new files, and check if this one is too old to
-          put in the archive. */
-  if (f_new_files
-      && !f_gnudump
-      && new_time > hstat.st_mtime
-      && !S_ISDIR (hstat.st_mode)
-      && (f_new_files > 1 || new_time > hstat.st_ctime))
+     put in the archive.  */
+
+  if (!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))
     {
-      if (curdev == -1)
-       {
-         msg ("%s: is unchanged; not dumped", p);
-       }
+      if (parent_device == -1)
+       WARN ((0, 0, _("%s: is unchanged; not dumped"), p));
+      /* FIXME: recheck this return.  */
       return;
     }
 
-#ifndef __MSDOS__
-  /* See if we are trying to dump the archive */
-  if (ar_dev && hstat.st_dev == ar_dev && hstat.st_ino == ar_ino)
+#if !MSDOS
+  /* See if we are trying to dump the archive.  */
+
+  if (ar_dev && current_stat.st_dev == ar_dev && current_stat.st_ino == ar_ino)
     {
-      msg ("%s is the archive; not dumped", p);
+      WARN ((0, 0, _("%s is the archive; not dumped"), p));
       return;
     }
 #endif
-  /*
-        * Check for multiple links.
-        *
-        * We maintain a list of all such files that we've written so
-        * far.  Any time we see another, we check the list and
-        * avoid dumping the data again if we've done it once already.
-        */
-  if (hstat.st_nlink > 1
-      && (S_ISREG (hstat.st_mode)
+
+  /* Check for multiple links.
+
+     We maintain a list of all such files that we've written so far.  Any
+     time we see another, we check the list and avoid dumping the data
+     again if we've done it once already.  */
+
+  if (current_stat.st_nlink > 1
+      && (S_ISREG (current_stat.st_mode)
 #ifdef S_ISCTG
-         || S_ISCTG (hstat.st_mode)
+         || S_ISCTG (current_stat.st_mode)
 #endif
 #ifdef S_ISCHR
-         || S_ISCHR (hstat.st_mode)
+         || S_ISCHR (current_stat.st_mode)
 #endif
 #ifdef S_ISBLK
-         || S_ISBLK (hstat.st_mode)
+         || S_ISBLK (current_stat.st_mode)
 #endif
 #ifdef S_ISFIFO
-         || S_ISFIFO (hstat.st_mode)
+         || S_ISFIFO (current_stat.st_mode)
 #endif
       ))
     {
-      register struct link *lp;
+      struct link *lp;
+
+      /* FIXME: First quick and dirty.  Hashing, etc later.  */
 
-      /* First quick and dirty.  Hashing, etc later FIXME */
       for (lp = linklist; lp; lp = lp->next)
-       {
-         if (lp->ino == hstat.st_ino &&
-             lp->dev == hstat.st_dev)
-           {
-             char *link_name = lp->name;
+       if (lp->ino == current_stat.st_ino && lp->dev == current_stat.st_dev)
+         {
+           char *link_name = lp->name;
 
-             /* We found a link. */
-             while (!f_absolute_paths && *link_name == '/')
-               {
-                 static int link_warn = 0;
-
-                 if (!link_warn)
-                   {
-                     msg ("Removing leading / from absolute links");
-                     link_warn++;
-                   }
-                 link_name++;
-               }
-             if (link_name - lp->name >= NAMSIZ)
-               write_long (link_name, LF_LONGLINK);
-             current_link_name = link_name;
+           /* We found a link.  */
 
-             hstat.st_size = 0;
-             header = start_header (p, &hstat);
-             if (header == NULL)
-               {
-                 critical_error = 1;
-                 goto badfile;
-               }
-             strncpy (header->header.arch_linkname,
-                      link_name, NAMSIZ);
+           while (!absolute_names_option && *link_name == '/')
+             {
+               static int warned_once = 0;
+
+               if (!warned_once)
+                 {
+                   warned_once = 1;
+                   WARN ((0, 0, _("\
+Removing leading `/' from absolute links")));
+                 }
+               link_name++;
+             }
+           if (strlen (link_name) >= NAME_FIELD_SIZE)
+             write_long (link_name, GNUTYPE_LONGLINK);
+           assign_string (&current_link_name, link_name);
 
-             /* Force null truncated */
-             header->header.arch_linkname[NAMSIZ - 1] = 0;
+           current_stat.st_size = 0;
+           header = start_header (p, &current_stat);
+           if (header == NULL)
+             {
+               exit_status = TAREXIT_FAILURE;
+               return;
+             }
+           strncpy (header->header.linkname,
+                    link_name, NAME_FIELD_SIZE);
 
-             header->header.linkflag = LF_LINK;
-             finish_header (header);
-             /* FIXME: Maybe remove from list after all links found? */
-             if (f_remove_files)
-               {
-                 if (unlink (p) == -1)
-                   msg_perror ("cannot remove %s", p);
-               }
-             return;           /* We dumped it */
-           }
-       }
+           /* Force null truncated.  */
 
-      /* Not found.  Add it to the list of possible links. */
-      lp = (struct link *) ck_malloc ((unsigned) (sizeof (struct link) + strlen (p)));
-      if (!lp)
-       {
-         if (!nolinks)
-           {
-             msg (
-             "no memory for links, they will be dumped as separate files");
-             nolinks++;
-           }
-       }
-      lp->ino = hstat.st_ino;
-      lp->dev = hstat.st_dev;
+           header->header.linkname[NAME_FIELD_SIZE - 1] = 0;
+
+           header->header.typeflag = LNKTYPE;
+           finish_header (header);
+
+           /* FIXME: Maybe remove from list after all links found?  */
+
+           if (remove_files_option)
+             if (unlink (p) == -1)
+               ERROR ((0, errno, _("Cannot remove %s"), p));
+
+           /* We dumped it.  */
+           return;
+         }
+
+      /* Not found.  Add it to the list of possible links.  */
+
+      lp = (struct link *)
+       xmalloc ((size_t) (sizeof (struct link) + strlen (p)));
+      lp->ino = current_stat.st_ino;
+      lp->dev = current_stat.st_dev;
       strcpy (lp->name, p);
       lp->next = linklist;
       linklist = lp;
     }
 
-  /*
-        * This is not a link to a previously dumped file, so dump it.
-        */
-  if (S_ISREG (hstat.st_mode)
+  /* This is not a link to a previously dumped file, so dump it.  */
+
+  if (S_ISREG (current_stat.st_mode)
 #ifdef S_ISCTG
-      || S_ISCTG (hstat.st_mode)
+      || S_ISCTG (current_stat.st_mode)
 #endif
-    )
+      )
     {
-      int f;                   /* File descriptor */
+      int f;                   /* file descriptor */
       long bufsize, count;
       long sizeleft;
-      register union record *start;
+      union block *start;
       int header_moved;
       char isextended = 0;
       int upperbound;
-      /*               int     end_nulls = 0; */
+#if 0
+      static int cried_once = 0;
+#endif
 
       header_moved = 0;
 
-#ifdef BSD42
-      if (f_sparse_files)
+      if (sparse_option)
        {
-         /*
-                * JK - This is the test for sparseness: whether the
-                * "size" of the file matches the number of blocks
-                * allocated for it.  If there is a smaller number
-                * of blocks that would be necessary to accommodate
-                * a file of this size, we have a sparse file, i.e.,
-                * at least one of those records in the file is just
-                * a useless hole.
-                */
-#ifdef hpux                    /* Nice of HPUX to gratuitiously change it, huh?  - mib */
-         if (hstat.st_size - (hstat.st_blocks * 1024) > 1024)
-#else
-         if (hstat.st_size - (hstat.st_blocks * RECORDSIZE) > RECORDSIZE)
-#endif
+         /* Check the size of the file against the number of blocks
+            allocated for it, counting both data and indirect blocks.
+            If there is a smaller number of blocks that would be
+            necessary to accommodate a file of this size, this is safe
+            to say that we have a sparse file: at least one of those
+            blocks in the file is just a useless hole.  For sparse
+            files not having more hole blocks than indirect blocks, the
+            sparseness will go undetected.  */
+
+         /* tar.h defines ST_NBLOCKS in term of 512 byte sectors, even
+            for HP-UX's which count in 1024 byte units and AIX's which
+            count in 4096 byte units.  So this should work...  */
+
+         /* Bruno Haible sent me these statistics for Linux.  It seems
+            that some filesystems count indirect blocks in st_blocks,
+            while others do not seem to:
+
+            minix-fs   tar: size=7205, st_blocks=18 and ST_NBLOCKS=18
+            extfs      tar: size=7205, st_blocks=18 and ST_NBLOCKS=18
+            ext2fs     tar: size=7205, st_blocks=16 and ST_NBLOCKS=16
+            msdos-fs   tar: size=7205, st_blocks=16 and ST_NBLOCKS=16
+
+            Dick Streefland reports the previous numbers as misleading,
+            because ext2fs use 12 direct blocks, while minix-fs uses only
+            6 direct blocks.  Dick gets:
+
+            ext2       size=20480      ls listed blocks=21
+            minix      size=20480      ls listed blocks=21
+            msdos      size=20480      ls listed blocks=20
+
+            It seems that indirect blocks *are* included in st_blocks.
+            The minix filesystem does not account for phantom blocks in
+            st_blocks, so `du' and `ls -s' give wrong results.  So, the
+            --sparse option would not work on a minix filesystem.  */
+
+         if (current_stat.st_size > ST_NBLOCKS (current_stat) * BLOCKSIZE)
            {
-             int filesize = hstat.st_size;
-             register int i;
+             int filesize = current_stat.st_size;
+             int counter;
 
-             header = start_header (p, &hstat);
+             header = start_header (p, &current_stat);
              if (header == NULL)
                {
-                 critical_error = 1;
-                 goto badfile;
+                 exit_status = TAREXIT_FAILURE;
+                 return;
                }
-             header->header.linkflag = LF_SPARSE;
-             header_moved++;
-
-             /*
-                        * Call the routine that figures out the
-                        * layout of the sparse file in question.
-                        * UPPERBOUND is the index of the last
-                        * element of the "sparsearray," i.e.,
-                        * the number of elements it needed to
-                        * describe the file.
-                        */
+             header->header.typeflag = GNUTYPE_SPARSE;
+             header_moved = 1;
+
+             /* Call the routine that figures out the layout of the
+                sparse file in question.  UPPERBOUND is the index of the
+                last element of the "sparsearray," i.e., the number of
+                elements it needed to describe the file.  */
 
              upperbound = deal_with_sparse (p, header);
 
-             /*
-                        * See if we'll need an extended header
-                        * later
-                        */
-             if (upperbound > SPARSE_IN_HDR - 1)
-               header->header.isextended++;
-             /*
-                        * We store the "real" file size so
-                        * we can show that in case someone wants
-                        * to list the archive, i.e., tar tvf <file>.
-                        * It might be kind of disconcerting if the
-                        * shrunken file size was the one that showed
-                        * up.
-                        */
-             to_oct ((long) hstat.st_size, 1 + 12,
-                     header->header.realsize);
-
-             /*
-                        * This will be the new "size" of the
-                        * file, i.e., the size of the file
-                        * minus the records of holes that we're
-                        * skipping over.
-                        */
+             /* See if we'll need an extended header later.  */
+
+             if (upperbound > SPARSES_IN_OLDGNU_HEADER - 1)
+               header->oldgnu_header.isextended = 1;
+
+             /* We store the "real" file size so we can show that in
+                case someone wants to list the archive, i.e., tar tvf
+                <file>.  It might be kind of disconcerting if the
+                shrunken file size was the one that showed up.  */
+
+             to_oct ((long) current_stat.st_size, 1 + 12,
+                     header->oldgnu_header.realsize);
+
+             /* This will be the new "size" of the file, i.e., the size
+                of the file minus the blocks of holes that we're
+                skipping over.  */
 
              find_new_file_size (&filesize, upperbound);
-             hstat.st_size = filesize;
-             to_oct ((long) filesize, 1 + 12,
-                     header->header.size);
-             /*                                to_oct((long) end_nulls, 1+12,
-                                               header->header.ending_blanks);*/
+             current_stat.st_size = filesize;
+             to_oct ((long) filesize, 1 + 12, header->header.size);
 
-             for (i = 0; i < SPARSE_IN_HDR; i++)
+             for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
                {
-                 if (!sparsearray[i].numbytes)
+                 if (!sparsearray[counter].numbytes)
                    break;
-                 to_oct (sparsearray[i].offset, 1 + 12,
-                         header->header.sp[i].offset);
-                 to_oct (sparsearray[i].numbytes, 1 + 12,
-                         header->header.sp[i].numbytes);
+
+                 to_oct (sparsearray[counter].offset, 1 + 12,
+                         header->oldgnu_header.sp[counter].offset);
+                 to_oct (sparsearray[counter].numbytes, 1 + 12,
+                         header->oldgnu_header.sp[counter].numbytes);
                }
 
            }
        }
-#else
-      upperbound = SPARSE_IN_HDR - 1;
-#endif
+      else
+       upperbound = SPARSES_IN_OLDGNU_HEADER - 1;
+
+      sizeleft = current_stat.st_size;
 
-      sizeleft = hstat.st_size;
-      /* Don't bother opening empty, world readable files. */
-      if (sizeleft > 0 || 0444 != (0444 & hstat.st_mode))
+      /* Don't bother opening empty, world readable files.  Also do not open
+        files when archive is meant for /dev/null.  */
+
+      if (dev_null_output
+         || (sizeleft == 0 && 0444 == (0444 & current_stat.st_mode)))
+       f = -1;
+      else
        {
          f = open (p, O_RDONLY | O_BINARY);
          if (f < 0)
-           goto badperror;
-       }
-      else
-       {
-         f = -1;
+           {
+             WARN ((0, errno, _("Cannot add file %s"), p));
+             if (!ignore_failed_read_option)
+               exit_status = TAREXIT_FAILURE;
+             return;
+           }
        }
 
-      /* If the file is sparse, we've already taken care of this */
+      /* If the file is sparse, we've already taken care of this.  */
+
       if (!header_moved)
        {
-         header = start_header (p, &hstat);
+         header = start_header (p, &current_stat);
          if (header == NULL)
            {
              if (f >= 0)
-               (void) close (f);
-             critical_error = 1;
-             goto badfile;
+               close (f);
+             exit_status = TAREXIT_FAILURE;
+             return;
            }
        }
 #ifdef S_ISCTG
-      /* Mark contiguous files, if we support them */
-      if (f_standard && S_ISCTG (hstat.st_mode))
-       {
-         header->header.linkflag = LF_CONTIG;
-       }
+      /* Mark contiguous files, if we support them.  */
+
+      if (archive_format != V7_FORMAT && S_ISCTG (current_stat.st_mode))
+       header->header.typeflag = CONTTYPE;
 #endif
-      isextended = header->header.isextended;
-      save_linkflag = header->header.linkflag;
+      isextended = header->oldgnu_header.isextended;
+      save_typeflag = header->header.typeflag;
       finish_header (header);
       if (isextended)
        {
-         /*                    int      sum = 0;*/
-         register int i;
-         /*                    register union record *exhdr;*/
-         /*                    int      arraybound = SPARSE_EXT_HDR;*/
-         /* static */ int index_offset = SPARSE_IN_HDR;
+#if 0
+         int sum = 0;
+#endif
+         int counter;
+#if 0
+         union block *exhdr;
+         int arraybound = SPARSES_IN_SPARSE_HEADER;
+#endif
+         /* static */ int index_offset = SPARSES_IN_OLDGNU_HEADER;
 
-       extend:exhdr = findrec ();
+       extend:
+         exhdr = find_next_block ();
 
          if (exhdr == NULL)
            {
-             critical_error = 1;
-             goto badfile;
+             exit_status = TAREXIT_FAILURE;
+             return;
            }
-         bzero (exhdr->charptr, RECORDSIZE);
-         for (i = 0; i < SPARSE_EXT_HDR; i++)
+         memset (exhdr->buffer, 0, BLOCKSIZE);
+         for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
            {
-             if (i + index_offset > upperbound)
+             if (counter + index_offset > upperbound)
                break;
-             to_oct ((long) sparsearray[i + index_offset].numbytes,
+
+             to_oct ((long) sparsearray[counter + index_offset].numbytes,
                      1 + 12,
-                     exhdr->ext_hdr.sp[i].numbytes);
-             to_oct ((long) sparsearray[i + index_offset].offset,
+                     exhdr->sparse_header.sp[counter].numbytes);
+             to_oct ((long) sparsearray[counter + index_offset].offset,
                      1 + 12,
-                     exhdr->ext_hdr.sp[i].offset);
+                     exhdr->sparse_header.sp[counter].offset);
            }
-         userec (exhdr);
-         /*                    sum += i;
-                       if (sum < upperbound)
-                               goto extend;*/
-         if (index_offset + i <= upperbound)
+         set_next_block_after (exhdr);
+#if 0
+         sum += counter;
+         if (sum < upperbound)
+           goto extend;
+#endif
+         if (index_offset + counter <= upperbound)
            {
-             index_offset += i;
-             exhdr->ext_hdr.isextended++;
+             index_offset += counter;
+             exhdr->sparse_header.isextended = 1;
              goto extend;
            }
 
        }
-      if (save_linkflag == LF_SPARSE)
+      if (save_typeflag == GNUTYPE_SPARSE)
        {
-         if (finish_sparse_file (f, &sizeleft, hstat.st_size, p))
+         if (finish_sparse_file (f, &sizeleft, current_stat.st_size, p))
            goto padit;
        }
       else
        while (sizeleft > 0)
          {
-
-           if (f_multivol)
+           if (multi_volume_option)
              {
-               save_name = p;
+               assign_string (&save_name, p);
                save_sizeleft = sizeleft;
-               save_totsize = hstat.st_size;
+               save_totsize = current_stat.st_size;
              }
-           start = findrec ();
+           start = find_next_block ();
 
-           bufsize = endofrecs ()->charptr - start->charptr;
+           bufsize = available_space_after (start);
 
            if (sizeleft < bufsize)
              {
-               /* Last read -- zero out area beyond */
+               /* Last read -- zero out area beyond.  */
+
                bufsize = (int) sizeleft;
-               count = bufsize % RECORDSIZE;
+               count = bufsize % BLOCKSIZE;
                if (count)
-                 bzero (start->charptr + sizeleft,
-                        (int) (RECORDSIZE - count));
+                 memset (start->buffer + sizeleft, 0,
+                         (size_t) (BLOCKSIZE - count));
              }
-           count = read (f, start->charptr, bufsize);
+           if (f < 0)
+             count = bufsize;
+           else
+             count = read (f, start->buffer, (size_t) bufsize);
            if (count < 0)
              {
-               msg_perror ("read error at byte %ld, reading\
- %d bytes, in file %s", hstat.st_size - sizeleft, bufsize, p);
+               ERROR ((0, errno, _("\
+Read error at byte %ld, reading %d bytes, in file %s"),
+                       (long) (current_stat.st_size - sizeleft), bufsize, p));
                goto padit;
              }
            sizeleft -= count;
 
-           /* This is nonportable (the type of userec's arg). */
-           userec (start + (count - 1) / RECORDSIZE);
+           /* This is nonportable (the type of set_next_block_after's arg).  */
+
+           set_next_block_after (start + (count - 1) / BLOCKSIZE);
 
            if (count == bufsize)
              continue;
-           msg ("file %s shrunk by %d bytes, padding with zeros.", p, sizeleft);
-           goto padit;         /* Short read */
+           ERROR ((0, 0, _("File %s shrunk by %d bytes, padding with zeros"),
+                   p, sizeleft));
+           goto padit;         /* short read */
          }
 
-      if (f_multivol)
-       save_name = 0;
+      if (multi_volume_option)
+       assign_string (&save_name, NULL);
 
       if (f >= 0)
-       (void) close (f);
-
-      if (f_remove_files)
+       {
+         close (f);
+         if (atime_preserve_option)
+           utime (p, &restore_times);
+       }
+      if (remove_files_option)
        {
          if (unlink (p) == -1)
-           msg_perror ("cannot remove %s", p);
+           ERROR ((0, errno, _("Cannot remove %s"), p));
        }
-      if (f_atime_preserve)
-       utime (p, &restore_times);
       return;
 
-      /*
-                * File shrunk or gave error, pad out tape to match
-                * the size we specified in the header.
-                */
+      /* File shrunk or gave error, pad out tape to match the size we
+        specified in the header.  */
+
     padit:
       while (sizeleft > 0)
        {
          save_sizeleft = sizeleft;
-         start = findrec ();
-         bzero (start->charptr, RECORDSIZE);
-         userec (start);
-         sizeleft -= RECORDSIZE;
+         start = find_next_block ();
+         memset (start->buffer, 0, BLOCKSIZE);
+         set_next_block_after (start);
+         sizeleft -= BLOCKSIZE;
        }
-      if (f_multivol)
-       save_name = 0;
+      if (multi_volume_option)
+       assign_string (&save_name, NULL);
       if (f >= 0)
-       (void) close (f);
-      if (f_atime_preserve)
-       utime (p, &restore_times);
+       {
+         close (f);
+         if (atime_preserve_option)
+           utime (p, &restore_times);
+       }
       return;
     }
 
 #ifdef S_ISLNK
-  else if (S_ISLNK (hstat.st_mode))
+  else if (S_ISLNK (current_stat.st_mode))
     {
       int size;
-      char *buf = alloca (PATH_MAX + 1);
+      char *buffer = (char *) alloca (PATH_MAX + 1);
 
-      size = readlink (p, buf, PATH_MAX + 1);
+      size = readlink (p, buffer, PATH_MAX + 1);
       if (size < 0)
-       goto badperror;
-      buf[size] = '\0';
-      if (size >= NAMSIZ)
-       write_long (buf, LF_LONGLINK);
-      current_link_name = buf;
-
-      hstat.st_size = 0;       /* Force 0 size on symlink */
-      header = start_header (p, &hstat);
+       {
+         WARN ((0, errno, _("Cannot add file %s"), p));
+         if (!ignore_failed_read_option)
+           exit_status = TAREXIT_FAILURE;
+         return;
+       }
+      buffer[size] = '\0';
+      if (size >= NAME_FIELD_SIZE)
+       write_long (buffer, GNUTYPE_LONGLINK);
+      assign_string (&current_link_name, buffer);
+
+      current_stat.st_size = 0;        /* force 0 size on symlink */
+      header = start_header (p, &current_stat);
       if (header == NULL)
        {
-         critical_error = 1;
-         goto badfile;
+         exit_status = TAREXIT_FAILURE;
+         return;
        }
-      strncpy (header->header.arch_linkname, buf, NAMSIZ);
-      header->header.arch_linkname[NAMSIZ - 1] = '\0';
-      header->header.linkflag = LF_SYMLINK;
-      finish_header (header);  /* Nothing more to do to it */
-      if (f_remove_files)
+      strncpy (header->header.linkname, buffer, NAME_FIELD_SIZE);
+      header->header.linkname[NAME_FIELD_SIZE - 1] = '\0';
+      header->header.typeflag = SYMTYPE;
+      finish_header (header);  /* nothing more to do to it */
+      if (remove_files_option)
        {
          if (unlink (p) == -1)
-           msg_perror ("cannot remove %s", p);
+           ERROR ((0, errno, _("Cannot remove %s"), p));
        }
       return;
     }
-#endif
+#endif /* S_ISLNK */
 
-  else if (S_ISDIR (hstat.st_mode))
+  else if (S_ISDIR (current_stat.st_mode))
     {
-      register DIR *dirp;
-      register struct dirent *d;
+      DIR *directory;
+      struct dirent *entry;
       char *namebuf;
       int buflen;
-      register int len;
-      int our_device = hstat.st_dev;
+      int len;
+      int our_device = current_stat.st_dev;
+
+      /* If this tar program is installed suid root, like for Amanda, the
+        access might look like denied, while it is not really.
+
+        FIXME: I have the feeling this test is done too early.  Couldn't it
+        just be bundled in later actions?  I guess that the proper support
+        of --ignore-failed-read is the key of the current writing.  */
+
+      if (access (p, R_OK) == -1 && geteuid () != 0)
+       {
+         WARN ((0, errno, _("Cannot add directory %s"), p));
+         if (!ignore_failed_read_option)
+           exit_status = TAREXIT_FAILURE;
+         return;
+       }
+
+      /* Build new prototype name.  Ensure exactly one trailing slash.  */
 
-      /* Build new prototype name */
       len = strlen (p);
-      buflen = len + NAMSIZ;
-      namebuf = ck_malloc (buflen + 1);
-      strncpy (namebuf, p, buflen);
-      while (len >= 1 && '/' == namebuf[len - 1])
-       len--;                  /* Delete trailing slashes */
-      namebuf[len++] = '/';    /* Now add exactly one back */
-      namebuf[len] = '\0';     /* Make sure null-terminated */
-
-      /*
-                * Output directory header record with permissions
-                * FIXME, do this AFTER files, to avoid R/O dir problems?
-                * If old archive format, don't write record at all.
-                */
-      if (!f_oldarch)
+      buflen = len + NAME_FIELD_SIZE;
+      namebuf = xmalloc ((size_t) (buflen + 1));
+      strncpy (namebuf, p, (size_t) buflen);
+      while (len >= 1 && namebuf[len - 1] == '/')
+       len--;
+      namebuf[len++] = '/';
+      namebuf[len] = '\0';
+
+      if (1)
        {
-         hstat.st_size = 0;    /* Force 0 size on dir */
-         /*
-                        * If people could really read standard archives,
-                        * this should be:              (FIXME)
-                       header = start_header(f_standard? p: namebuf, &hstat);
-                        * but since they'd interpret LF_DIR records as
-                        * regular files, we'd better put the / on the name.
-                        */
-         header = start_header (namebuf, &hstat);
+         /* The "1" above used to be "archive_format != V7_FORMAT", GNU tar
+            was just not writing directory blocks at all.  Daniel Trinkle
+            writes: ``All old versions of tar I have ever seen have
+            correctly archived an empty directory.  The really old ones I
+            checked included HP-UX 7 and Mt. Xinu More/BSD.  There may be
+            some subtle reason for the exclusion that I don't know, but the
+            current behavior is broken.''  I do not know those subtle
+            reasons either, so until these are reported (anew?), just allow
+            directory blocks to be written even with old archives.  */
+
+         current_stat.st_size = 0;     /* force 0 size on dir */
+
+         /* FIXME: If people could really read standard archives, this
+            should be:
+
+            header
+              = start_header (standard_option ? p : namebuf, &current_stat);
+
+            but since they'd interpret DIRTYPE blocks as regular
+            files, we'd better put the / on the name.  */
+
+         header = start_header (namebuf, &current_stat);
          if (header == NULL)
            {
-             critical_error = 1;
-             goto badfile;     /* eg name too long */
+             exit_status = TAREXIT_FAILURE;
+             return;   /* eg name too long */
            }
 
-         if (f_gnudump)
-           header->header.linkflag = LF_DUMPDIR;
-         else if (f_standard)
-           header->header.linkflag = LF_DIR;
+         if (incremental_option)
+           header->header.typeflag = GNUTYPE_DUMPDIR;
+         else /* if (standard_option) */
+           header->header.typeflag = DIRTYPE;
+
+         /* If we're gnudumping, we aren't done yet so don't close it.  */
 
-         /* If we're gnudumping, we aren't done yet so don't close it. */
-         if (!f_gnudump)
-           finish_header (header);     /* Done with directory header */
+         if (!incremental_option)
+           finish_header (header);     /* done with directory header */
        }
 
-      if (f_gnudump)
+      if (incremental_option && gnu_list_name->dir_contents)
        {
          int sizeleft;
          int totsize;
          int bufsize;
-         union record *start;
+         union block *start;
          int count;
-         char *buf, *p_buf;
+         char *buffer, *p_buffer;
 
-         buf = gnu_list_name->dir_contents;    /* FOO */
+         buffer = gnu_list_name->dir_contents; /* FOO */
          totsize = 0;
-         for (p_buf = buf; p_buf && *p_buf;)
+         for (p_buffer = buffer; p_buffer && *p_buffer;)
            {
              int tmp;
 
-             tmp = strlen (p_buf) + 1;
+             tmp = strlen (p_buffer) + 1;
              totsize += tmp;
-             p_buf += tmp;
+             p_buffer += tmp;
            }
          totsize++;
          to_oct ((long) totsize, 1 + 12, header->header.size);
          finish_header (header);
-         p_buf = buf;
+         p_buffer = buffer;
          sizeleft = totsize;
          while (sizeleft > 0)
            {
-             if (f_multivol)
+             if (multi_volume_option)
                {
-                 save_name = p;
+                 assign_string (&save_name, p);
                  save_sizeleft = sizeleft;
                  save_totsize = totsize;
                }
-             start = findrec ();
-             bufsize = endofrecs ()->charptr - start->charptr;
+             start = find_next_block ();
+             bufsize = available_space_after (start);
              if (sizeleft < bufsize)
                {
                  bufsize = sizeleft;
-                 count = bufsize % RECORDSIZE;
+                 count = bufsize % BLOCKSIZE;
                  if (count)
-                   bzero (start->charptr + sizeleft, RECORDSIZE - count);
+                   memset (start->buffer + sizeleft, 0,
+                          (size_t) (BLOCKSIZE - count));
                }
-             bcopy (p_buf, start->charptr, bufsize);
+             memcpy (start->buffer, p_buffer, (size_t) bufsize);
              sizeleft -= bufsize;
-             p_buf += bufsize;
-             userec (start + (bufsize - 1) / RECORDSIZE);
+             p_buffer += bufsize;
+             set_next_block_after (start + (bufsize - 1) / BLOCKSIZE);
            }
-         if (f_multivol)
-           save_name = 0;
-         if (f_atime_preserve)
+         if (multi_volume_option)
+           assign_string (&save_name, NULL);
+         if (atime_preserve_option)
            utime (p, &restore_times);
          return;
        }
 
-      /* Now output all the files in the directory */
-#if 0
-      if (f_dironly)
-       return;                 /* Unless the cmdline said not to */
-#endif
-      /*
-                * See if we are crossing from one file system to another,
-                * and avoid doing so if the user only wants to dump one file system.
-                */
-      if (f_local_filesys && !toplevel && curdev != hstat.st_dev)
+      /* See if we are about to recurse into a directory, and avoid doing
+        so if the user wants that we do not descend into directories.  */
+
+      if (no_recurse_option)
+       return;
+
+      /* See if we are crossing from one file system to another, and
+        avoid doing so if the user only wants to dump one file system.  */
+
+      if (one_file_system_option && !top_level
+         && parent_device != current_stat.st_dev)
        {
-         if (f_verbose)
-           msg ("%s: is on a different filesystem; not dumped", p);
+         if (verbose_option)
+           WARN ((0, 0, _("%s: On a different filesystem; not dumped"), p));
          return;
        }
 
+      /* Now output all the files in the directory.  */
+
+      errno = 0;               /* FIXME: errno should be read-only */
 
-      errno = 0;
-      dirp = opendir (p);
-      if (!dirp)
+      directory = opendir (p);
+      if (!directory)
        {
-         if (errno)
-           {
-             msg_perror ("can't open directory %s", p);
-           }
-         else
-           {
-             msg ("error opening directory %s",
-                  p);
-           }
+         ERROR ((0, errno, _("Cannot open directory %s"), p));
          return;
        }
 
-      /* Hack to remove "./" from the front of all the file names */
+      /* Hack to remove "./" from the front of all the file names.  */
+
       if (len == 2 && namebuf[0] == '.' && namebuf[1] == '/')
        len = 0;
 
-      /* Should speed this up by cd-ing into the dir, FIXME */
-      while (NULL != (d = readdir (dirp)))
+      /* FIXME: Should speed this up by cd-ing into the dir.  */
+
+      while (entry = readdir (directory), entry)
        {
-         /* Skip . and .. */
-         if (is_dot_or_dotdot (d->d_name))
+         /* Skip `.' and `..'.  */
+
+         if (is_dot_or_dotdot (entry->d_name))
            continue;
 
-         if (NLENGTH (d) + len >= buflen)
+         if ((int) NAMLEN (entry) + len >= buflen)
            {
-             buflen = len + NLENGTH (d);
-             namebuf = ck_realloc (namebuf, buflen + 1);
-             /* namebuf[len]='\0';
-                               msg("file name %s%s too long",
-                                       namebuf, d->d_name);
-                               continue; */
+             buflen = len + NAMLEN (entry);
+             namebuf = (char *) xrealloc (namebuf, (size_t) (buflen + 1));
+#if 0
+             namebuf[len] = '\0';
+             ERROR ((0, 0, _("File name %s%s too long"),
+                     namebuf, entry->d_name));
+             continue;
+#endif
            }
-         strcpy (namebuf + len, d->d_name);
-         if (f_exclude && check_exclude (namebuf))
+         strcpy (namebuf + len, entry->d_name);
+         if (exclude_option && check_exclude (namebuf))
            continue;
          dump_file (namebuf, our_device, 0);
        }
 
-      closedir (dirp);
+      closedir (directory);
       free (namebuf);
-      if (f_atime_preserve)
+      if (atime_preserve_option)
        utime (p, &restore_times);
       return;
     }
 
 #ifdef S_ISCHR
-  else if (S_ISCHR (hstat.st_mode))
-    {
-      type = LF_CHR;
-    }
+  else if (S_ISCHR (current_stat.st_mode))
+    type = CHRTYPE;
 #endif
 
 #ifdef S_ISBLK
-  else if (S_ISBLK (hstat.st_mode))
-    {
-      type = LF_BLK;
-    }
+  else if (S_ISBLK (current_stat.st_mode))
+    type = BLKTYPE;
 #endif
 
-  /* Avoid screwy apollo lossage where S_IFIFO == S_IFSOCK */
+  /* Avoid screwy apollo lossage where S_IFIFO == S_IFSOCK.  */
+
 #if (_ISP__M68K == 0) && (_ISP__A88K == 0) && defined(S_ISFIFO)
-  else if (S_ISFIFO (hstat.st_mode))
-    {
-      type = LF_FIFO;
-    }
+  else if (S_ISFIFO (current_stat.st_mode))
+    type = FIFOTYPE;
 #endif
 
 #ifdef S_ISSOCK
-  else if (S_ISSOCK (hstat.st_mode))
-    {
-      type = LF_FIFO;
-    }
+  else if (S_ISSOCK (current_stat.st_mode))
+    type = FIFOTYPE;
 #endif
+
   else
     goto unknown;
 
-  if (!f_standard)
+  if (archive_format == V7_FORMAT)
     goto unknown;
 
-  hstat.st_size = 0;           /* Force 0 size */
-  header = start_header (p, &hstat);
+  current_stat.st_size = 0;    /* force 0 size */
+  header = start_header (p, &current_stat);
   if (header == NULL)
     {
-      critical_error = 1;
-      goto badfile;            /* eg name too long */
+      exit_status = TAREXIT_FAILURE;
+      return;  /* eg name too long */
     }
 
-  header->header.linkflag = type;
+  header->header.typeflag = type;
+
 #if defined(S_IFBLK) || defined(S_IFCHR)
-  if (type != LF_FIFO)
+  if (type != FIFOTYPE)
     {
-      to_oct ((long) major (hstat.st_rdev), 8,
+      to_oct ((long) major (current_stat.st_rdev), 8,
              header->header.devmajor);
-      to_oct ((long) minor (hstat.st_rdev), 8,
+      to_oct ((long) minor (current_stat.st_rdev), 8,
              header->header.devminor);
     }
 #endif
 
   finish_header (header);
-  if (f_remove_files)
+  if (remove_files_option)
     {
       if (unlink (p) == -1)
-       msg_perror ("cannot remove %s", p);
+       ERROR ((0, errno, _("Cannot remove %s"), p));
     }
   return;
 
 unknown:
-  msg ("%s: Unknown file type; file ignored.", p);
-}
-
-int
-finish_sparse_file (fd, sizeleft, fullsize, name)
-     int fd;
-     long *sizeleft, fullsize;
-     char *name;
-{
-  union record *start;
-  char tempbuf[RECORDSIZE];
-  int bufsize, sparse_ind = 0, count;
-  long pos;
-  long nwritten = 0;
-
-
-  while (*sizeleft > 0)
-    {
-      start = findrec ();
-      bzero (start->charptr, RECORDSIZE);
-      bufsize = sparsearray[sparse_ind].numbytes;
-      if (!bufsize)
-       {                       /* we blew it, maybe */
-         msg ("Wrote %ld of %ld bytes to file %s",
-              fullsize - *sizeleft, fullsize, name);
-         break;
-       }
-      pos = lseek (fd, sparsearray[sparse_ind++].offset, 0);
-      /*
-                * If the number of bytes to be written here exceeds
-                * the size of the temporary buffer, do it in steps.
-                */
-      while (bufsize > RECORDSIZE)
-       {
-         /*                    if (amt_read) {
-                               count = read(fd, start->charptr+amt_read, RECORDSIZE-amt_read);
-                               bufsize -= RECORDSIZE - amt_read;
-                               amt_read = 0;
-                               userec(start);
-                               start = findrec();
-                               bzero(start->charptr, RECORDSIZE);
-                       }*/
-         /* store the data */
-         count = read (fd, start->charptr, RECORDSIZE);
-         if (count < 0)
-           {
-             msg_perror ("read error at byte %ld, reading %d bytes, in file %s",
-                         fullsize - *sizeleft, bufsize, name);
-             return 1;
-           }
-         bufsize -= count;
-         *sizeleft -= count;
-         userec (start);
-         nwritten += RECORDSIZE;       /* XXX */
-         start = findrec ();
-         bzero (start->charptr, RECORDSIZE);
-       }
-
-
-      clear_buffer (tempbuf);
-      count = read (fd, tempbuf, bufsize);
-      bcopy (tempbuf, start->charptr, RECORDSIZE);
-      if (count < 0)
-       {
-         msg_perror ("read error at byte %ld, reading %d bytes, in file %s",
-                     fullsize - *sizeleft, bufsize, name);
-         return 1;
-       }
-      /*               if (amt_read >= RECORDSIZE) {
-                       amt_read = 0;
-                       userec(start+(count-1)/RECORDSIZE);
-                       if (count != bufsize) {
-                               msg("file %s shrunk by %d bytes, padding with zeros.", name, sizeleft);
-                               return 1;
-                       }
-                       start = findrec();
-               } else
-                       amt_read += bufsize;*/
-      nwritten += count;       /* XXX */
-      *sizeleft -= count;
-      userec (start);
-
-    }
-  free (sparsearray);
-  /*   printf ("Amount actually written is (I hope) %d.\n", nwritten); */
-  /*   userec(start+(count-1)/RECORDSIZE);*/
-  return 0;
-
-}
-
-void
-init_sparsearray ()
-{
-  register int i;
-
-  sp_array_size = 10;
-  /*
-        * Make room for our scratch space -- initially is 10 elts long
-        */
-  sparsearray = (struct sp_array *) ck_malloc (sp_array_size * sizeof (struct sp_array));
-  for (i = 0; i < sp_array_size; i++)
-    {
-      sparsearray[i].offset = 0;
-      sparsearray[i].numbytes = 0;
-    }
-}
-
-
-
-/*
- * Okay, we've got a sparse file on our hands -- now, what we need to do is
- * make a pass through the file and carefully note where any data is, i.e.,
- * we want to find how far into the file each instance of data is, and how
- * many bytes are there.  We store this information in the sparsearray,
- * which will later be translated into header information.  For now, we use
- * the sparsearray as convenient storage.
- *
- * As a side note, this routine is a mess.  If I could have found a cleaner
- * way to do it, I would have.  If anyone wants to find a nicer way to do
- * this, feel free.
- */
-
-/* There is little point in trimming small amounts of null data at the */
-/* head and tail of blocks -- it's ok if we only avoid dumping blocks */
-/* of complete null data */
-int
-deal_with_sparse (name, header, nulls_at_end)
-     char *name;
-     union record *header;
-     int nulls_at_end;
-{
-  long numbytes = 0;
-  long offset = 0;
-  /*   long    save_offset;*/
-  int fd;
-  /*   int     current_size = hstat.st_size;*/
-  int sparse_ind = 0, cc;
-  char buf[RECORDSIZE];
-#if 0
-  int read_last_data = 0;      /* did we just read the last record? */
-#endif
-  int amidst_data = 0;
-
-  header->header.isextended = 0;
-  /*
-        * Can't open the file -- this problem will be caught later on,
-        * so just return.
-        */
-  if ((fd = open (name, O_RDONLY)) < 0)
-    return 0;
-
-  init_sparsearray ();
-  clear_buffer (buf);
-
-  while ((cc = read (fd, buf, sizeof buf)) != 0)
-    {
-
-      if (sparse_ind > sp_array_size - 1)
-       {
-
-         /*
-                * realloc the scratch area, since we've run out of room --
-                */
-         sparsearray = (struct sp_array *)
-           ck_realloc (sparsearray,
-                    2 * sp_array_size * (sizeof (struct sp_array)));
-         sp_array_size *= 2;
-       }
-      if (cc == sizeof buf)
-       {
-         if (zero_record (buf))
-           {
-             if (amidst_data)
-               {
-                 sparsearray[sparse_ind++].numbytes
-                   = numbytes;
-                 amidst_data = 0;
-               }
-           }
-         else
-           {                   /* !zero_record(buf) */
-             if (amidst_data)
-               numbytes += cc;
-             else
-               {
-                 amidst_data = 1;
-                 numbytes = cc;
-                 sparsearray[sparse_ind].offset
-                   = offset;
-               }
-           }
-       }
-      else if (cc < sizeof buf)
-       {
-         /* This has to be the last bit of the file, so this */
-         /* is somewhat shorter than the above. */
-         if (!zero_record (buf))
-           {
-             if (!amidst_data)
-               {
-                 amidst_data = 1;
-                 numbytes = cc;
-                 sparsearray[sparse_ind].offset
-                   = offset;
-               }
-             else
-               numbytes += cc;
-           }
-       }
-      offset += cc;
-      clear_buffer (buf);
-    }
-  if (amidst_data)
-    sparsearray[sparse_ind++].numbytes = numbytes;
-  else
-    {
-      sparsearray[sparse_ind].offset = offset-1;
-      sparsearray[sparse_ind++].numbytes = 1;
-    }
-  close (fd);
-
-  return sparse_ind - 1;
-}
-
-/*
- * Just zeroes out the buffer so we don't confuse ourselves with leftover
- * data.
- */
-void
-clear_buffer (buf)
-     char *buf;
-{
-  register int i;
-
-  for (i = 0; i < RECORDSIZE; i++)
-    buf[i] = '\0';
-}
-
-#if 0                          /* I'm leaving this as a monument to Joy Kendall, who wrote it -mib */
-/*
- * JK -
- * This routine takes a character array, and tells where within that array
- * the data can be found.  It skips over any zeros, and sets the first
- * non-zero point in the array to be the "start", and continues until it
- * finds non-data again, which is marked as the "end."  This routine is
- * mainly for 1) seeing how far into a file we must lseek to data, given
- * that we have a sparse file, and 2) determining the "real size" of the
- * file, i.e., the number of bytes in the sparse file that are data, as
- * opposed to the zeros we are trying to skip.
- */
-where_is_data (from, to, buffer)
-     int *from, *to;
-     char *buffer;
-{
-  register int i = 0;
-  register int save_to = *to;
-  int amidst_data = 0;
-
-
-  while (!buffer[i])
-    i++;
-  *from = i;
-
-  if (*from < 16)              /* don't bother */
-    *from = 0;
-  /* keep going to make sure there isn't more real
-          data in this record */
-  while (i < RECORDSIZE)
-    {
-      if (!buffer[i])
-       {
-         if (amidst_data)
-           {
-             save_to = i;
-             amidst_data = 0;
-           }
-         i++;
-       }
-      else if (buffer[i])
-       {
-         if (!amidst_data)
-           amidst_data = 1;
-         i++;
-       }
-    }
-  if (i == RECORDSIZE)
-    *to = i;
-  else
-    *to = save_to;
-
-}
-
-#endif
-
-/* Note that this routine is only called if zero_record returned true */
-#if 0                          /* But we actually don't need it at all. */
-where_is_data (from, to, buffer)
-     int *from, *to;
-     char *buffer;
-{
-  char *fp, *tp;
-
-  for (fp = buffer; !*fp; fp++)
-    ;
-  for (tp = buffer + RECORDSIZE - 1; !*tp; tp--)
-    ;
-  *from = fp - buffer;
-  *to = tp - buffer + 1;
-}
-
-#endif
-
-
-
-/*
- * Takes a recordful of data and basically cruises through it to see if
- * it's made *entirely* of zeros, returning a 0 the instant it finds
- * something that is a non-zero, i.e., useful data.
- */
-int
-zero_record (buffer)
-     char *buffer;
-{
-  register int i;
-
-  for (i = 0; i < RECORDSIZE; i++)
-    if (buffer[i] != '\000')
-      return 0;
-  return 1;
-}
-
-void
-find_new_file_size (filesize, highest_index)
-     int *filesize;
-     int highest_index;
-{
-  register int i;
-
-  *filesize = 0;
-  for (i = 0; sparsearray[i].numbytes && i <= highest_index; i++)
-    *filesize += sparsearray[i].numbytes;
-}
-
-/*
- * Make a header block for the file  name  whose stat info is  st .
- * Return header pointer for success, NULL if the name is too long.
- */
-union record *
-start_header (name, st)
-     char *name;
-     register struct stat *st;
-{
-  register union record *header;
-
-  if (strlen (name) >= NAMSIZ)
-    write_long (name, LF_LONGNAME);
-
-  header = (union record *) findrec ();
-  bzero (header->charptr, sizeof (*header));   /* XXX speed up */
-
-  /*
-        * Check the file name and put it in the record.
-        */
-  if (!f_absolute_paths)
-    {
-      static int warned_once = 0;
-#ifdef __MSDOS__
-      if (name[1] == ':')
-       {
-         name += 2;
-         if (!warned_once++)
-           msg ("Removing drive spec from names in the archive");
-       }
-#endif
-      while ('/' == *name)
-       {
-         name++;               /* Force relative path */
-         if (!warned_once++)
-           msg ("Removing leading / from absolute path names in the archive.");
-       }
-    }
-  current_file_name = name;
-  strncpy (header->header.arch_name, name, NAMSIZ);
-  header->header.arch_name[NAMSIZ - 1] = '\0';
-
-  to_oct ((long) (f_oldarch ? (st->st_mode & 07777) : st->st_mode),
-         8, header->header.mode);
-  to_oct ((long) st->st_uid, 8, header->header.uid);
-  to_oct ((long) st->st_gid, 8, header->header.gid);
-  to_oct ((long) st->st_size, 1 + 12, header->header.size);
-  to_oct ((long) st->st_mtime, 1 + 12, header->header.mtime);
-  /* header->header.linkflag is left as null */
-  if (f_gnudump)
-    {
-      to_oct ((long) st->st_atime, 1 + 12, header->header.atime);
-      to_oct ((long) st->st_ctime, 1 + 12, header->header.ctime);
-    }
-
-#ifndef NONAMES
-  /* Fill in new Unix Standard fields if desired. */
-  if (f_standard)
-    {
-      header->header.linkflag = LF_NORMAL;     /* New default */
-      strcpy (header->header.magic, TMAGIC);   /* Mark as Unix Std */
-      finduname (header->header.uname, st->st_uid);
-      findgname (header->header.gname, st->st_gid);
-    }
-#endif
-  return header;
-}
-
-/*
- * Finish off a filled-in header block and write it out.
- * We also print the file name and/or full info if verbose is on.
- */
-void
-finish_header (header)
-     register union record *header;
-{
-  register int i, sum;
-  register char *p;
-
-  bcopy (CHKBLANKS, header->header.chksum, sizeof (header->header.chksum));
-
-  sum = 0;
-  p = header->charptr;
-  for (i = sizeof (*header); --i >= 0;)
-    {
-      /*
-                * We can't use unsigned char here because of old compilers,
-                * e.g. V7.
-                */
-      sum += 0xFF & *p++;
-    }
-
-  /*
-        * Fill in the checksum field.  It's formatted differently
-        * from the other fields:  it has [6] digits, a null, then a
-        * space -- rather than digits, a space, then a null.
-        * We use to_oct then write the null in over to_oct's space.
-        * The final space is already there, from checksumming, and
-        * to_oct doesn't modify it.
-        *
-        * This is a fast way to do:
-        * (void) sprintf(header->header.chksum, "%6o", sum);
-        */
-  to_oct ((long) sum, 8, header->header.chksum);
-  header->header.chksum[6] = '\0';     /* Zap the space */
-
-  userec (header);
-
-  if (f_verbose)
-    {
-      extern union record *head;/* Points to current tape header */
-      extern int head_standard;        /* Tape header is in ANSI format */
-
-      /* These globals are parameters to print_header, sigh */
-      head = header;
-      /* hstat is already set up */
-      head_standard = f_standard;
-      print_header ();
-    }
-
-  return;
-}
-
-
-/*
- * Quick and dirty octal conversion.
- * Converts long "value" into a "digs"-digit field at "where",
- * including a trailing space and room for a null.  "digs"==3 means
- * 1 digit, a space, and room for a null.
- *
- * We assume the trailing null is already there and don't fill it in.
- * This fact is used by start_header and finish_header, so don't change it!
- *
- * This should be equivalent to:
- *     (void) sprintf(where, "%*lo ", digs-2, value);
- * except that sprintf fills in the trailing null and we don't.
- */
-void
-to_oct (value, digs, where)
-     register long value;
-     register int digs;
-     register char *where;
-{
-
-  --digs;                      /* Trailing null slot is left alone */
-  where[--digs] = ' ';         /* Put in the space, though */
-
-  /* Produce the digits -- at least one */
-  do
-    {
-      where[--digs] = '0' + (char) (value & 7);        /* one octal digit */
-      value >>= 3;
-    }
-  while (digs > 0 && value != 0);
-
-  /* Leading spaces, if necessary */
-  while (digs > 0)
-    where[--digs] = ' ';
-
-}
-
-
-/*
- * Write the EOT record(s).
- * We actually zero at least one record, through the end of the block.
- * Old tar writes garbage after two zeroed records -- and PDtar used to.
- */
-void
-write_eot ()
-{
-  union record *p;
-  int bufsize;
-
-  p = findrec ();
-  if (p)
-    {
-      bufsize = endofrecs ()->charptr - p->charptr;
-      bzero (p->charptr, bufsize);
-      userec (p);
-    }
-}
-
-/* Write a LF_LONGLINK or LF_LONGNAME record. */
-void
-write_long (p, type)
-     char *p;
-     char type;
-{
-  int size = strlen (p) + 1;
-  int bufsize;
-  union record *header;
-  struct stat foo;
-
-
-  bzero (&foo, sizeof foo);
-  foo.st_size = size;
-
-  header = start_header ("././@LongLink", &foo);
-  header->header.linkflag = type;
-  finish_header (header);
-
-  header = findrec ();
-
-  bufsize = endofrecs ()->charptr - header->charptr;
-
-  while (bufsize < size)
-    {
-      bcopy (p, header->charptr, bufsize);
-      p += bufsize;
-      size -= bufsize;
-      userec (header + (bufsize - 1) / RECORDSIZE);
-      header = findrec ();
-      bufsize = endofrecs ()->charptr - header->charptr;
-    }
-  bcopy (p, header->charptr, size);
-  bzero (header->charptr + size, bufsize - size);
-  userec (header + (size - 1) / RECORDSIZE);
+  ERROR ((0, 0, _("%s: Unknown file type; file ignored"), p));
 }
This page took 0.135961 seconds and 4 git commands to generate.