summaryrefslogtreecommitdiffstats
path: root/contrib/tar/src
diff options
context:
space:
mode:
authorsobomax <sobomax@FreeBSD.org>2002-06-04 10:37:47 +0000
committersobomax <sobomax@FreeBSD.org>2002-06-04 10:37:47 +0000
commit0f70d6636c8f836f50cc56b9ea9b8dc51cd12dbe (patch)
tree8e3e6da9ce2dfb3d403e8ed0fab9168ce589ca80 /contrib/tar/src
downloadFreeBSD-src-0f70d6636c8f836f50cc56b9ea9b8dc51cd12dbe.zip
FreeBSD-src-0f70d6636c8f836f50cc56b9ea9b8dc51cd12dbe.tar.gz
Virgin import (trimmed) of GNU Tar version 1.13.25.
Diffstat (limited to 'contrib/tar/src')
-rw-r--r--contrib/tar/src/arith.h27
-rw-r--r--contrib/tar/src/buffer.c1600
-rw-r--r--contrib/tar/src/common.h578
-rw-r--r--contrib/tar/src/compare.c817
-rw-r--r--contrib/tar/src/create.c1550
-rw-r--r--contrib/tar/src/delete.c364
-rw-r--r--contrib/tar/src/extract.c1313
-rw-r--r--contrib/tar/src/incremen.c584
-rw-r--r--contrib/tar/src/list.c1191
-rw-r--r--contrib/tar/src/mangle.c121
-rw-r--r--contrib/tar/src/misc.c847
-rw-r--r--contrib/tar/src/names.c941
-rw-r--r--contrib/tar/src/rmt.c576
-rw-r--r--contrib/tar/src/rmt.h93
-rw-r--r--contrib/tar/src/rtapelib.c718
-rw-r--r--contrib/tar/src/system.h587
-rw-r--r--contrib/tar/src/tar.c1354
-rw-r--r--contrib/tar/src/tar.h235
-rw-r--r--contrib/tar/src/update.c195
19 files changed, 13691 insertions, 0 deletions
diff --git a/contrib/tar/src/arith.h b/contrib/tar/src/arith.h
new file mode 100644
index 0000000..a1e532f
--- /dev/null
+++ b/contrib/tar/src/arith.h
@@ -0,0 +1,27 @@
+/* Long integers, for GNU tar.
+ Copyright 1999 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* Handle large integers for calculating big tape lengths and the
+ like. In practice, double precision does for now. On the vast
+ majority of machines, it counts up to 2**52 bytes without any loss
+ of information, and counts up to 2**62 bytes if data are always
+ blocked in 1 kB boundaries. We'll need arbitrary precision
+ arithmetic anyway once we get into the 2**64 range, so there's no
+ point doing anything fancy before then. */
+
+#define TARLONG_FORMAT "%.0f"
+typedef double tarlong;
diff --git a/contrib/tar/src/buffer.c b/contrib/tar/src/buffer.c
new file mode 100644
index 0000000..1d164b4
--- /dev/null
+++ b/contrib/tar/src/buffer.c
@@ -0,0 +1,1600 @@
+/* Buffer management for tar.
+
+ Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+
+#include <signal.h>
+
+#if MSDOS
+# include <process.h>
+#endif
+
+#if XENIX
+# include <sys/inode.h>
+#endif
+
+#include <fnmatch.h>
+#include <human.h>
+#include <quotearg.h>
+
+#include "common.h"
+#include "rmt.h"
+
+#define PREAD 0 /* read file descriptor from pipe() */
+#define PWRITE 1 /* write file descriptor from pipe() */
+
+/* Number of retries before giving up on read. */
+#define READ_ERROR_MAX 10
+
+/* Globbing pattern to append to volume label if initial match failed. */
+#define VOLUME_LABEL_APPEND " Volume [1-9]*"
+
+/* Variables. */
+
+static tarlong prev_written; /* bytes written on previous volumes */
+static tarlong bytes_written; /* bytes written on this volume */
+
+/* FIXME: The following variables should ideally be static to this
+ module. However, this cannot be done yet. The cleanup continues! */
+
+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 */
+off_t records_read; /* number of records read from this archive */
+off_t records_written; /* likewise, for records written */
+
+static struct stat archive_stat; /* stat block for archive file */
+
+static off_t record_start_block; /* block ordinal at record_start */
+
+/* Where we write list messages (not errors, not interactions) to. Stdout
+ unless we're writing a pipe, in which case stderr. */
+FILE *stdlis;
+
+static void backspace_output PARAMS ((void));
+static int new_volume PARAMS ((enum access_mode));
+static void archive_write_error PARAMS ((ssize_t)) __attribute__ ((noreturn));
+static void archive_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
+
+/* PID of child program, if compress_option or remote archive access. */
+static pid_t child_pid;
+
+/* Error recovery stuff */
+static int read_error_count;
+
+/* Have we hit EOF yet? */
+static int hit_eof;
+
+/* Checkpointing counter */
+static int checkpoint;
+
+/* 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; /* 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 */
+off_t save_totsize; /* total size of file we are writing, only
+ valid if save_name is nonzero */
+off_t save_sizeleft; /* where we are in the file we are writing,
+ only valid if save_name is nonzero */
+
+bool write_archive_to_stdout;
+
+/* Used by flush_read and flush_write to store the real info about saved
+ names. */
+static char *real_s_name;
+static off_t real_s_totsize;
+static off_t real_s_sizeleft;
+
+/* Functions. */
+
+void
+print_total_written (void)
+{
+ tarlong written = prev_written + bytes_written;
+ char bytes[sizeof (tarlong) * CHAR_BIT];
+ char abbr[LONGEST_HUMAN_READABLE + 1];
+ char rate[LONGEST_HUMAN_READABLE + 1];
+ double seconds;
+
+#if HAVE_CLOCK_GETTIME
+ struct timespec now;
+ if (clock_gettime (CLOCK_REALTIME, &now) == 0)
+ seconds = ((now.tv_sec - start_timespec.tv_sec)
+ + (now.tv_nsec - start_timespec.tv_nsec) / 1e9);
+ else
+#endif
+ seconds = time (0) - start_time;
+
+ sprintf (bytes, TARLONG_FORMAT, written);
+
+ /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*". */
+ fprintf (stderr, _("Total bytes written: %s (%sB, %sB/s)\n"), bytes,
+ human_readable ((uintmax_t) written, abbr, 1, -1024),
+ (0 < seconds && written / seconds < (uintmax_t) -1
+ ? human_readable ((uintmax_t) (written / seconds), rate, 1, -1024)
+ : "?"));
+}
+
+/* Compute and return the block ordinal at current_block. */
+off_t
+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 (void)
+{
+ if (hit_eof)
+ {
+ hit_eof = 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 block.
+ Return zero for EOF. Once we have returned zero, we just keep returning
+ it, to avoid accidentally going on to the next file on the tape. */
+union block *
+find_next_block (void)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return 0;
+ flush_archive ();
+ if (current_block == record_end)
+ {
+ hit_eof = 1;
+ return 0;
+ }
+ }
+ return current_block;
+}
+
+/* Indicate that we have used all blocks up thru BLOCK.
+ FIXME: should the arg have an off-by-1? */
+void
+set_next_block_after (union block *block)
+{
+ 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. */
+size_t
+available_space_after (union block *pointer)
+{
+ return record_end->buffer - pointer->buffer;
+}
+
+/* Close file having descriptor FD, and abort if close unsuccessful. */
+static void
+xclose (int fd)
+{
+ if (close (fd) != 0)
+ close_error (_("(pipe)"));
+}
+
+/* Duplicate file descriptor FROM into becoming INTO.
+ INTO is closed first and has to be the next available slot. */
+static void
+xdup2 (int from, int into)
+{
+ if (from != into)
+ {
+ int status = close (into);
+
+ if (status != 0 && errno != EBADF)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, _("Cannot close")));
+ }
+ status = dup (from);
+ if (status != into)
+ {
+ if (status < 0)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, _("Cannot dup")));
+ }
+ abort ();
+ }
+ xclose (from);
+ }
+}
+
+#if MSDOS
+
+/* Set ARCHIVE for writing, then compressing an archive. */
+static void
+child_open_for_compress (void)
+{
+ FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
+}
+
+/* Set ARCHIVE for uncompressing, then reading an archive. */
+static void
+child_open_for_uncompress (void)
+{
+ FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
+}
+
+#else /* not MSDOS */
+
+/* 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). */
+static int
+is_regular_file (const char *name)
+{
+ struct stat stbuf;
+
+ if (stat (name, &stbuf) == 0)
+ return S_ISREG (stbuf.st_mode);
+ else
+ return errno == ENOENT;
+}
+
+static ssize_t
+write_archive_buffer (void)
+{
+ ssize_t status;
+ ssize_t written = 0;
+
+ while (0 <= (status = rmtwrite (archive, record_start->buffer + written,
+ record_size - written)))
+ {
+ written += status;
+ if (written == record_size
+ || _isrmt (archive)
+ || ! (S_ISFIFO (archive_stat.st_mode)
+ || S_ISSOCK (archive_stat.st_mode)))
+ break;
+ }
+
+ return written ? written : status;
+}
+
+/* Set ARCHIVE for writing, then compressing an archive. */
+static void
+child_open_for_compress (void)
+{
+ int parent_pipe[2];
+ int child_pipe[2];
+ pid_t grandchild_pid;
+ int wait_status;
+
+ xpipe (parent_pipe);
+ child_pid = xfork ();
+
+ if (child_pid > 0)
+ {
+ /* The parent tar is still here! Just clean up. */
+
+ archive = parent_pipe[PWRITE];
+ xclose (parent_pipe[PREAD]);
+ return;
+ }
+
+ /* The new born child tar is here! */
+
+ program_name = _("tar (child)");
+
+ xdup2 (parent_pipe[PREAD], STDIN_FILENO);
+ 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]))
+ {
+ if (backup_option)
+ maybe_backup_file (archive_name_array[0], 1);
+
+ /* We don't need a grandchild tar. Open the archive and launch the
+ compressor. */
+
+ archive = creat (archive_name_array[0], MODE_RW);
+ if (archive < 0)
+ {
+ int saved_errno = errno;
+
+ if (backup_option)
+ undo_last_backup ();
+ errno = saved_errno;
+ open_fatal (archive_name_array[0]);
+ }
+ xdup2 (archive, STDOUT_FILENO);
+ execlp (use_compress_program_option, use_compress_program_option,
+ (char *) 0);
+ exec_fatal (use_compress_program_option);
+ }
+
+ /* We do need a grandchild tar. */
+
+ xpipe (child_pipe);
+ grandchild_pid = xfork ();
+
+ if (grandchild_pid == 0)
+ {
+ /* The newborn grandchild tar is here! Launch the compressor. */
+
+ program_name = _("tar (grandchild)");
+
+ xdup2 (child_pipe[PWRITE], STDOUT_FILENO);
+ xclose (child_pipe[PREAD]);
+ execlp (use_compress_program_option, use_compress_program_option,
+ (char *) 0);
+ exec_fatal (use_compress_program_option);
+ }
+
+ /* The child tar is still here! */
+
+ /* Prepare for reblocking the data from the compressor into the archive. */
+
+ xdup2 (child_pipe[PREAD], STDIN_FILENO);
+ xclose (child_pipe[PWRITE]);
+
+ if (strcmp (archive_name_array[0], "-") == 0)
+ archive = STDOUT_FILENO;
+ else
+ {
+ archive = rmtcreat (archive_name_array[0], MODE_RW, rsh_command_option);
+ if (archive < 0)
+ open_fatal (archive_name_array[0]);
+ }
+
+ /* Let's read out of the stdin pipe and write an archive. */
+
+ while (1)
+ {
+ ssize_t status = 0;
+ char *cursor;
+ size_t length;
+
+ /* Assemble a record. */
+
+ for (length = 0, cursor = record_start->buffer;
+ length < record_size;
+ length += status, cursor += status)
+ {
+ size_t size = record_size - length;
+
+ if (size < BLOCKSIZE)
+ size = BLOCKSIZE;
+ status = safe_read (STDIN_FILENO, cursor, size);
+ if (status <= 0)
+ break;
+ }
+
+ if (status < 0)
+ read_fatal (use_compress_program_option);
+
+ /* Copy the record. */
+
+ if (status == 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)
+ {
+ memset (record_start->buffer + length, 0, record_size - length);
+ status = write_archive_buffer ();
+ if (status != record_size)
+ archive_write_error (status);
+ }
+
+ /* There is nothing else to read, break out. */
+ break;
+ }
+
+ status = write_archive_buffer ();
+ if (status != record_size)
+ archive_write_error (status);
+ }
+
+#if 0
+ close_archive ();
+#endif
+
+ /* Propagate any failure of the grandchild back to the parent. */
+
+ while (waitpid (grandchild_pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ {
+ kill (child_pid, WTERMSIG (wait_status));
+ exit_status = TAREXIT_FAILURE;
+ }
+ else if (WEXITSTATUS (wait_status) != 0)
+ exit_status = WEXITSTATUS (wait_status);
+
+ 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];
+ pid_t grandchild_pid;
+ int wait_status;
+
+ xpipe (parent_pipe);
+ child_pid = xfork ();
+
+ 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 newborn child tar is here! */
+
+ program_name = _("tar (child)");
+
+ xdup2 (parent_pipe[PWRITE], STDOUT_FILENO);
+ 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, MODE_RW);
+ if (archive < 0)
+ open_fatal (archive_name_array[0]);
+ xdup2 (archive, STDIN_FILENO);
+ execlp (use_compress_program_option, use_compress_program_option,
+ "-d", (char *) 0);
+ exec_fatal (use_compress_program_option);
+ }
+
+ /* We do need a grandchild tar. */
+
+ xpipe (child_pipe);
+ grandchild_pid = xfork ();
+
+ if (grandchild_pid == 0)
+ {
+ /* The newborn grandchild tar is here! Launch the uncompressor. */
+
+ program_name = _("tar (grandchild)");
+
+ xdup2 (child_pipe[PREAD], STDIN_FILENO);
+ xclose (child_pipe[PWRITE]);
+ execlp (use_compress_program_option, use_compress_program_option,
+ "-d", (char *) 0);
+ exec_fatal (use_compress_program_option);
+ }
+
+ /* The child tar is still here! */
+
+ /* Prepare for unblocking the data from the archive into the
+ uncompressor. */
+
+ xdup2 (child_pipe[PWRITE], STDOUT_FILENO);
+ xclose (child_pipe[PREAD]);
+
+ if (strcmp (archive_name_array[0], "-") == 0)
+ archive = STDIN_FILENO;
+ else
+ archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY,
+ MODE_RW, rsh_command_option);
+ if (archive < 0)
+ open_fatal (archive_name_array[0]);
+
+ /* Let's read the archive and pipe it into stdout. */
+
+ while (1)
+ {
+ char *cursor;
+ size_t maximum;
+ size_t count;
+ ssize_t status;
+
+ read_error_count = 0;
+
+ error_loop:
+ status = rmtread (archive, record_start->buffer, record_size);
+ if (status < 0)
+ {
+ archive_read_error ();
+ goto error_loop;
+ }
+ if (status == 0)
+ break;
+ cursor = record_start->buffer;
+ maximum = status;
+ while (maximum)
+ {
+ count = maximum < BLOCKSIZE ? maximum : BLOCKSIZE;
+ if (full_write (STDOUT_FILENO, cursor, count) != count)
+ write_error (use_compress_program_option);
+ cursor += count;
+ maximum -= count;
+ }
+ }
+
+ xclose (STDOUT_FILENO);
+#if 0
+ close_archive ();
+#endif
+
+ /* Propagate any failure of the grandchild back to the parent. */
+
+ while (waitpid (grandchild_pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ {
+ kill (child_pid, WTERMSIG (wait_status));
+ exit_status = TAREXIT_FAILURE;
+ }
+ else if (WEXITSTATUS (wait_status) != 0)
+ exit_status = WEXITSTATUS (wait_status);
+
+ exit (exit_status);
+}
+
+#endif /* not MSDOS */
+
+/* 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)
+{
+ char *string;
+ int result;
+
+ if (! memchr (label->header.name, '\0', sizeof label->header.name))
+ return 0;
+
+ if (fnmatch (volume_label_option, label->header.name, 0) == 0)
+ return 1;
+
+ 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;
+}
+
+/* Open an archive file. The argument specifies whether we are
+ reading or writing, or both. */
+void
+open_archive (enum access_mode wanted_access)
+{
+ int backed_up_flag = 0;
+
+ stdlis = to_stdout_option ? stderr : stdout;
+
+ 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")));
+
+ current_file_name = 0;
+ current_link_name = 0;
+ save_name = 0;
+ real_s_name = 0;
+
+ if (multi_volume_option)
+ {
+ if (verify_option)
+ FATAL_ERROR ((0, 0, _("Cannot verify multi-volume archives")));
+ record_start = valloc (record_size + (2 * BLOCKSIZE));
+ if (record_start)
+ record_start += 2;
+ }
+ else
+ record_start = valloc (record_size);
+ if (!record_start)
+ FATAL_ERROR ((0, 0, _("Cannot allocate memory for blocking factor %d"),
+ blocking_factor));
+
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+ /* When updating the archive, we start with reading. */
+ access_mode = wanted_access == ACCESS_UPDATE ? ACCESS_READ : wanted_access;
+
+ if (use_compress_program_option)
+ {
+ 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 (wanted_access)
+ {
+ 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;
+ }
+
+ if (wanted_access == ACCESS_WRITE
+ && strcmp (archive_name_array[0], "-") == 0)
+ stdlis = stderr;
+ }
+ else if (strcmp (archive_name_array[0], "-") == 0)
+ {
+ read_full_records_option = 1; /* could be a pipe, be safe */
+ if (verify_option)
+ FATAL_ERROR ((0, 0, _("Cannot verify stdin/stdout archive")));
+
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ archive = STDIN_FILENO;
+ break;
+
+ case ACCESS_WRITE:
+ archive = STDOUT_FILENO;
+ stdlis = stderr;
+ break;
+
+ case ACCESS_UPDATE:
+ archive = STDIN_FILENO;
+ stdlis = stderr;
+ write_archive_to_stdout = 1;
+ break;
+ }
+ }
+ else if (verify_option)
+ archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+ else
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY,
+ MODE_RW, 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], MODE_RW,
+ rsh_command_option);
+ break;
+
+ case ACCESS_UPDATE:
+ archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+ break;
+ }
+
+ if (archive < 0
+ || (! _isrmt (archive) && fstat (archive, &archive_stat) < 0))
+ {
+ int saved_errno = errno;
+
+ if (backed_up_flag)
+ undo_last_backup ();
+ errno = saved_errno;
+ open_fatal (archive_name_array[0]);
+ }
+
+#if !MSDOS
+
+ /* Detect if outputting to "/dev/null". */
+ {
+ static char const dev_null[] = "/dev/null";
+ struct stat dev_null_stat;
+
+ dev_null_output =
+ (strcmp (archive_name_array[0], dev_null) == 0
+ || (! _isrmt (archive)
+ && S_ISCHR (archive_stat.st_mode)
+ && stat (dev_null, &dev_null_stat) == 0
+ && archive_stat.st_dev == dev_null_stat.st_dev
+ && archive_stat.st_ino == dev_null_stat.st_ino));
+ }
+
+ if (!_isrmt (archive) && S_ISREG (archive_stat.st_mode))
+ {
+ ar_dev = archive_stat.st_dev;
+ ar_ino = archive_stat.st_ino;
+ }
+ else
+ ar_dev = 0;
+
+#endif /* not MSDOS */
+
+#if MSDOS
+ setmode (archive, O_BINARY);
+#endif
+
+ switch (wanted_access)
+ {
+ case ACCESS_UPDATE:
+ records_written = 0;
+ case ACCESS_READ:
+ records_read = 0;
+ record_end = record_start; /* set up for 1st record = # 0 */
+ find_next_block (); /* read it in, check for EOF */
+
+ if (volume_label_option)
+ {
+ union block *label = find_next_block ();
+
+ if (!label)
+ FATAL_ERROR ((0, 0, _("Archive not labeled to match %s"),
+ quote (volume_label_option)));
+ if (!check_label_pattern (label))
+ FATAL_ERROR ((0, 0, _("Volume %s does not match %s"),
+ quote_n (0, label->header.name),
+ quote_n (1, volume_label_option)));
+ }
+ break;
+
+ case ACCESS_WRITE:
+ records_written = 0;
+ if (volume_label_option)
+ {
+ memset (record_start, 0, BLOCKSIZE);
+ if (multi_volume_option)
+ sprintf (record_start->header.name, "%s Volume 1",
+ volume_label_option);
+ else
+ strcpy (record_start->header.name, volume_label_option);
+
+ assign_string (&current_file_name, record_start->header.name);
+
+ record_start->header.typeflag = GNUTYPE_VOLHDR;
+ TIME_TO_CHARS (start_time, record_start->header.mtime);
+ finish_header (record_start);
+#if 0
+ current_block++;
+#endif
+ }
+ break;
+ }
+}
+
+/* Perform a write to flush the buffer. */
+void
+flush_write (void)
+{
+ int copy_back;
+ ssize_t status;
+
+ if (checkpoint_option && !(++checkpoint % 10))
+ WARN ((0, 0, _("Write checkpoint %d"), checkpoint));
+
+ if (tape_length_option && tape_length_option <= bytes_written)
+ {
+ errno = ENOSPC;
+ status = 0;
+ }
+ else if (dev_null_output)
+ status = record_size;
+ else
+ status = write_archive_buffer ();
+ if (status != record_size && !multi_volume_option)
+ archive_write_error (status);
+
+ if (status > 0)
+ {
+ records_written++;
+ bytes_written += status;
+ }
+
+ if (status == record_size)
+ {
+ if (multi_volume_option)
+ {
+ char *cursor;
+
+ if (!save_name)
+ {
+ assign_string (&real_s_name, 0);
+ real_s_totsize = 0;
+ real_s_sizeleft = 0;
+ return;
+ }
+
+ cursor = save_name + FILESYSTEM_PREFIX_LEN (save_name);
+ while (ISSLASH (*cursor))
+ cursor++;
+
+ assign_string (&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 (status < 0 && errno != ENOSPC && errno != EIO && errno != ENXIO)
+ archive_write_error (status);
+
+ /* If error indicates a short write, we just move to the next tape. */
+
+ if (!new_volume (ACCESS_WRITE))
+ return;
+
+ if (totals_option)
+ prev_written += bytes_written;
+ bytes_written = 0;
+
+ if (volume_label_option && real_s_name)
+ {
+ copy_back = 2;
+ record_start -= 2;
+ }
+ else if (volume_label_option || real_s_name)
+ {
+ copy_back = 1;
+ record_start--;
+ }
+ else
+ copy_back = 0;
+
+ if (volume_label_option)
+ {
+ memset (record_start, 0, BLOCKSIZE);
+ sprintf (record_start->header.name, "%s Volume %d",
+ volume_label_option, volno);
+ TIME_TO_CHARS (start_time, record_start->header.mtime);
+ record_start->header.typeflag = GNUTYPE_VOLHDR;
+ finish_header (record_start);
+ }
+
+ if (real_s_name)
+ {
+ int tmp;
+
+ if (volume_label_option)
+ record_start++;
+
+ memset (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;
+ OFF_TO_CHARS (real_s_sizeleft, record_start->header.size);
+ OFF_TO_CHARS (real_s_totsize - real_s_sizeleft,
+ record_start->oldgnu_header.offset);
+ tmp = verbose_option;
+ verbose_option = 0;
+ finish_header (record_start);
+ verbose_option = tmp;
+
+ if (volume_label_option)
+ record_start--;
+ }
+
+ status = write_archive_buffer ();
+ if (status != record_size)
+ archive_write_error (status);
+
+ bytes_written += status;
+
+ if (copy_back)
+ {
+ record_start += copy_back;
+ memcpy (current_block,
+ record_start + blocking_factor - copy_back,
+ 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)
+ assign_string (&real_s_name, 0);
+ else
+ {
+ char *cursor = save_name + FILESYSTEM_PREFIX_LEN (save_name);
+
+ while (ISSLASH (*cursor))
+ cursor++;
+
+ assign_string (&real_s_name, cursor);
+ real_s_sizeleft = save_sizeleft;
+ real_s_totsize = save_totsize;
+ }
+ copy_back = 0;
+ }
+}
+
+/* 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. */
+static void
+archive_write_error (ssize_t status)
+{
+ /* It might be useful to know how much was written before the error
+ occurred. */
+ if (totals_option)
+ {
+ int e = errno;
+ print_total_written ();
+ errno = e;
+ }
+
+ write_fatal_details (*archive_name_cursor, status, record_size);
+}
+
+/* Handle read errors on the archive. If the read should be retried,
+ return to the caller. */
+static void
+archive_read_error (void)
+{
+ read_error (*archive_name_cursor);
+
+ if (record_start_block == 0)
+ FATAL_ERROR ((0, 0, _("At beginning of tape, quitting now")));
+
+ /* Read error in mid archive. We retry up to READ_ERROR_MAX times and
+ then give up on reading the archive. */
+
+ if (read_error_count++ > READ_ERROR_MAX)
+ FATAL_ERROR ((0, 0, _("Too many errors, quitting")));
+ return;
+}
+
+/* Perform a read to flush the buffer. */
+void
+flush_read (void)
+{
+ ssize_t status; /* result from system call */
+ size_t left; /* bytes left */
+ char *more; /* pointer to next byte to read */
+
+ if (checkpoint_option && !(++checkpoint % 10))
+ WARN ((0, 0, _("Read checkpoint %d"), checkpoint));
+
+ /* 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)
+ {
+ archive = STDOUT_FILENO;
+ status = write_archive_buffer ();
+ archive = STDIN_FILENO;
+ if (status != record_size)
+ archive_write_error (status);
+ }
+ if (multi_volume_option)
+ {
+ if (save_name)
+ {
+ char *cursor = save_name + FILESYSTEM_PREFIX_LEN (save_name);
+
+ while (ISSLASH (*cursor))
+ cursor++;
+
+ assign_string (&real_s_name, cursor);
+ real_s_sizeleft = save_sizeleft;
+ real_s_totsize = save_totsize;
+ }
+ else
+ {
+ assign_string (&real_s_name, 0);
+ real_s_totsize = 0;
+ real_s_sizeleft = 0;
+ }
+ }
+
+ error_loop:
+ status = rmtread (archive, record_start->buffer, record_size);
+ if (status == record_size)
+ {
+ records_read++;
+ return;
+ }
+
+ if ((status == 0
+ || (status < 0 && errno == ENOSPC)
+ || (status > 0 && !read_full_records_option))
+ && multi_volume_option)
+ {
+ union block *cursor;
+
+ try_volume:
+ 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:
+ status = rmtread (archive, record_start->buffer, record_size);
+ if (status < 0)
+ {
+ archive_read_error ();
+ goto vol_error;
+ }
+ if (status != record_size)
+ goto short_read;
+
+ cursor = record_start;
+
+ if (cursor->header.typeflag == GNUTYPE_VOLHDR)
+ {
+ if (volume_label_option)
+ {
+ if (!check_label_pattern (cursor))
+ {
+ WARN ((0, 0, _("Volume %s does not match %s"),
+ quote_n (0, cursor->header.name),
+ quote_n (1, volume_label_option)));
+ volno--;
+ global_volno--;
+ goto try_volume;
+ }
+ }
+ if (verbose_option)
+ fprintf (stdlis, _("Reading %s\n"), quote (cursor->header.name));
+ cursor++;
+ }
+ else if (volume_label_option)
+ WARN ((0, 0, _("WARNING: No volume header")));
+
+ if (real_s_name)
+ {
+ uintmax_t s1, s2;
+ if (cursor->header.typeflag != GNUTYPE_MULTIVOL
+ || strcmp (cursor->header.name, real_s_name))
+ {
+ WARN ((0, 0, _("%s is not continued on this volume"),
+ quote (real_s_name)));
+ volno--;
+ global_volno--;
+ goto try_volume;
+ }
+ s1 = UINTMAX_FROM_HEADER (cursor->header.size);
+ s2 = UINTMAX_FROM_HEADER (cursor->oldgnu_header.offset);
+ if (real_s_totsize != s1 + s2 || s1 + s2 < s2)
+ {
+ char totsizebuf[UINTMAX_STRSIZE_BOUND];
+ char s1buf[UINTMAX_STRSIZE_BOUND];
+ char s2buf[UINTMAX_STRSIZE_BOUND];
+
+ WARN ((0, 0, _("%s is the wrong size (%s != %s + %s)"),
+ quote (cursor->header.name),
+ STRINGIFY_BIGINT (save_totsize, totsizebuf),
+ STRINGIFY_BIGINT (s1, s1buf),
+ STRINGIFY_BIGINT (s2, s2buf)));
+ volno--;
+ global_volno--;
+ goto try_volume;
+ }
+ if (real_s_totsize - real_s_sizeleft
+ != OFF_FROM_HEADER (cursor->oldgnu_header.offset))
+ {
+ WARN ((0, 0, _("This volume is out of sequence")));
+ volno--;
+ global_volno--;
+ goto try_volume;
+ }
+ cursor++;
+ }
+ current_block = cursor;
+ records_read++;
+ return;
+ }
+ else if (status < 0)
+ {
+ archive_read_error ();
+ goto error_loop; /* try again */
+ }
+
+ short_read:
+ more = record_start->buffer + status;
+ left = record_size - status;
+
+ while (left % BLOCKSIZE != 0
+ || (left && status && read_full_records_option))
+ {
+ if (status)
+ while ((status = rmtread (archive, more, left)) < 0)
+ archive_read_error ();
+
+ if (status == 0)
+ break;
+
+ if (! read_full_records_option)
+ FATAL_ERROR ((0, 0, _("Unaligned block (%lu bytes) in archive"),
+ (unsigned long) (record_size - left)));
+
+ /* User warned us about this. Fix up. */
+
+ left -= status;
+ more += status;
+ }
+
+ /* 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 = %lu blocks"),
+ (unsigned long) ((record_size - left) / BLOCKSIZE)));
+
+ record_end = record_start + (record_size - left) / BLOCKSIZE;
+ records_read++;
+}
+
+/* Flush the current buffer to/from the archive. */
+void
+flush_archive (void)
+{
+ record_start_block += record_end - record_start;
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+
+ if (access_mode == ACCESS_READ && time_to_start_writing)
+ {
+ access_mode = ACCESS_WRITE;
+ time_to_start_writing = 0;
+
+ if (file_to_switch_to >= 0)
+ {
+ if (rmtclose (archive) != 0)
+ close_warn (*archive_name_cursor);
+
+ archive = file_to_switch_to;
+ }
+ else
+ backspace_output ();
+ }
+
+ switch (access_mode)
+ {
+ case ACCESS_READ:
+ flush_read ();
+ break;
+
+ case ACCESS_WRITE:
+ flush_write ();
+ break;
+
+ case ACCESS_UPDATE:
+ abort ();
+ }
+}
+
+/* Backspace the archive descriptor by one record worth. If it's a
+ tape, MTIOCTOP will work. If it's something else, try to seek on
+ it. If we can't seek, we lose! */
+static void
+backspace_output (void)
+{
+#ifdef MTIOCTOP
+ {
+ struct mtop operation;
+
+ 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
+
+ {
+ off_t position = rmtlseek (archive, (off_t) 0, SEEK_CUR);
+
+ /* Seek back to the beginning of this record and start writing there. */
+
+ position -= record_size;
+ if (position < 0)
+ position = 0;
+ if (rmtlseek (archive, position, SEEK_SET) != position)
+ {
+ /* Lseek failed. Try a different method. */
+
+ WARN ((0, 0,
+ _("Cannot 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,
+ output_start - record_start->buffer);
+ }
+ }
+}
+
+/* Close the archive file. */
+void
+close_archive (void)
+{
+ if (time_to_start_writing || access_mode == ACCESS_WRITE)
+ flush_archive ();
+
+#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
+ && ! _isrmt (archive)
+ && (S_ISFIFO (archive_stat.st_mode) || S_ISSOCK (archive_stat.st_mode)))
+ while (rmtread (archive, record_start->buffer, record_size) > 0)
+ continue;
+#endif
+
+ if (verify_option)
+ verify_volume ();
+
+ if (rmtclose (archive) != 0)
+ close_warn (*archive_name_cursor);
+
+#if !MSDOS
+
+ if (child_pid)
+ {
+ int wait_status;
+
+ while (waitpid (child_pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ ERROR ((0, 0, _("Child died with signal %d"),
+ WTERMSIG (wait_status)));
+ else if (WEXITSTATUS (wait_status) != 0)
+ ERROR ((0, 0, _("Child returned status %d"),
+ WEXITSTATUS (wait_status)));
+ }
+#endif /* !MSDOS */
+
+ if (current_file_name)
+ free (current_file_name);
+ if (current_link_name)
+ free (current_link_name);
+ if (save_name)
+ free (save_name);
+ if (real_s_name)
+ free (real_s_name);
+ free (multi_volume_option ? record_start - 2 : record_start);
+}
+
+/* Called to initialize the global volume number. */
+void
+init_volume_number (void)
+{
+ FILE *file = fopen (volno_file_option, "r");
+
+ if (file)
+ {
+ if (fscanf (file, "%d", &global_volno) != 1
+ || global_volno < 0)
+ FATAL_ERROR ((0, 0, _("%s: contains invalid volume number"),
+ quotearg_colon (volno_file_option)));
+ if (ferror (file))
+ read_error (volno_file_option);
+ if (fclose (file) != 0)
+ close_error (volno_file_option);
+ }
+ else if (errno != ENOENT)
+ open_error (volno_file_option);
+}
+
+/* Called to write out the closing global volume number. */
+void
+closeout_volume_number (void)
+{
+ FILE *file = fopen (volno_file_option, "w");
+
+ if (file)
+ {
+ fprintf (file, "%d\n", global_volno);
+ if (ferror (file))
+ write_error (volno_file_option);
+ if (fclose (file) != 0)
+ close_error (volno_file_option);
+ }
+ else
+ open_error (volno_file_option);
+}
+
+/* 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)
+{
+ static FILE *read_file;
+ static int looped;
+
+ if (!read_file && !info_script_option)
+ /* FIXME: if fopen is used, it will never be closed. */
+ read_file = archive == STDIN_FILENO ? fopen (TTY_NAME, "r") : stdin;
+
+ if (now_verifying)
+ return 0;
+ if (verify_option)
+ verify_volume ();
+
+ if (rmtclose (archive) != 0)
+ close_warn (*archive_name_cursor);
+
+ global_volno++;
+ if (global_volno < 0)
+ FATAL_ERROR ((0, 0, _("Volume number overflow")));
+ volno++;
+ archive_name_cursor++;
+ if (archive_name_cursor == archive_name_array + archive_names)
+ {
+ archive_name_cursor = archive_name_array;
+ looped = 1;
+ }
+
+ tryagain:
+ if (looped)
+ {
+ /* We have to prompt from now on. */
+
+ if (info_script_option)
+ {
+ if (volno_file_option)
+ closeout_volume_number ();
+ if (system (info_script_option) != 0)
+ FATAL_ERROR ((0, 0, _("`%s' command failed"), info_script_option));
+ }
+ else
+ while (1)
+ {
+ char input_buffer[80];
+
+ fputc ('\007', stderr);
+ fprintf (stderr,
+ _("Prepare volume #%d for %s and hit return: "),
+ global_volno, quote (*archive_name_cursor));
+ fflush (stderr);
+
+ if (fgets (input_buffer, sizeof input_buffer, read_file) == 0)
+ {
+ WARN ((0, 0, _("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")));
+
+ fatal_exit ();
+ }
+ if (input_buffer[0] == '\n'
+ || input_buffer[0] == 'y'
+ || input_buffer[0] == 'Y')
+ break;
+
+ switch (input_buffer[0])
+ {
+ case '?':
+ {
+ 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"));
+ }
+ break;
+
+ case 'q':
+ /* Quit. */
+
+ WARN ((0, 0, _("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")));
+
+ fatal_exit ();
+
+ case 'n':
+ /* Get new file name. */
+
+ {
+ 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 '!':
+#if MSDOS
+ spawnl (P_WAIT, getenv ("COMSPEC"), "-", 0);
+#else /* not MSDOS */
+ {
+ pid_t child;
+ const char *shell = getenv ("SHELL");
+ if (! shell)
+ shell = "/bin/sh";
+ child = xfork ();
+ if (child == 0)
+ {
+ execlp (shell, "-sh", "-i", 0);
+ exec_fatal (shell);
+ }
+ else
+ {
+ int wait_status;
+ while (waitpid (child, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (shell);
+ break;
+ }
+ }
+ }
+#endif /* not MSDOS */
+ break;
+ }
+ }
+ }
+
+ if (verify_option)
+ archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, MODE_RW,
+ rsh_command_option);
+ else
+ switch (access)
+ {
+ case ACCESS_READ:
+ archive = rmtopen (*archive_name_cursor, O_RDONLY, MODE_RW,
+ rsh_command_option);
+ break;
+
+ case ACCESS_WRITE:
+ if (backup_option)
+ maybe_backup_file (*archive_name_cursor, 1);
+ archive = rmtcreat (*archive_name_cursor, MODE_RW,
+ rsh_command_option);
+ break;
+
+ case ACCESS_UPDATE:
+ archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, MODE_RW,
+ rsh_command_option);
+ break;
+ }
+
+ if (archive < 0)
+ {
+ open_warn (*archive_name_cursor);
+ if (!verify_option && access == ACCESS_WRITE && backup_option)
+ undo_last_backup ();
+ goto tryagain;
+ }
+
+#if MSDOS
+ setmode (archive, O_BINARY);
+#endif
+
+ return 1;
+}
diff --git a/contrib/tar/src/common.h b/contrib/tar/src/common.h
new file mode 100644
index 0000000..4ea6a52
--- /dev/null
+++ b/contrib/tar/src/common.h
@@ -0,0 +1,578 @@
+/* Common declarations for the tar program.
+
+ Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* Declare the GNU tar archive format. */
+#include "tar.h"
+
+/* The checksum field is filled with this while the checksum is computed. */
+#define CHKBLANKS " " /* 8 blanks, no null */
+
+/* Some constants from POSIX are given names. */
+#define NAME_FIELD_SIZE 100
+#define PREFIX_FIELD_SIZE 155
+#define UNAME_FIELD_SIZE 32
+#define GNAME_FIELD_SIZE 32
+
+/* Some various global definitions. */
+
+/* Name of file to use for interacting with user. */
+#if MSDOS
+# define TTY_NAME "con"
+#else
+# define TTY_NAME "/dev/tty"
+#endif
+
+/* GLOBAL is defined to empty in tar.c only, and left alone in other *.c
+ modules. Here, we merely set it to "extern" if it is not already set.
+ GNU tar does depend on the system loader to preset all GLOBAL variables to
+ neutral (or zero) values, explicit initialization is usually not done. */
+#ifndef GLOBAL
+# define GLOBAL extern
+#endif
+
+/* Exit status for GNU tar. Let's try to keep this list as simple as
+ possible. -d option strongly invites a status different for unequal
+ comparison and other errors. */
+GLOBAL int exit_status;
+
+#define TAREXIT_SUCCESS 0
+#define TAREXIT_DIFFERS 1
+#define TAREXIT_FAILURE 2
+
+/* Both WARN and ERROR write a message on stderr and continue processing,
+ however ERROR manages so tar will exit unsuccessfully. FATAL_ERROR
+ writes a message on stderr and aborts immediately, with another message
+ line telling so. USAGE_ERROR works like FATAL_ERROR except that the
+ other message line suggests trying --help. All four macros accept a
+ single argument of the form ((0, errno, _("FORMAT"), Args...)). errno
+ is zero when the error is not being detected by the system. */
+
+#define WARN(Args) \
+ error Args
+#define ERROR(Args) \
+ (error Args, exit_status = TAREXIT_FAILURE)
+#define FATAL_ERROR(Args) \
+ (error Args, fatal_exit ())
+#define USAGE_ERROR(Args) \
+ (error Args, usage (TAREXIT_FAILURE))
+
+/* Information gleaned from the command line. */
+
+#include "arith.h"
+#include <backupfile.h>
+#include <exclude.h>
+#include <modechange.h>
+#include <safe-read.h>
+#include <full-write.h>
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+/* Name of this program. */
+GLOBAL const char *program_name;
+
+/* Main command option. */
+
+enum subcommand
+{
+ UNKNOWN_SUBCOMMAND, /* none of the following */
+ APPEND_SUBCOMMAND, /* -r */
+ CAT_SUBCOMMAND, /* -A */
+ CREATE_SUBCOMMAND, /* -c */
+ DELETE_SUBCOMMAND, /* -D */
+ DIFF_SUBCOMMAND, /* -d */
+ EXTRACT_SUBCOMMAND, /* -x */
+ LIST_SUBCOMMAND, /* -t */
+ UPDATE_SUBCOMMAND /* -u */
+};
+
+GLOBAL enum subcommand subcommand_option;
+
+/* Selected format for output archive. */
+GLOBAL enum archive_format archive_format;
+
+/* Either NL or NUL, as decided by the --null option. */
+GLOBAL char filename_terminator;
+
+/* Size of each record, once in blocks, once in bytes. Those two variables
+ are always related, the second being BLOCKSIZE times the first. They do
+ not have _option in their name, even if their values is derived from
+ option decoding, as these are especially important in tar. */
+GLOBAL int blocking_factor;
+GLOBAL size_t record_size;
+
+/* Boolean value. */
+GLOBAL int absolute_names_option;
+
+/* This variable tells how to interpret newer_mtime_option, below. If zero,
+ files get archived if their mtime is not less than newer_mtime_option.
+ If nonzero, files get archived if *either* their ctime or mtime is not less
+ than newer_mtime_option. */
+GLOBAL int after_date_option;
+
+/* Boolean value. */
+GLOBAL int atime_preserve_option;
+
+/* Boolean value. */
+GLOBAL int backup_option;
+
+/* Type of backups being made. */
+GLOBAL enum backup_type backup_type;
+
+/* Boolean value. */
+GLOBAL int block_number_option;
+
+/* Boolean value. */
+GLOBAL int checkpoint_option;
+
+/* Specified name of compression program, or "gzip" as implied by -z. */
+GLOBAL const char *use_compress_program_option;
+
+/* Boolean value. */
+GLOBAL int dereference_option;
+
+/* Patterns that match file names to be excluded. */
+GLOBAL struct exclude *excluded;
+
+/* Specified file containing names to work on. */
+GLOBAL const char *files_from_option;
+
+/* Boolean value. */
+GLOBAL int force_local_option;
+
+/* Specified value to be put into tar file in place of stat () results, or
+ just -1 if such an override should not take place. */
+GLOBAL gid_t group_option;
+
+/* Boolean value. */
+GLOBAL int ignore_failed_read_option;
+
+/* Boolean value. */
+GLOBAL int ignore_zeros_option;
+
+/* Boolean value. */
+GLOBAL int incremental_option;
+
+/* Specified name of script to run at end of each tape change. */
+GLOBAL const char *info_script_option;
+
+/* Boolean value. */
+GLOBAL int interactive_option;
+
+enum old_files
+{
+ DEFAULT_OLD_FILES, /* default */
+ UNLINK_FIRST_OLD_FILES, /* --unlink-first */
+ KEEP_OLD_FILES, /* --keep-old-files */
+ OVERWRITE_OLD_DIRS, /* --overwrite-dir */
+ OVERWRITE_OLD_FILES /* --overwrite */
+};
+GLOBAL enum old_files old_files_option;
+
+/* Specified file name for incremental list. */
+GLOBAL const char *listed_incremental_option;
+
+/* Specified mode change string. */
+GLOBAL struct mode_change *mode_option;
+
+/* Boolean value. */
+GLOBAL int multi_volume_option;
+
+/* The same variable hold the time, whether mtime or ctime. Just fake a
+ non-existing option, for making the code clearer, elsewhere. */
+#define newer_ctime_option newer_mtime_option
+
+/* Specified threshold date and time. Files having an older time stamp
+ do not get archived (also see after_date_option above). */
+GLOBAL time_t newer_mtime_option;
+
+/* Zero if there is no recursion, otherwise FNM_LEADING_DIR. */
+GLOBAL int recursion_option;
+
+/* Boolean value. */
+GLOBAL int numeric_owner_option;
+
+/* Boolean value. */
+GLOBAL int one_file_system_option;
+
+/* Specified value to be put into tar file in place of stat () results, or
+ just -1 if such an override should not take place. */
+GLOBAL uid_t owner_option;
+
+/* Boolean value. */
+GLOBAL int recursive_unlink_option;
+
+/* Boolean value. */
+GLOBAL int read_full_records_option;
+
+/* Boolean value. */
+GLOBAL int remove_files_option;
+
+/* Specified remote shell command. */
+GLOBAL const char *rsh_command_option;
+
+/* Boolean value. */
+GLOBAL int same_order_option;
+
+/* If positive, preserve ownership when extracting. */
+GLOBAL int same_owner_option;
+
+/* If positive, preserve permissions when extracting. */
+GLOBAL int same_permissions_option;
+
+/* Boolean value. */
+GLOBAL int show_omitted_dirs_option;
+
+/* Boolean value. */
+GLOBAL int sparse_option;
+
+/* Boolean value. */
+GLOBAL int starting_file_option;
+
+/* Specified maximum byte length of each tape volume (multiple of 1024). */
+GLOBAL tarlong tape_length_option;
+
+/* Boolean value. */
+GLOBAL int to_stdout_option;
+
+/* Boolean value. */
+GLOBAL int totals_option;
+
+/* Boolean value. */
+GLOBAL int touch_option;
+
+/* Count how many times the option has been set, multiple setting yields
+ more verbose behavior. Value 0 means no verbosity, 1 means file name
+ only, 2 means file name and all attributes. More than 2 is just like 2. */
+GLOBAL int verbose_option;
+
+/* Boolean value. */
+GLOBAL int verify_option;
+
+/* Specified name of file containing the volume number. */
+GLOBAL const char *volno_file_option;
+
+/* Specified value or pattern. */
+GLOBAL const char *volume_label_option;
+
+/* Other global variables. */
+
+/* File descriptor for archive file. */
+GLOBAL int archive;
+
+/* Nonzero when outputting to /dev/null. */
+GLOBAL int dev_null_output;
+
+/* Timestamp for when we started execution. */
+#if HAVE_CLOCK_GETTIME
+ GLOBAL struct timespec start_timespec;
+# define start_time (start_timespec.tv_sec)
+#else
+ GLOBAL time_t start_time;
+#endif
+
+/* Name of file for the current archive entry. */
+GLOBAL char *current_file_name;
+
+/* Name of link for the current archive entry. */
+GLOBAL char *current_link_name;
+
+/* List of tape drive names, number of such tape drives, allocated number,
+ and current cursor in list. */
+GLOBAL const char **archive_name_array;
+GLOBAL int archive_names;
+GLOBAL int allocated_archive_names;
+GLOBAL const char **archive_name_cursor;
+
+/* Structure for keeping track of filenames and lists thereof. */
+struct name
+ {
+ struct name *next;
+ size_t length; /* cached strlen(name) */
+ char found; /* a matching file has been found */
+ char firstch; /* first char is literally matched */
+ char regexp; /* this name is a regexp, not literal */
+ int change_dir; /* set with the -C option */
+ char const *dir_contents; /* for incremental_option */
+ char fake; /* dummy entry */
+ char name[1];
+ };
+
+/* Information about a sparse file. */
+struct sp_array
+ {
+ off_t offset;
+ size_t numbytes;
+ };
+GLOBAL struct sp_array *sparsearray;
+
+/* Number of elements in sparsearray. */
+GLOBAL int sp_array_size;
+
+/* Declarations for each module. */
+
+/* FIXME: compare.c should not directly handle the following variable,
+ instead, this should be done in buffer.c only. */
+
+enum access_mode
+{
+ ACCESS_READ,
+ ACCESS_WRITE,
+ ACCESS_UPDATE
+};
+extern enum access_mode access_mode;
+
+/* Module buffer.c. */
+
+extern FILE *stdlis;
+extern char *save_name;
+extern off_t save_sizeleft;
+extern off_t save_totsize;
+extern bool write_archive_to_stdout;
+
+size_t available_space_after PARAMS ((union block *));
+off_t current_block_ordinal PARAMS ((void));
+void close_archive PARAMS ((void));
+void closeout_volume_number PARAMS ((void));
+union block *find_next_block PARAMS ((void));
+void flush_read PARAMS ((void));
+void flush_write PARAMS ((void));
+void flush_archive PARAMS ((void));
+void init_volume_number PARAMS ((void));
+void open_archive PARAMS ((enum access_mode));
+void print_total_written PARAMS ((void));
+void reset_eof PARAMS ((void));
+void set_next_block_after PARAMS ((union block *));
+
+/* Module create.c. */
+
+void create_archive PARAMS ((void));
+void dump_file PARAMS ((char *, int, dev_t));
+void finish_header PARAMS ((union block *));
+void write_eot PARAMS ((void));
+
+#define GID_TO_CHARS(val, where) gid_to_chars (val, where, sizeof (where))
+#define MAJOR_TO_CHARS(val, where) major_to_chars (val, where, sizeof (where))
+#define MINOR_TO_CHARS(val, where) minor_to_chars (val, where, sizeof (where))
+#define MODE_TO_CHARS(val, where) mode_to_chars (val, where, sizeof (where))
+#define OFF_TO_CHARS(val, where) off_to_chars (val, where, sizeof (where))
+#define SIZE_TO_CHARS(val, where) size_to_chars (val, where, sizeof (where))
+#define TIME_TO_CHARS(val, where) time_to_chars (val, where, sizeof (where))
+#define UID_TO_CHARS(val, where) uid_to_chars (val, where, sizeof (where))
+#define UINTMAX_TO_CHARS(val, where) uintmax_to_chars (val, where, sizeof (where))
+
+void gid_to_chars PARAMS ((gid_t, char *, size_t));
+void major_to_chars PARAMS ((major_t, char *, size_t));
+void minor_to_chars PARAMS ((minor_t, char *, size_t));
+void mode_to_chars PARAMS ((mode_t, char *, size_t));
+void off_to_chars PARAMS ((off_t, char *, size_t));
+void size_to_chars PARAMS ((size_t, char *, size_t));
+void time_to_chars PARAMS ((time_t, char *, size_t));
+void uid_to_chars PARAMS ((uid_t, char *, size_t));
+void uintmax_to_chars PARAMS ((uintmax_t, char *, size_t));
+
+/* Module diffarch.c. */
+
+extern int now_verifying;
+
+void diff_archive PARAMS ((void));
+void diff_init PARAMS ((void));
+void verify_volume PARAMS ((void));
+
+/* Module extract.c. */
+
+extern int we_are_root;
+void extr_init PARAMS ((void));
+void extract_archive PARAMS ((void));
+void extract_finish PARAMS ((void));
+void fatal_exit PARAMS ((void)) __attribute__ ((noreturn));
+
+/* Module delete.c. */
+
+void delete_archive_members PARAMS ((void));
+
+/* Module incremen.c. */
+
+char *get_directory_contents PARAMS ((char *, dev_t));
+void read_directory_file PARAMS ((void));
+void write_directory_file PARAMS ((void));
+void gnu_restore PARAMS ((size_t));
+
+/* Module list.c. */
+
+enum read_header
+{
+ HEADER_STILL_UNREAD, /* for when read_header has not been called */
+ HEADER_SUCCESS, /* header successfully read and checksummed */
+ HEADER_SUCCESS_EXTENDED, /* likewise, but we got an extended header */
+ HEADER_ZERO_BLOCK, /* zero block where header expected */
+ HEADER_END_OF_FILE, /* true end of file while header expected */
+ HEADER_FAILURE /* ill-formed header, or bad checksum */
+};
+
+extern union block *current_header;
+extern struct stat current_stat;
+extern enum archive_format current_format;
+
+void decode_header PARAMS ((union block *, struct stat *,
+ enum archive_format *, int));
+#define STRINGIFY_BIGINT(i, b) \
+ stringify_uintmax_t_backwards ((uintmax_t) (i), (b) + UINTMAX_STRSIZE_BOUND)
+char *stringify_uintmax_t_backwards PARAMS ((uintmax_t, char *));
+char const *tartime PARAMS ((time_t));
+
+#define GID_FROM_HEADER(where) gid_from_header (where, sizeof (where))
+#define MAJOR_FROM_HEADER(where) major_from_header (where, sizeof (where))
+#define MINOR_FROM_HEADER(where) minor_from_header (where, sizeof (where))
+#define MODE_FROM_HEADER(where) mode_from_header (where, sizeof (where))
+#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
+#define SIZE_FROM_HEADER(where) size_from_header (where, sizeof (where))
+#define TIME_FROM_HEADER(where) time_from_header (where, sizeof (where))
+#define UID_FROM_HEADER(where) uid_from_header (where, sizeof (where))
+#define UINTMAX_FROM_HEADER(where) uintmax_from_header (where, sizeof (where))
+
+gid_t gid_from_header PARAMS ((const char *, size_t));
+major_t major_from_header PARAMS ((const char *, size_t));
+minor_t minor_from_header PARAMS ((const char *, size_t));
+mode_t mode_from_header PARAMS ((const char *, size_t));
+off_t off_from_header PARAMS ((const char *, size_t));
+size_t size_from_header PARAMS ((const char *, size_t));
+time_t time_from_header PARAMS ((const char *, size_t));
+uid_t uid_from_header PARAMS ((const char *, size_t));
+uintmax_t uintmax_from_header PARAMS ((const char *, size_t));
+
+void list_archive PARAMS ((void));
+void print_for_mkdir PARAMS ((char *, int, mode_t));
+void print_header PARAMS ((void));
+void read_and PARAMS ((void (*do_) ()));
+enum read_header read_header PARAMS ((bool));
+void skip_file PARAMS ((off_t));
+void skip_member PARAMS ((void));
+
+/* Module mangle.c. */
+
+void extract_mangle PARAMS ((void));
+
+/* Module misc.c. */
+
+void assign_string PARAMS ((char **, const char *));
+char *quote_copy_string PARAMS ((const char *));
+int unquote_string PARAMS ((char *));
+
+int contains_dot_dot PARAMS ((char const *));
+
+int remove_any_file PARAMS ((const char *, int));
+int maybe_backup_file PARAMS ((const char *, int));
+void undo_last_backup PARAMS ((void));
+
+int deref_stat PARAMS ((int, char const *, struct stat *));
+
+int chdir_arg PARAMS ((char const *));
+void chdir_do PARAMS ((int));
+
+void decode_mode PARAMS ((mode_t, char *));
+
+void chdir_fatal PARAMS ((char const *)) __attribute__ ((noreturn));
+void chmod_error_details PARAMS ((char const *, mode_t));
+void chown_error_details PARAMS ((char const *, uid_t, gid_t));
+void close_error PARAMS ((char const *));
+void close_warn PARAMS ((char const *));
+void exec_fatal PARAMS ((char const *)) __attribute__ ((noreturn));
+void link_error PARAMS ((char const *, char const *));
+void mkdir_error PARAMS ((char const *));
+void mkfifo_error PARAMS ((char const *));
+void mknod_error PARAMS ((char const *));
+void open_error PARAMS ((char const *));
+void open_fatal PARAMS ((char const *)) __attribute__ ((noreturn));
+void open_warn PARAMS ((char const *));
+void read_error PARAMS ((char const *));
+void read_error_details PARAMS ((char const *, off_t, size_t));
+void read_fatal PARAMS ((char const *)) __attribute__ ((noreturn));
+void read_fatal_details PARAMS ((char const *, off_t, size_t));
+void read_warn_details PARAMS ((char const *, off_t, size_t));
+void readlink_error PARAMS ((char const *));
+void readlink_warn PARAMS ((char const *));
+void savedir_error PARAMS ((char const *));
+void savedir_warn PARAMS ((char const *));
+void seek_error PARAMS ((char const *));
+void seek_error_details PARAMS ((char const *, off_t));
+void seek_warn PARAMS ((char const *));
+void seek_warn_details PARAMS ((char const *, off_t));
+void stat_error PARAMS ((char const *));
+void stat_warn PARAMS ((char const *));
+void symlink_error PARAMS ((char const *, char const *));
+void truncate_error PARAMS ((char const *));
+void truncate_warn PARAMS ((char const *));
+void unlink_error PARAMS ((char const *));
+void utime_error PARAMS ((char const *));
+void waitpid_error PARAMS ((char const *));
+void write_error PARAMS ((char const *));
+void write_error_details PARAMS ((char const *, ssize_t, size_t));
+void write_fatal PARAMS ((char const *)) __attribute__ ((noreturn));
+void write_fatal_details PARAMS ((char const *, ssize_t, size_t))
+ __attribute__ ((noreturn));
+
+pid_t xfork PARAMS ((void));
+void xpipe PARAMS ((int[2]));
+
+char const *quote PARAMS ((char const *));
+char const *quote_n PARAMS ((int, char const *));
+
+/* Module names.c. */
+
+extern struct name *gnu_list_name;
+
+void gid_to_gname PARAMS ((gid_t, char gname[GNAME_FIELD_SIZE]));
+int gname_to_gid PARAMS ((char gname[GNAME_FIELD_SIZE], gid_t *));
+void uid_to_uname PARAMS ((uid_t, char uname[UNAME_FIELD_SIZE]));
+int uname_to_uid PARAMS ((char uname[UNAME_FIELD_SIZE], uid_t *));
+
+void init_names PARAMS ((void));
+void name_add PARAMS ((const char *));
+void name_init PARAMS ((int, char *const *));
+void name_term PARAMS ((void));
+char *name_next PARAMS ((int));
+void name_close PARAMS ((void));
+void name_gather PARAMS ((void));
+struct name *addname PARAMS ((char const *, int));
+int name_match PARAMS ((const char *));
+void names_notfound PARAMS ((void));
+void collect_and_sort_names PARAMS ((void));
+struct name *name_scan PARAMS ((const char *));
+char *name_from_list PARAMS ((void));
+void blank_name_list PARAMS ((void));
+char *new_name PARAMS ((const char *, const char *));
+
+bool excluded_name PARAMS ((char const *));
+
+void add_avoided_name PARAMS ((char const *));
+int is_avoided_name PARAMS ((char const *));
+
+/* Module tar.c. */
+
+int confirm PARAMS ((const char *, const char *));
+void request_stdin PARAMS ((const char *));
+
+/* Module update.c. */
+
+extern char *output_start;
+
+void update_archive PARAMS ((void));
diff --git a/contrib/tar/src/compare.c b/contrib/tar/src/compare.c
new file mode 100644
index 0000000..c3b9bb9
--- /dev/null
+++ b/contrib/tar/src/compare.c
@@ -0,0 +1,817 @@
+/* Diff files from a tar archive.
+
+ Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ Written by John Gilmore, on 1987-04-30.
+
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+
+#if HAVE_UTIME_H
+# include <utime.h>
+#else
+struct utimbuf
+ {
+ long actime;
+ long modtime;
+ };
+#endif
+
+#if HAVE_LINUX_FD_H
+# include <linux/fd.h>
+#endif
+
+#include <quotearg.h>
+
+#include "common.h"
+#include "rmt.h"
+
+/* Spare space for messages, hopefully safe even after gettext. */
+#define MESSAGE_BUFFER_SIZE 100
+
+/* Nonzero if we are verifying at the moment. */
+int now_verifying;
+
+/* File descriptor for the file we are diffing. */
+static int diff_handle;
+
+/* Area for reading file contents into. */
+static char *diff_buffer;
+
+/* Initialize for a diff operation. */
+void
+diff_init (void)
+{
+ diff_buffer = valloc (record_size);
+ if (!diff_buffer)
+ xalloc_die ();
+}
+
+/* Sigh about something that differs by writing a MESSAGE to stdlis,
+ given MESSAGE is nonzero. Also set the exit status if not already. */
+static void
+report_difference (const char *message)
+{
+ if (message)
+ fprintf (stdlis, "%s: %s\n", quotearg_colon (current_file_name), message);
+
+ if (exit_status == TAREXIT_SUCCESS)
+ exit_status = TAREXIT_DIFFERS;
+}
+
+/* Take a buffer returned by read_and_process and do nothing with it. */
+static int
+process_noop (size_t size, char *data)
+{
+ /* Yes, I know. SIZE and DATA are unused in this function. Some
+ compilers may even report it. That's OK, just relax! */
+ return 1;
+}
+
+static int
+process_rawdata (size_t bytes, char *buffer)
+{
+ ssize_t status = safe_read (diff_handle, diff_buffer, bytes);
+ char message[MESSAGE_BUFFER_SIZE];
+
+ if (status != bytes)
+ {
+ if (status < 0)
+ {
+ read_error (current_file_name);
+ report_difference (0);
+ }
+ else
+ {
+ sprintf (message, _("Could only read %lu of %lu bytes"),
+ (unsigned long) status, (unsigned long) bytes);
+ report_difference (message);
+ }
+ return 0;
+ }
+
+ if (memcmp (buffer, diff_buffer, bytes))
+ {
+ report_difference (_("Contents differ"));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Directory contents, only for GNUTYPE_DUMPDIR. */
+
+static char *dumpdir_cursor;
+
+static int
+process_dumpdir (size_t bytes, char *buffer)
+{
+ if (memcmp (buffer, dumpdir_cursor, bytes))
+ {
+ report_difference (_("Contents differ"));
+ return 0;
+ }
+
+ dumpdir_cursor += bytes;
+ return 1;
+}
+
+/* Some other routine wants SIZE bytes in the archive. For each chunk
+ of the archive, call PROCESSOR with the size of the chunk, and the
+ address of the chunk it can work with. The PROCESSOR should return
+ nonzero for success. It it return error once, continue skipping
+ without calling PROCESSOR anymore. */
+static void
+read_and_process (off_t size, int (*processor) (size_t, char *))
+{
+ union block *data_block;
+ size_t data_size;
+
+ if (multi_volume_option)
+ save_sizeleft = size;
+ while (size)
+ {
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return;
+ }
+
+ data_size = available_space_after (data_block);
+ if (data_size > size)
+ data_size = size;
+ if (!(*processor) (data_size, data_block->buffer))
+ processor = process_noop;
+ set_next_block_after ((union block *)
+ (data_block->buffer + data_size - 1));
+ size -= data_size;
+ if (multi_volume_option)
+ save_sizeleft -= data_size;
+ }
+}
+
+/* JK This routine should be used more often than it is ... look into
+ that. Anyhow, what it does is translate the sparse information on the
+ header, and in any subsequent extended headers, into an array of
+ structures with true numbers, as opposed to character strings. It
+ simply makes our life much easier, doing so many comparisons and such.
+ */
+static void
+fill_in_sparse_array (void)
+{
+ int counter;
+
+ /* Allocate space for our scratch space; it's initially 10 elements
+ long, but can change in this routine if necessary. */
+
+ sp_array_size = 10;
+ sparsearray = xmalloc (sp_array_size * sizeof (struct sp_array));
+
+ /* There are at most five of these structures in the header itself;
+ read these in first. */
+
+ for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
+ {
+ /* Compare to 0, or use !(int)..., for Pyramid's dumb compiler. */
+ if (current_header->oldgnu_header.sp[counter].numbytes == 0)
+ break;
+
+ sparsearray[counter].offset =
+ OFF_FROM_HEADER (current_header->oldgnu_header.sp[counter].offset);
+ sparsearray[counter].numbytes =
+ SIZE_FROM_HEADER (current_header->oldgnu_header.sp[counter].numbytes);
+ }
+
+ /* If the header's extended, we gotta read in exhdr's till we're done. */
+
+ if (current_header->oldgnu_header.isextended)
+ {
+ /* How far into the sparsearray we are `so far'. */
+ static int so_far_ind = SPARSES_IN_OLDGNU_HEADER;
+ union block *exhdr;
+
+ while (1)
+ {
+ exhdr = find_next_block ();
+ if (!exhdr)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+
+ for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
+ {
+ if (counter + so_far_ind > sp_array_size - 1)
+ {
+ /* We just ran out of room in our scratch area -
+ realloc it. */
+
+ sp_array_size *= 2;
+ sparsearray =
+ xrealloc (sparsearray,
+ sp_array_size * sizeof (struct sp_array));
+ }
+
+ /* Convert the character strings into offsets and sizes. */
+
+ sparsearray[counter + so_far_ind].offset =
+ OFF_FROM_HEADER (exhdr->sparse_header.sp[counter].offset);
+ sparsearray[counter + so_far_ind].numbytes =
+ SIZE_FROM_HEADER (exhdr->sparse_header.sp[counter].numbytes);
+ }
+
+ /* If this is the last extended header for this file, we can
+ stop. */
+
+ if (!exhdr->sparse_header.isextended)
+ break;
+
+ so_far_ind += SPARSES_IN_SPARSE_HEADER;
+ set_next_block_after (exhdr);
+ }
+
+ /* Be sure to skip past the last one. */
+
+ set_next_block_after (exhdr);
+ }
+}
+
+/* JK Diff'ing a sparse file with its counterpart on the tar file is a
+ bit of a different story than a normal file. First, we must know what
+ areas of the file to skip through, i.e., we need to construct a
+ sparsearray, which will hold all the information we need. We must
+ compare small amounts of data at a time as we find it. */
+
+/* FIXME: This does not look very solid to me, at first glance. Zero areas
+ are not checked, spurious sparse entries seemingly goes undetected, and
+ I'm not sure overall identical sparsity is verified. */
+
+static void
+diff_sparse_files (off_t size_of_file)
+{
+ off_t remaining_size = size_of_file;
+ char *buffer = xmalloc (BLOCKSIZE * sizeof (char));
+ size_t buffer_size = BLOCKSIZE;
+ union block *data_block = 0;
+ int counter = 0;
+ int different = 0;
+
+ fill_in_sparse_array ();
+
+ while (remaining_size > 0)
+ {
+ ssize_t status;
+ size_t chunk_size;
+ off_t offset;
+
+#if 0
+ off_t amount_read = 0;
+#endif
+
+ data_block = find_next_block ();
+ if (!data_block)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+ chunk_size = sparsearray[counter].numbytes;
+ if (!chunk_size)
+ break;
+
+ offset = sparsearray[counter].offset;
+ if (lseek (diff_handle, offset, SEEK_SET) < 0)
+ {
+ seek_error_details (current_file_name, offset);
+ report_difference (0);
+ }
+
+ /* Take care to not run out of room in our buffer. */
+
+ while (buffer_size < chunk_size)
+ {
+ if (buffer_size * 2 < buffer_size)
+ xalloc_die ();
+ buffer_size *= 2;
+ buffer = xrealloc (buffer, buffer_size * sizeof (char));
+ }
+
+ while (chunk_size > BLOCKSIZE)
+ {
+ if (status = safe_read (diff_handle, buffer, BLOCKSIZE),
+ status != BLOCKSIZE)
+ {
+ if (status < 0)
+ {
+ read_error (current_file_name);
+ report_difference (0);
+ }
+ else
+ {
+ char message[MESSAGE_BUFFER_SIZE];
+
+ sprintf (message, _("Could only read %lu of %lu bytes"),
+ (unsigned long) status, (unsigned long) chunk_size);
+ report_difference (message);
+ }
+ break;
+ }
+
+ if (memcmp (buffer, data_block->buffer, BLOCKSIZE))
+ {
+ different = 1;
+ break;
+ }
+
+ chunk_size -= status;
+ remaining_size -= status;
+ set_next_block_after (data_block);
+ data_block = find_next_block ();
+ if (!data_block)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+ }
+ if (status = safe_read (diff_handle, buffer, chunk_size),
+ status != chunk_size)
+ {
+ if (status < 0)
+ {
+ read_error (current_file_name);
+ report_difference (0);
+ }
+ else
+ {
+ char message[MESSAGE_BUFFER_SIZE];
+
+ sprintf (message, _("Could only read %lu of %lu bytes"),
+ (unsigned long) status, (unsigned long) chunk_size);
+ report_difference (message);
+ }
+ break;
+ }
+
+ if (memcmp (buffer, data_block->buffer, chunk_size))
+ {
+ different = 1;
+ break;
+ }
+#if 0
+ amount_read += chunk_size;
+ if (amount_read >= BLOCKSIZE)
+ {
+ amount_read = 0;
+ set_next_block_after (data_block);
+ data_block = find_next_block ();
+ if (!data_block)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+ }
+#endif
+ set_next_block_after (data_block);
+ counter++;
+ remaining_size -= chunk_size;
+ }
+
+#if 0
+ /* If the number of bytes read isn't the number of bytes supposedly in
+ the file, they're different. */
+
+ if (amount_read != size_of_file)
+ different = 1;
+#endif
+
+ set_next_block_after (data_block);
+ free (sparsearray);
+
+ if (different)
+ report_difference (_("Contents differ"));
+}
+
+/* Call either stat or lstat over STAT_DATA, depending on
+ --dereference (-h), for a file which should exist. Diagnose any
+ problem. Return nonzero for success, zero otherwise. */
+static int
+get_stat_data (char const *file_name, struct stat *stat_data)
+{
+ int status = deref_stat (dereference_option, file_name, stat_data);
+
+ if (status != 0)
+ {
+ if (errno == ENOENT)
+ stat_warn (file_name);
+ else
+ stat_error (file_name);
+ report_difference (0);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Diff a file against the archive. */
+void
+diff_archive (void)
+{
+ struct stat stat_data;
+ size_t name_length;
+ int status;
+ struct utimbuf restore_times;
+
+ set_next_block_after (current_header);
+ decode_header (current_header, &current_stat, &current_format, 1);
+
+ /* Print the block from current_header and current_stat. */
+
+ if (verbose_option)
+ {
+ if (now_verifying)
+ fprintf (stdlis, _("Verify "));
+ print_header ();
+ }
+
+ switch (current_header->header.typeflag)
+ {
+ default:
+ ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
+ quotearg_colon (current_file_name),
+ current_header->header.typeflag));
+ /* Fall through. */
+
+ case AREGTYPE:
+ case REGTYPE:
+ case GNUTYPE_SPARSE:
+ case CONTTYPE:
+
+ /* Appears to be a file. See if it's really a directory. */
+
+ name_length = strlen (current_file_name) - 1;
+ if (ISSLASH (current_file_name[name_length]))
+ goto really_dir;
+
+ if (!get_stat_data (current_file_name, &stat_data))
+ {
+ skip_member ();
+ goto quit;
+ }
+
+ if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (_("File type differs"));
+ skip_member ();
+ goto quit;
+ }
+
+ if ((current_stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
+ report_difference (_("Mode differs"));
+
+#if !MSDOS
+ /* stat() in djgpp's C library gives a constant number of 42 as the
+ uid and gid of a file. So, comparing an FTP'ed archive just after
+ unpack would fail on MSDOS. */
+ if (stat_data.st_uid != current_stat.st_uid)
+ report_difference (_("Uid differs"));
+ if (stat_data.st_gid != current_stat.st_gid)
+ report_difference (_("Gid differs"));
+#endif
+
+ if (stat_data.st_mtime != current_stat.st_mtime)
+ report_difference (_("Mod time differs"));
+ if (current_header->header.typeflag != GNUTYPE_SPARSE &&
+ stat_data.st_size != current_stat.st_size)
+ {
+ report_difference (_("Size differs"));
+ skip_member ();
+ goto quit;
+ }
+
+ diff_handle = open (current_file_name, O_RDONLY | O_BINARY);
+
+ if (diff_handle < 0)
+ {
+ open_error (current_file_name);
+ skip_member ();
+ report_difference (0);
+ goto quit;
+ }
+
+ restore_times.actime = stat_data.st_atime;
+ restore_times.modtime = stat_data.st_mtime;
+
+ /* Need to treat sparse files completely differently here. */
+
+ if (current_header->header.typeflag == GNUTYPE_SPARSE)
+ diff_sparse_files (current_stat.st_size);
+ else
+ {
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, current_file_name);
+ save_totsize = current_stat.st_size;
+ /* save_sizeleft is set in read_and_process. */
+ }
+
+ read_and_process (current_stat.st_size, process_rawdata);
+
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+ }
+
+ status = close (diff_handle);
+ if (status != 0)
+ close_error (current_file_name);
+
+ if (atime_preserve_option)
+ utime (current_file_name, &restore_times);
+
+ quit:
+ break;
+
+#if !MSDOS
+ case LNKTYPE:
+ {
+ struct stat link_data;
+
+ if (!get_stat_data (current_file_name, &stat_data))
+ break;
+ if (!get_stat_data (current_link_name, &link_data))
+ break;
+
+ if (stat_data.st_dev != link_data.st_dev
+ || stat_data.st_ino != link_data.st_ino)
+ {
+ char *message =
+ xmalloc (MESSAGE_BUFFER_SIZE + 4 * strlen (current_link_name));
+
+ sprintf (message, _("Not linked to %s"),
+ quote (current_link_name));
+ report_difference (message);
+ free (message);
+ break;
+ }
+
+ break;
+ }
+#endif /* not MSDOS */
+
+#ifdef HAVE_READLINK
+ case SYMTYPE:
+ {
+ size_t len = strlen (current_link_name);
+ char *linkbuf = alloca (len + 1);
+
+ status = readlink (current_file_name, linkbuf, len + 1);
+
+ if (status < 0)
+ {
+ if (errno == ENOENT)
+ readlink_warn (current_file_name);
+ else
+ readlink_error (current_file_name);
+ report_difference (0);
+ }
+ else if (status != len
+ || strncmp (current_link_name, linkbuf, len) != 0)
+ report_difference (_("Symlink differs"));
+
+ break;
+ }
+#endif
+
+ case CHRTYPE:
+ case BLKTYPE:
+ case FIFOTYPE:
+
+ /* FIXME: deal with umask. */
+
+ if (!get_stat_data (current_file_name, &stat_data))
+ break;
+
+ if (current_header->header.typeflag == CHRTYPE
+ ? !S_ISCHR (stat_data.st_mode)
+ : current_header->header.typeflag == BLKTYPE
+ ? !S_ISBLK (stat_data.st_mode)
+ : /* current_header->header.typeflag == FIFOTYPE */
+ !S_ISFIFO (stat_data.st_mode))
+ {
+ report_difference (_("File type differs"));
+ break;
+ }
+
+ if ((current_header->header.typeflag == CHRTYPE
+ || current_header->header.typeflag == BLKTYPE)
+ && current_stat.st_rdev != stat_data.st_rdev)
+ {
+ report_difference (_("Device number differs"));
+ break;
+ }
+
+ if ((current_stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
+ {
+ report_difference (_("Mode differs"));
+ break;
+ }
+
+ break;
+
+ case GNUTYPE_DUMPDIR:
+ {
+ char *dumpdir_buffer = get_directory_contents (current_file_name, 0);
+
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, current_file_name);
+ save_totsize = current_stat.st_size;
+ /* save_sizeleft is set in read_and_process. */
+ }
+
+ if (dumpdir_buffer)
+ {
+ dumpdir_cursor = dumpdir_buffer;
+ read_and_process (current_stat.st_size, process_dumpdir);
+ free (dumpdir_buffer);
+ }
+ else
+ read_and_process (current_stat.st_size, process_noop);
+
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+ /* Fall through. */
+ }
+
+ case DIRTYPE:
+ /* Check for trailing /. */
+
+ name_length = strlen (current_file_name) - 1;
+
+ really_dir:
+ while (name_length && ISSLASH (current_file_name[name_length]))
+ current_file_name[name_length--] = '\0'; /* zap / */
+
+ if (!get_stat_data (current_file_name, &stat_data))
+ break;
+
+ if (!S_ISDIR (stat_data.st_mode))
+ {
+ report_difference (_("File type differs"));
+ break;
+ }
+
+ if ((current_stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL))
+ {
+ report_difference (_("Mode differs"));
+ break;
+ }
+
+ break;
+
+ case GNUTYPE_VOLHDR:
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ {
+ off_t offset;
+
+ name_length = strlen (current_file_name) - 1;
+ if (ISSLASH (current_file_name[name_length]))
+ goto really_dir;
+
+ if (!get_stat_data (current_file_name, &stat_data))
+ break;
+
+ if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (_("File type differs"));
+ skip_member ();
+ break;
+ }
+
+ offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
+ if (stat_data.st_size != current_stat.st_size + offset)
+ {
+ report_difference (_("Size differs"));
+ skip_member ();
+ break;
+ }
+
+ diff_handle = open (current_file_name, O_RDONLY | O_BINARY);
+
+ if (diff_handle < 0)
+ {
+ open_error (current_file_name);
+ report_difference (0);
+ skip_member ();
+ break;
+ }
+
+ if (lseek (diff_handle, offset, SEEK_SET) < 0)
+ {
+ seek_error_details (current_file_name, offset);
+ report_difference (0);
+ break;
+ }
+
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, current_file_name);
+ save_totsize = stat_data.st_size;
+ /* save_sizeleft is set in read_and_process. */
+ }
+
+ read_and_process (current_stat.st_size, process_rawdata);
+
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+
+ status = close (diff_handle);
+ if (status != 0)
+ close_error (current_file_name);
+
+ break;
+ }
+ }
+}
+
+void
+verify_volume (void)
+{
+ if (!diff_buffer)
+ diff_init ();
+
+ /* Verifying an archive is meant to check if the physical media got it
+ correctly, so try to defeat clever in-memory buffering pertaining to
+ this particular media. On Linux, for example, the floppy drive would
+ not even be accessed for the whole verification.
+
+ The code was using fsync only when the ioctl is unavailable, but
+ Marty Leisner says that the ioctl does not work when not preceded by
+ fsync. So, until we know better, or maybe to please Marty, let's do it
+ the unbelievable way :-). */
+
+#if HAVE_FSYNC
+ fsync (archive);
+#endif
+#ifdef FDFLUSH
+ ioctl (archive, FDFLUSH);
+#endif
+
+#ifdef MTIOCTOP
+ {
+ struct mtop operation;
+ int status;
+
+ operation.mt_op = MTBSF;
+ operation.mt_count = 1;
+ if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
+ {
+ if (errno != EIO
+ || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
+ status < 0))
+ {
+#endif
+ if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
+ {
+ /* Lseek failed. Try a different method. */
+ seek_warn (archive_name_array[0]);
+ return;
+ }
+#ifdef MTIOCTOP
+ }
+ }
+ }
+#endif
+
+ access_mode = ACCESS_READ;
+ now_verifying = 1;
+
+ flush_read ();
+ while (1)
+ {
+ enum read_header status = read_header (0);
+
+ if (status == HEADER_FAILURE)
+ {
+ int counter = 0;
+
+ while (status == HEADER_FAILURE);
+ {
+ counter++;
+ status = read_header (0);
+ }
+ ERROR ((0, 0,
+ _("VERIFY FAILURE: %d invalid header(s) detected"), counter));
+ }
+ if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
+ break;
+
+ diff_archive ();
+ }
+
+ access_mode = ACCESS_WRITE;
+ now_verifying = 0;
+}
diff --git a/contrib/tar/src/create.c b/contrib/tar/src/create.c
new file mode 100644
index 0000000..b3de6a8
--- /dev/null
+++ b/contrib/tar/src/create.c
@@ -0,0 +1,1550 @@
+/* Create a tar archive.
+ Copyright 1985,92,93,94,96,97,99,2000, 2001 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+
+#if !MSDOS
+# include <pwd.h>
+# include <grp.h>
+#endif
+
+#if HAVE_UTIME_H
+# include <utime.h>
+#else
+struct utimbuf
+ {
+ long actime;
+ long modtime;
+ };
+#endif
+
+#include <quotearg.h>
+
+#include "common.h"
+#include <hash.h>
+
+#ifndef MSDOS
+extern dev_t ar_dev;
+extern ino_t ar_ino;
+#endif
+
+struct link
+ {
+ dev_t dev;
+ ino_t ino;
+ char name[1];
+ };
+
+/* The maximum uintmax_t value that can be represented with DIGITS digits,
+ assuming that each digit is BITS_PER_DIGIT wide. */
+#define MAX_VAL_WITH_DIGITS(digits, bits_per_digit) \
+ ((digits) * (bits_per_digit) < sizeof (uintmax_t) * CHAR_BIT \
+ ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \
+ : (uintmax_t) -1)
+
+/* Convert VALUE to an octal representation suitable for tar headers.
+ Output to buffer WHERE with size SIZE.
+ The result is undefined if SIZE is 0 or if VALUE is too large to fit. */
+
+static void
+to_octal (uintmax_t value, char *where, size_t size)
+{
+ uintmax_t v = value;
+ size_t i = size;
+
+ do
+ {
+ where[--i] = '0' + (v & ((1 << LG_8) - 1));
+ v >>= LG_8;
+ }
+ while (i);
+}
+
+/* Convert NEGATIVE VALUE to a base-256 representation suitable for
+ tar headers. NEGATIVE is 1 if VALUE was negative before being cast
+ to uintmax_t, 0 otherwise. Output to buffer WHERE with size SIZE.
+ The result is undefined if SIZE is 0 or if VALUE is too large to
+ fit. */
+
+static void
+to_base256 (int negative, uintmax_t value, char *where, size_t size)
+{
+ uintmax_t v = value;
+ uintmax_t propagated_sign_bits =
+ ((uintmax_t) - negative << (CHAR_BIT * sizeof v - LG_256));
+ size_t i = size;
+
+ do
+ {
+ where[--i] = v & ((1 << LG_256) - 1);
+ v = propagated_sign_bits | (v >> LG_256);
+ }
+ while (i);
+}
+
+/* Convert NEGATIVE VALUE (which was originally of size VALSIZE) to
+ external form, using SUBSTITUTE (...) if VALUE won't fit. Output
+ to buffer WHERE with size SIZE. NEGATIVE is 1 iff VALUE was
+ negative before being cast to uintmax_t; its original bitpattern
+ can be deduced from VALSIZE, its original size before casting.
+ TYPE is the kind of value being output (useful for diagnostics).
+ Prefer the POSIX format of SIZE - 1 octal digits (with leading zero
+ digits), followed by '\0'. If this won't work, and if GNU or
+ OLDGNU format is allowed, use '\200' followed by base-256, or (if
+ NEGATIVE is nonzero) '\377' followed by two's complement base-256.
+ If neither format works, use SUBSTITUTE (...) instead. Pass to
+ SUBSTITUTE the address of an 0-or-1 flag recording whether the
+ substitute value is negative. */
+
+static void
+to_chars (int negative, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) PARAMS ((int *)),
+ char *where, size_t size, const char *type)
+{
+ int base256_allowed = (archive_format == GNU_FORMAT
+ || archive_format == OLDGNU_FORMAT);
+
+ /* Generate the POSIX octal representation if the number fits. */
+ if (! negative && value <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
+ {
+ where[size - 1] = '\0';
+ to_octal (value, where, size - 1);
+ }
+
+ /* Otherwise, generate the base-256 representation if we are
+ generating an old or new GNU format and if the number fits. */
+ else if (((negative ? -1 - value : value)
+ <= MAX_VAL_WITH_DIGITS (size - 1, LG_256))
+ && base256_allowed)
+ {
+ where[0] = negative ? -1 : 1 << (LG_256 - 1);
+ to_base256 (negative, value, where + 1, size - 1);
+ }
+
+ /* Otherwise, if the number is negative, and if it would not cause
+ ambiguity on this host by confusing positive with negative
+ values, then generate the POSIX octal representation of the value
+ modulo 2**(field bits). The resulting tar file is
+ machine-dependent, since it depends on the host word size. Yuck!
+ But this is the traditional behavior. */
+ else if (negative && valsize * CHAR_BIT <= (size - 1) * LG_8)
+ {
+ static int warned_once;
+ if (! warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Generating negative octal headers")));
+ }
+ where[size - 1] = '\0';
+ to_octal (value & MAX_VAL_WITH_DIGITS (valsize * CHAR_BIT, 1),
+ where, size - 1);
+ }
+
+ /* Otherwise, output a substitute value if possible (with a
+ warning), and an error message if not. */
+ else
+ {
+ uintmax_t maxval = (base256_allowed
+ ? MAX_VAL_WITH_DIGITS (size - 1, LG_256)
+ : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
+ char valbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char maxbuf[UINTMAX_STRSIZE_BOUND];
+ char minbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char const *minval_string;
+ char const *maxval_string = STRINGIFY_BIGINT (maxval, maxbuf);
+ char const *value_string;
+
+ if (base256_allowed)
+ {
+ uintmax_t m = maxval + 1 ? maxval + 1 : maxval / 2 + 1;
+ char *p = STRINGIFY_BIGINT (m, minbuf + 1);
+ *--p = '-';
+ minval_string = p;
+ }
+ else
+ minval_string = "0";
+
+ if (negative)
+ {
+ char *p = STRINGIFY_BIGINT (- value, valbuf + 1);
+ *--p = '-';
+ value_string = p;
+ }
+ else
+ value_string = STRINGIFY_BIGINT (value, valbuf);
+
+ if (substitute)
+ {
+ int negsub;
+ uintmax_t sub = substitute (&negsub) & maxval;
+ uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? - sub : sub;
+ char subbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char *sub_string = STRINGIFY_BIGINT (s, subbuf + 1);
+ if (negsub)
+ *--sub_string = '-';
+ WARN ((0, 0, _("value %s out of %s range %s..%s; substituting %s"),
+ value_string, type, minval_string, maxval_string,
+ sub_string));
+ to_chars (negsub, s, valsize, 0, where, size, type);
+ }
+ else
+ ERROR ((0, 0, _("value %s out of %s range %s..%s"),
+ value_string, type, minval_string, maxval_string));
+ }
+}
+
+static uintmax_t
+gid_substitute (int *negative)
+{
+ gid_t r;
+#ifdef GID_NOBODY
+ r = GID_NOBODY;
+#else
+ static gid_t gid_nobody;
+ if (!gid_nobody && !gname_to_gid ("nobody", &gid_nobody))
+ gid_nobody = -2;
+ r = gid_nobody;
+#endif
+ *negative = r < 0;
+ return r;
+}
+
+void
+gid_to_chars (gid_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
+}
+
+void
+major_to_chars (major_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "major_t");
+}
+
+void
+minor_to_chars (minor_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "minor_t");
+}
+
+void
+mode_to_chars (mode_t v, char *p, size_t s)
+{
+ /* In the common case where the internal and external mode bits are the same,
+ and we are not using POSIX or GNU format,
+ propagate all unknown bits to the external mode.
+ This matches historical practice.
+ Otherwise, just copy the bits we know about. */
+ int negative;
+ uintmax_t u;
+ if (S_ISUID == TSUID && S_ISGID == TSGID && S_ISVTX == TSVTX
+ && S_IRUSR == TUREAD && S_IWUSR == TUWRITE && S_IXUSR == TUEXEC
+ && S_IRGRP == TGREAD && S_IWGRP == TGWRITE && S_IXGRP == TGEXEC
+ && S_IROTH == TOREAD && S_IWOTH == TOWRITE && S_IXOTH == TOEXEC
+ && archive_format != POSIX_FORMAT
+ && archive_format != GNU_FORMAT)
+ {
+ negative = v < 0;
+ u = v;
+ }
+ else
+ {
+ negative = 0;
+ u = ((v & S_ISUID ? TSUID : 0)
+ | (v & S_ISGID ? TSGID : 0)
+ | (v & S_ISVTX ? TSVTX : 0)
+ | (v & S_IRUSR ? TUREAD : 0)
+ | (v & S_IWUSR ? TUWRITE : 0)
+ | (v & S_IXUSR ? TUEXEC : 0)
+ | (v & S_IRGRP ? TGREAD : 0)
+ | (v & S_IWGRP ? TGWRITE : 0)
+ | (v & S_IXGRP ? TGEXEC : 0)
+ | (v & S_IROTH ? TOREAD : 0)
+ | (v & S_IWOTH ? TOWRITE : 0)
+ | (v & S_IXOTH ? TOEXEC : 0));
+ }
+ to_chars (negative, u, sizeof v, 0, p, s, "mode_t");
+}
+
+void
+off_to_chars (off_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "off_t");
+}
+
+void
+size_to_chars (size_t v, char *p, size_t s)
+{
+ to_chars (0, (uintmax_t) v, sizeof v, 0, p, s, "size_t");
+}
+
+void
+time_to_chars (time_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "time_t");
+}
+
+static uintmax_t
+uid_substitute (int *negative)
+{
+ uid_t r;
+#ifdef UID_NOBODY
+ r = UID_NOBODY;
+#else
+ static uid_t uid_nobody;
+ if (!uid_nobody && !uname_to_uid ("nobody", &uid_nobody))
+ uid_nobody = -2;
+ r = uid_nobody;
+#endif
+ *negative = r < 0;
+ return r;
+}
+
+void
+uid_to_chars (uid_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
+}
+
+void
+uintmax_to_chars (uintmax_t v, char *p, size_t s)
+{
+ to_chars (0, v, sizeof v, 0, p, s, "uintmax_t");
+}
+
+/* Writing routines. */
+
+/* Zero 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). Zero at least two blocks, 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 ();
+ memset (pointer->buffer, 0, BLOCKSIZE);
+ set_next_block_after (pointer);
+ pointer = find_next_block ();
+ memset (pointer->buffer, 0, available_space_after (pointer));
+ 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)
+{
+ size_t size = strlen (p) + 1;
+ size_t 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, 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);
+ memset (header->buffer + size, 0, bufsize - size);
+ set_next_block_after (header + (size - 1) / BLOCKSIZE);
+}
+
+/* Return a suffix of the file NAME that is a relative file name.
+ Warn about `..' in file names. But return NAME if the user wants
+ absolute file names. */
+static char const *
+relativize (char const *name)
+{
+ if (! absolute_names_option)
+ {
+ {
+ static int warned_once;
+ if (! warned_once && contains_dot_dot (name))
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Member names contain `..'")));
+ }
+ }
+
+ {
+ size_t prefix_len = FILESYSTEM_PREFIX_LEN (name);
+
+ while (ISSLASH (name[prefix_len]))
+ prefix_len++;
+
+ if (prefix_len)
+ {
+ static int warned_once;
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Removing leading `%.*s' from member names"),
+ (int) prefix_len, name));
+ }
+ name += prefix_len;
+ }
+ }
+ }
+
+ return name;
+}
+
+/* Header handling. */
+
+/* Make a header block for the file whose stat info is st,
+ and return its address. */
+
+static union block *
+start_header (const char *name, struct stat *st)
+{
+ union block *header;
+
+ name = relativize (name);
+
+ if (sizeof header->header.name <= strlen (name))
+ 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 & ~MODE_ALL)
+ | 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)
+ MODE_TO_CHARS (st->st_mode & MODE_ALL, header->header.mode);
+ else
+ MODE_TO_CHARS (st->st_mode, header->header.mode);
+
+ UID_TO_CHARS (st->st_uid, header->header.uid);
+ GID_TO_CHARS (st->st_gid, header->header.gid);
+ OFF_TO_CHARS (st->st_size, header->header.size);
+ TIME_TO_CHARS (st->st_mtime, header->header.mtime);
+
+ if (incremental_option)
+ if (archive_format == OLDGNU_FORMAT)
+ {
+ TIME_TO_CHARS (st->st_atime, header->oldgnu_header.atime);
+ TIME_TO_CHARS (st->st_ctime, header->oldgnu_header.ctime);
+ }
+
+ header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
+
+ switch (archive_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;
+
+ default:
+ abort ();
+ }
+
+ 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)
+{
+ size_t i;
+ int 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, then a null. We use to_chars.
+ The final space is already there, from
+ checksumming, and to_chars doesn't modify it.
+
+ This is a fast way to do:
+
+ sprintf(header->header.chksum, "%6o", sum); */
+
+ uintmax_to_chars ((uintmax_t) sum, header->header.chksum, 7);
+
+ 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 ();
+ }
+
+ set_next_block_after (header);
+}
+
+/* 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)
+{
+ sp_array_size = 10;
+
+ /* Make room for our scratch space -- initially is 10 elts long. */
+
+ sparsearray = xmalloc (sp_array_size * sizeof (struct sp_array));
+}
+
+static off_t
+find_new_file_size (int sparses)
+{
+ int i;
+ off_t s = 0;
+ for (i = 0; i < sparses; i++)
+ s += sparsearray[i].numbytes;
+ return s;
+}
+
+/* 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)
+{
+ size_t numbytes = 0;
+ off_t offset = 0;
+ int file;
+ int sparses = 0;
+ ssize_t 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);
+
+ for (;;)
+ {
+ /* Realloc the scratch area as necessary. FIXME: should reallocate
+ only at beginning of a new instance of non-zero data. */
+
+ if (sp_array_size <= sparses)
+ {
+ sparsearray =
+ xrealloc (sparsearray,
+ 2 * sp_array_size * sizeof (struct sp_array));
+ sp_array_size *= 2;
+ }
+
+ count = safe_read (file, buffer, sizeof buffer);
+ if (count <= 0)
+ break;
+
+ /* Process one block. */
+
+ if (count == sizeof buffer)
+
+ if (zero_block_p (buffer))
+ {
+ if (numbytes)
+ {
+ sparsearray[sparses++].numbytes = numbytes;
+ numbytes = 0;
+ }
+ }
+ else
+ {
+ if (!numbytes)
+ sparsearray[sparses].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[sparses].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[sparses++].numbytes = numbytes;
+ else
+ {
+ sparsearray[sparses].offset = offset - 1;
+ sparsearray[sparses++].numbytes = 1;
+ }
+
+ return close (file) == 0 && 0 <= count ? sparses : 0;
+}
+
+static int
+finish_sparse_file (int file, off_t *sizeleft, off_t fullsize, char *name)
+{
+ union block *start;
+ size_t bufsize;
+ int sparses = 0;
+ ssize_t count;
+
+ while (*sizeleft > 0)
+ {
+ start = find_next_block ();
+ memset (start->buffer, 0, BLOCKSIZE);
+ bufsize = sparsearray[sparses].numbytes;
+ if (! bufsize)
+ abort ();
+
+ if (lseek (file, sparsearray[sparses++].offset, SEEK_SET) < 0)
+ {
+ (ignore_failed_read_option ? seek_warn_details : seek_error_details)
+ (name, sparsearray[sparses - 1].offset);
+ break;
+ }
+
+ /* If the number of bytes to be written here exceeds the size of
+ the temporary buffer, do it in steps. */
+
+ while (bufsize > BLOCKSIZE)
+ {
+ count = safe_read (file, start->buffer, BLOCKSIZE);
+ if (count < 0)
+ {
+ (ignore_failed_read_option
+ ? read_warn_details
+ : read_error_details)
+ (name, fullsize - *sizeleft, bufsize);
+ return 1;
+ }
+ bufsize -= count;
+ *sizeleft -= count;
+ set_next_block_after (start);
+ start = find_next_block ();
+ memset (start->buffer, 0, BLOCKSIZE);
+ }
+
+ {
+ char buffer[BLOCKSIZE];
+
+ clear_buffer (buffer);
+ count = safe_read (file, buffer, bufsize);
+ memcpy (start->buffer, buffer, BLOCKSIZE);
+ }
+
+ if (count < 0)
+ {
+ (ignore_failed_read_option
+ ? read_warn_details
+ : read_error_details)
+ (name, fullsize - *sizeleft, bufsize);
+ return 1;
+ }
+
+ *sizeleft -= count;
+ set_next_block_after (start);
+ }
+ free (sparsearray);
+#if 0
+ set_next_block_after (start + (count - 1) / BLOCKSIZE);
+#endif
+ return 0;
+}
+
+/* Main functions of this module. */
+
+void
+create_archive (void)
+{
+ char *p;
+
+ open_archive (ACCESS_WRITE);
+
+ if (incremental_option)
+ {
+ size_t buffer_size = 1000;
+ char *buffer = xmalloc (buffer_size);
+ const char *q;
+
+ collect_and_sort_names ();
+
+ while (p = name_from_list (), p)
+ if (!excluded_name (p))
+ dump_file (p, -1, (dev_t) 0);
+
+ blank_name_list ();
+ while (p = name_from_list (), p)
+ if (!excluded_name (p))
+ {
+ size_t plen = strlen (p);
+ if (buffer_size <= plen)
+ {
+ while ((buffer_size *= 2) <= plen)
+ continue;
+ buffer = xrealloc (buffer, buffer_size);
+ }
+ memcpy (buffer, p, plen);
+ if (! ISSLASH (buffer[plen - 1]))
+ buffer[plen++] = '/';
+ q = gnu_list_name->dir_contents;
+ if (q)
+ while (*q)
+ {
+ size_t qlen = strlen (q);
+ if (*q == 'Y')
+ {
+ if (buffer_size < plen + qlen)
+ {
+ while ((buffer_size *=2 ) < plen + qlen)
+ continue;
+ buffer = xrealloc (buffer, buffer_size);
+ }
+ strcpy (buffer + plen, q + 1);
+ dump_file (buffer, -1, (dev_t) 0);
+ }
+ q += qlen + 1;
+ }
+ }
+ free (buffer);
+ }
+ else
+ {
+ while (p = name_next (1), p)
+ if (!excluded_name (p))
+ dump_file (p, 1, (dev_t) 0);
+ }
+
+ write_eot ();
+ close_archive ();
+
+ if (listed_incremental_option)
+ write_directory_file ();
+}
+
+
+/* Calculate the hash of a link. */
+static unsigned
+hash_link (void const *entry, unsigned n_buckets)
+{
+ struct link const *link = entry;
+ return (uintmax_t) (link->dev ^ link->ino) % n_buckets;
+}
+
+/* Compare two links for equality. */
+static bool
+compare_links (void const *entry1, void const *entry2)
+{
+ struct link const *link1 = entry1;
+ struct link const *link2 = entry2;
+ return ((link1->dev ^ link2->dev) | (link1->ino ^ link2->ino)) == 0;
+}
+
+/* Dump a single file, recursing on directories. P is the file name
+ to dump. TOP_LEVEL tells whether this is a top-level call; zero
+ means no, positive means yes, and negative means an incremental
+ dump. PARENT_DEVICE is the device of P's
+ parent directory; it is examined only if TOP_LEVEL is zero.
+
+ Set 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 (char *p, int top_level, dev_t parent_device)
+{
+ union block *header;
+ char type;
+ union block *exhdr;
+ char save_typeflag;
+ time_t original_ctime;
+ struct utimbuf restore_times;
+
+ /* FIXME: `header' might be used uninitialized in this
+ function. Reported by Bruno Haible. */
+
+ if (interactive_option && !confirm ("add", p))
+ return;
+
+ if (deref_stat (dereference_option, p, &current_stat) != 0)
+ {
+ if (ignore_failed_read_option)
+ stat_warn (p);
+ else
+ stat_error (p);
+ return;
+ }
+
+ original_ctime = current_stat.st_ctime;
+ restore_times.actime = current_stat.st_atime;
+ restore_times.modtime = current_stat.st_mtime;
+
+#ifdef S_ISHIDDEN
+ if (S_ISHIDDEN (current_stat.st_mode))
+ {
+ char *new = (char *) alloca (strlen (p) + 2);
+ if (new)
+ {
+ strcpy (new, p);
+ strcat (new, "@");
+ p = new;
+ }
+ }
+#endif
+
+ /* See if we want only new files, and check if this one is too old to
+ put in the archive. */
+
+ if ((0 < top_level || !incremental_option)
+ && !S_ISDIR (current_stat.st_mode)
+ && current_stat.st_mtime < newer_mtime_option
+ && (!after_date_option || current_stat.st_ctime < newer_ctime_option))
+ {
+ if (0 < top_level)
+ WARN ((0, 0, _("%s: file is unchanged; not dumped"),
+ quotearg_colon (p)));
+ /* FIXME: recheck this return. */
+ return;
+ }
+
+#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)
+ {
+ WARN ((0, 0, _("%s: file is the archive; not dumped"),
+ quotearg_colon (p)));
+ return;
+ }
+#endif
+
+ if (S_ISDIR (current_stat.st_mode))
+ {
+ char *directory;
+ char const *entry;
+ size_t entrylen;
+ char *namebuf;
+ size_t buflen;
+ size_t len;
+ dev_t our_device = current_stat.st_dev;
+
+ errno = 0;
+
+ directory = savedir (p);
+ if (! directory)
+ {
+ if (ignore_failed_read_option)
+ savedir_warn (p);
+ else
+ savedir_error (p);
+ return;
+ }
+
+ /* Build new prototype name. Ensure exactly one trailing slash. */
+
+ len = strlen (p);
+ buflen = len + NAME_FIELD_SIZE;
+ namebuf = xmalloc (buflen + 1);
+ memcpy (namebuf, p, len);
+ while (len >= 1 && ISSLASH (namebuf[len - 1]))
+ len--;
+ namebuf[len++] = '/';
+ namebuf[len] = '\0';
+
+ if (! is_avoided_name (namebuf))
+ {
+ /* The condition above used to be "archive_format != V7_FORMAT".
+ GNU tar was 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 (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 (!incremental_option)
+ finish_header (header); /* done with directory header */
+ }
+
+ if (incremental_option && gnu_list_name->dir_contents)
+ {
+ off_t sizeleft;
+ off_t totsize;
+ size_t bufsize;
+ union block *start;
+ ssize_t count;
+ const char *buffer, *p_buffer;
+
+ buffer = gnu_list_name->dir_contents; /* FOO */
+ totsize = 0;
+ for (p_buffer = buffer; p_buffer && *p_buffer;)
+ {
+ size_t tmp;
+
+ tmp = strlen (p_buffer) + 1;
+ totsize += tmp;
+ p_buffer += tmp;
+ }
+ totsize++;
+ OFF_TO_CHARS (totsize, header->header.size);
+ finish_header (header);
+ p_buffer = buffer;
+ sizeleft = totsize;
+ while (sizeleft > 0)
+ {
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, p);
+ save_sizeleft = sizeleft;
+ save_totsize = totsize;
+ }
+ start = find_next_block ();
+ bufsize = available_space_after (start);
+ if (sizeleft < bufsize)
+ {
+ bufsize = sizeleft;
+ count = bufsize % BLOCKSIZE;
+ if (count)
+ memset (start->buffer + sizeleft, 0, BLOCKSIZE - count);
+ }
+ memcpy (start->buffer, p_buffer, bufsize);
+ sizeleft -= bufsize;
+ p_buffer += bufsize;
+ set_next_block_after (start + (bufsize - 1) / BLOCKSIZE);
+ }
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+ goto finish_dir;
+ }
+
+ /* 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 (! recursion_option)
+ goto finish_dir;
+
+ /* 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 (verbose_option)
+ WARN ((0, 0,
+ _("%s: file is on a different filesystem; not dumped"),
+ quotearg_colon (p)));
+ goto finish_dir;
+ }
+
+ /* Now output all the files in the directory. */
+
+ /* FIXME: Should speed this up by cd-ing into the dir. */
+
+ for (entry = directory;
+ (entrylen = strlen (entry)) != 0;
+ entry += entrylen + 1)
+ {
+ if (buflen <= len + entrylen)
+ {
+ buflen = len + entrylen;
+ namebuf = xrealloc (namebuf, buflen + 1);
+ }
+ strcpy (namebuf + len, entry);
+ if (!excluded_name (namebuf))
+ dump_file (namebuf, 0, our_device);
+ }
+
+ finish_dir:
+
+ free (directory);
+ free (namebuf);
+ if (atime_preserve_option)
+ utime (p, &restore_times);
+ return;
+ }
+ else if (is_avoided_name (p))
+ return;
+ else
+ {
+ /* Check for multiple links.
+
+ We maintain a table of all such files that we've written so
+ far. Any time we see another, we check the table and avoid
+ dumping the data again if we've done it once already. */
+
+ if (1 < current_stat.st_nlink)
+ {
+ static Hash_table *link_table;
+ struct link *lp = xmalloc (offsetof (struct link, name)
+ + strlen (p) + 1);
+ struct link *dup;
+ lp->ino = current_stat.st_ino;
+ lp->dev = current_stat.st_dev;
+ strcpy (lp->name, p);
+
+ if (! ((link_table
+ || (link_table = hash_initialize (0, 0, hash_link,
+ compare_links, 0)))
+ && (dup = hash_insert (link_table, lp))))
+ xalloc_die ();
+
+ if (dup != lp)
+ {
+ /* We found a link. */
+ char const *link_name = relativize (dup->name);
+
+ free (lp);
+
+ if (NAME_FIELD_SIZE <= strlen (link_name))
+ write_long (link_name, GNUTYPE_LONGLINK);
+ assign_string (&current_link_name, link_name);
+
+ current_stat.st_size = 0;
+ header = start_header (p, &current_stat);
+ strncpy (header->header.linkname, link_name, NAME_FIELD_SIZE);
+
+ /* Force null termination. */
+ header->header.linkname[NAME_FIELD_SIZE - 1] = 0;
+
+ header->header.typeflag = LNKTYPE;
+ finish_header (header);
+
+ /* FIXME: Maybe remove from table after all links found? */
+
+ if (remove_files_option && unlink (p) != 0)
+ unlink_error (p);
+
+ /* We dumped it. */
+ return;
+ }
+ }
+
+ /* This is not a link to a previously dumped file, so dump it. */
+
+ if (S_ISREG (current_stat.st_mode)
+ || S_ISCTG (current_stat.st_mode))
+ {
+ int f; /* file descriptor */
+ size_t bufsize;
+ ssize_t count;
+ off_t sizeleft;
+ union block *start;
+ int header_moved;
+ char isextended = 0;
+ int sparses = 0;
+
+ header_moved = 0;
+
+ if (sparse_option)
+ {
+ /* 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. */
+
+ /* 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 (ST_NBLOCKS (current_stat)
+ < (current_stat.st_size / ST_NBLOCKSIZE
+ + (current_stat.st_size % ST_NBLOCKSIZE != 0)))
+ {
+ int counter;
+
+ header = start_header (p, &current_stat);
+ header->header.typeflag = GNUTYPE_SPARSE;
+ header_moved = 1;
+
+ /* Call the routine that figures out the layout of the
+ sparse file in question. SPARSES is the index of the
+ first unused element of the "sparsearray," i.e.,
+ the number of elements it needed to describe the file. */
+
+ sparses = deal_with_sparse (p, header);
+
+ /* See if we'll need an extended header later. */
+
+ if (SPARSES_IN_OLDGNU_HEADER < sparses)
+ 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. */
+
+ OFF_TO_CHARS (current_stat.st_size,
+ 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. */
+
+ current_stat.st_size = find_new_file_size (sparses);
+ OFF_TO_CHARS (current_stat.st_size, header->header.size);
+
+ for (counter = 0;
+ counter < sparses && counter < SPARSES_IN_OLDGNU_HEADER;
+ counter++)
+ {
+ OFF_TO_CHARS (sparsearray[counter].offset,
+ header->oldgnu_header.sp[counter].offset);
+ SIZE_TO_CHARS (sparsearray[counter].numbytes,
+ header->oldgnu_header.sp[counter].numbytes);
+ }
+ }
+ }
+
+ sizeleft = current_stat.st_size;
+
+ /* 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
+ && MODE_R == (MODE_R & current_stat.st_mode)))
+ f = -1;
+ else
+ {
+ f = open (p, O_RDONLY | O_BINARY);
+ if (f < 0)
+ {
+ if (! top_level && errno == ENOENT)
+ WARN ((0, 0, _("%s: File removed before we read it"),
+ quotearg_colon (p)));
+ else
+ (ignore_failed_read_option ? open_warn : open_error) (p);
+ return;
+ }
+ }
+
+ /* If the file is sparse, we've already taken care of this. */
+
+ if (!header_moved)
+ header = start_header (p, &current_stat);
+
+ /* Mark contiguous files, if we support them. */
+
+ if (archive_format != V7_FORMAT && S_ISCTG (current_stat.st_mode))
+ header->header.typeflag = CONTTYPE;
+
+ isextended = header->oldgnu_header.isextended;
+ save_typeflag = header->header.typeflag;
+ finish_header (header);
+ if (isextended)
+ {
+ int sparses_emitted = SPARSES_IN_OLDGNU_HEADER;
+
+ for (;;)
+ {
+ int i;
+ exhdr = find_next_block ();
+ memset (exhdr->buffer, 0, BLOCKSIZE);
+ for (i = 0;
+ (i < SPARSES_IN_SPARSE_HEADER
+ && sparses_emitted + i < sparses);
+ i++)
+ {
+ SIZE_TO_CHARS (sparsearray[sparses_emitted + i].numbytes,
+ exhdr->sparse_header.sp[i].numbytes);
+ OFF_TO_CHARS (sparsearray[sparses_emitted + i].offset,
+ exhdr->sparse_header.sp[i].offset);
+ }
+ set_next_block_after (exhdr);
+ sparses_emitted += i;
+ if (sparses == sparses_emitted)
+ break;
+ exhdr->sparse_header.isextended = 1;
+ }
+ }
+ if (save_typeflag == GNUTYPE_SPARSE)
+ {
+ if (f < 0
+ || finish_sparse_file (f, &sizeleft,
+ current_stat.st_size, p))
+ goto padit;
+ }
+ else
+ while (sizeleft > 0)
+ {
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, p);
+ save_sizeleft = sizeleft;
+ save_totsize = current_stat.st_size;
+ }
+ start = find_next_block ();
+
+ bufsize = available_space_after (start);
+
+ if (sizeleft < bufsize)
+ {
+ /* Last read -- zero out area beyond. */
+
+ bufsize = sizeleft;
+ count = bufsize % BLOCKSIZE;
+ if (count)
+ memset (start->buffer + sizeleft, 0, BLOCKSIZE - count);
+ }
+ if (f < 0)
+ count = bufsize;
+ else
+ count = safe_read (f, start->buffer, bufsize);
+ if (count < 0)
+ {
+ (ignore_failed_read_option
+ ? read_warn_details
+ : read_error_details)
+ (p, current_stat.st_size - sizeleft, bufsize);
+ goto padit;
+ }
+ sizeleft -= bufsize;
+
+ /* This is nonportable (the type of set_next_block_after's arg). */
+
+ set_next_block_after (start + (bufsize - 1) / BLOCKSIZE);
+
+
+ if (count != bufsize)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ memset (start->buffer + count, 0, bufsize - count);
+ WARN ((0, 0,
+ _("%s: File shrank by %s bytes; padding with zeros"),
+ quotearg_colon (p),
+ STRINGIFY_BIGINT (sizeleft, buf)));
+ if (! ignore_failed_read_option)
+ exit_status = TAREXIT_FAILURE;
+ goto padit; /* short read */
+ }
+ }
+
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+
+ if (f >= 0)
+ {
+ struct stat final_stat;
+ if (fstat (f, &final_stat) != 0)
+ {
+ if (ignore_failed_read_option)
+ stat_warn (p);
+ else
+ stat_error (p);
+ }
+ else if (final_stat.st_ctime != original_ctime)
+ {
+ char const *qp = quotearg_colon (p);
+ WARN ((0, 0, _("%s: file changed as we read it"), qp));
+ if (! ignore_failed_read_option)
+ exit_status = TAREXIT_FAILURE;
+ }
+ if (close (f) != 0)
+ {
+ if (ignore_failed_read_option)
+ close_warn (p);
+ else
+ close_error (p);
+ }
+ if (atime_preserve_option)
+ utime (p, &restore_times);
+ }
+ if (remove_files_option)
+ {
+ if (unlink (p) == -1)
+ unlink_error (p);
+ }
+ return;
+
+ /* 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 = find_next_block ();
+ memset (start->buffer, 0, BLOCKSIZE);
+ set_next_block_after (start);
+ sizeleft -= BLOCKSIZE;
+ }
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+ if (f >= 0)
+ {
+ close (f);
+ if (atime_preserve_option)
+ utime (p, &restore_times);
+ }
+ return;
+ }
+#ifdef HAVE_READLINK
+ else if (S_ISLNK (current_stat.st_mode))
+ {
+ char *buffer;
+ int size;
+ size_t linklen = current_stat.st_size;
+ if (linklen != current_stat.st_size || linklen + 1 == 0)
+ xalloc_die ();
+ buffer = (char *) alloca (linklen + 1);
+ size = readlink (p, buffer, linklen + 1);
+ if (size < 0)
+ {
+ if (ignore_failed_read_option)
+ readlink_warn (p);
+ else
+ readlink_error (p);
+ 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);
+ 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)
+ unlink_error (p);
+ }
+ return;
+ }
+#endif
+ else if (S_ISCHR (current_stat.st_mode))
+ type = CHRTYPE;
+ else if (S_ISBLK (current_stat.st_mode))
+ type = BLKTYPE;
+ else if (S_ISFIFO (current_stat.st_mode))
+ type = FIFOTYPE;
+ else if (S_ISSOCK (current_stat.st_mode))
+ {
+ WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p)));
+ return;
+ }
+ else if (S_ISDOOR (current_stat.st_mode))
+ {
+ WARN ((0, 0, _("%s: door ignored"), quotearg_colon (p)));
+ return;
+ }
+ else
+ goto unknown;
+ }
+
+ if (archive_format == V7_FORMAT)
+ goto unknown;
+
+ current_stat.st_size = 0; /* force 0 size */
+ header = start_header (p, &current_stat);
+ header->header.typeflag = type;
+
+ if (type != FIFOTYPE)
+ {
+ MAJOR_TO_CHARS (major (current_stat.st_rdev), header->header.devmajor);
+ MINOR_TO_CHARS (minor (current_stat.st_rdev), header->header.devminor);
+ }
+
+ finish_header (header);
+ if (remove_files_option)
+ {
+ if (unlink (p) == -1)
+ unlink_error (p);
+ }
+ return;
+
+unknown:
+ WARN ((0, 0, _("%s: Unknown file type; file ignored"),
+ quotearg_colon (p)));
+ if (! ignore_failed_read_option)
+ exit_status = TAREXIT_FAILURE;
+}
diff --git a/contrib/tar/src/delete.c b/contrib/tar/src/delete.c
new file mode 100644
index 0000000..ad7b590
--- /dev/null
+++ b/contrib/tar/src/delete.c
@@ -0,0 +1,364 @@
+/* Delete entries from a tar archive.
+
+ Copyright (C) 1988, 1992, 1994, 1996, 1997, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+
+#include "common.h"
+#include "rmt.h"
+
+static union block *new_record;
+static int new_blocks;
+static bool acting_as_filter;
+
+/* FIXME: This module should not directly handle the following
+ variables, instead, the interface should be cleaned up. */
+extern union block *record_start;
+extern union block *record_end;
+extern union block *current_block;
+extern union block *recent_long_name;
+extern union block *recent_long_link;
+extern size_t recent_long_name_blocks;
+extern size_t recent_long_link_blocks;
+extern off_t records_read;
+extern off_t records_written;
+
+/* The number of records skipped at the start of the archive, when
+ passing over members that are not deleted. */
+static off_t records_skipped;
+
+/* Move archive descriptor by COUNT records worth. If COUNT is
+ positive we move forward, else we move negative. If it's a tape,
+ MTIOCTOP had better work. If it's something else, we try to seek
+ on it. If we can't seek, we lose! */
+static void
+move_archive (off_t count)
+{
+ if (count == 0)
+ return;
+
+#ifdef MTIOCTOP
+ {
+ struct mtop operation;
+
+ if (count < 0
+ ? (operation.mt_op = MTBSR,
+ operation.mt_count = -count,
+ operation.mt_count == -count)
+ : (operation.mt_op = MTFSR,
+ operation.mt_count = count,
+ operation.mt_count == count))
+ {
+ if (0 <= rmtioctl (archive, MTIOCTOP, (char *) &operation))
+ return;
+
+ if (errno == EIO
+ && 0 <= rmtioctl (archive, MTIOCTOP, (char *) &operation))
+ return;
+ }
+ }
+#endif /* MTIOCTOP */
+
+ {
+ off_t position0 = rmtlseek (archive, (off_t) 0, SEEK_CUR);
+ off_t increment = record_size * (off_t) count;
+ off_t position = position0 + increment;
+
+ if (increment / count != record_size
+ || (position < position0) != (increment < 0)
+ || (position = position < 0 ? 0 : position,
+ rmtlseek (archive, position, SEEK_SET) != position))
+ seek_error_details (archive_name_array[0], position);
+
+ return;
+ }
+}
+
+/* Write out the record which has been filled. If MOVE_BACK_FLAG,
+ backspace to where we started. */
+static void
+write_record (int move_back_flag)
+{
+ union block *save_record = record_start;
+ record_start = new_record;
+
+ if (acting_as_filter)
+ {
+ archive = STDOUT_FILENO;
+ flush_write ();
+ archive = STDIN_FILENO;
+ }
+ else
+ {
+ move_archive ((records_written + records_skipped) - records_read);
+ flush_write ();
+ }
+
+ record_start = save_record;
+
+ if (move_back_flag)
+ {
+ /* Move the tape head back to where we were. */
+
+ if (! acting_as_filter)
+ move_archive (records_read - (records_written + records_skipped));
+ }
+
+ new_blocks = 0;
+}
+
+static void
+write_recent_blocks (union block *h, size_t blocks)
+{
+ size_t i;
+ for (i = 0; i < blocks; i++)
+ {
+ new_record[new_blocks++] = h[i];
+ if (new_blocks == blocking_factor)
+ write_record (1);
+ }
+}
+
+void
+delete_archive_members (void)
+{
+ enum read_header logical_status = HEADER_STILL_UNREAD;
+ enum read_header previous_status = HEADER_STILL_UNREAD;
+
+ /* FIXME: Should clean the routine before cleaning these variables :-( */
+ struct name *name;
+ off_t blocks_to_skip = 0;
+ off_t blocks_to_keep = 0;
+ int kept_blocks_in_record;
+
+ name_gather ();
+ open_archive (ACCESS_UPDATE);
+ acting_as_filter = strcmp (archive_name_array[0], "-") == 0;
+
+ do
+ {
+ enum read_header status = read_header (1);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ abort ();
+
+ case HEADER_SUCCESS:
+ if (name = name_scan (current_file_name), !name)
+ {
+ skip_member ();
+ break;
+ }
+ name->found = 1;
+ /* Fall through. */
+ case HEADER_SUCCESS_EXTENDED:
+ logical_status = status;
+ break;
+
+ case HEADER_ZERO_BLOCK:
+ if (ignore_zeros_option)
+ {
+ set_next_block_after (current_header);
+ break;
+ }
+ /* Fall through. */
+ case HEADER_END_OF_FILE:
+ logical_status = HEADER_END_OF_FILE;
+ break;
+
+ case HEADER_FAILURE:
+ set_next_block_after (current_header);
+ switch (previous_status)
+ {
+ case HEADER_STILL_UNREAD:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_SUCCESS:
+ case HEADER_ZERO_BLOCK:
+ ERROR ((0, 0, _("Skipping to next header")));
+ /* Fall through. */
+
+ case HEADER_FAILURE:
+ break;
+
+ case HEADER_END_OF_FILE:
+ abort ();
+ }
+ break;
+ }
+
+ previous_status = status;
+ }
+ while (logical_status == HEADER_STILL_UNREAD);
+
+ records_skipped = records_read - 1;
+ new_record = xmalloc (record_size);
+
+ if (logical_status == HEADER_SUCCESS
+ || logical_status == HEADER_SUCCESS_EXTENDED)
+ {
+ write_archive_to_stdout = 0;
+
+ /* Save away blocks before this one in this record. */
+
+ new_blocks = current_block - record_start;
+ if (new_blocks)
+ memcpy (new_record, record_start, new_blocks * BLOCKSIZE);
+
+ if (logical_status == HEADER_SUCCESS)
+ {
+ /* FIXME: Pheew! This is crufty code! */
+ logical_status = HEADER_STILL_UNREAD;
+ goto flush_file;
+ }
+
+ /* FIXME: Solaris 2.4 Sun cc (the ANSI one, not the old K&R) says:
+ "delete.c", line 223: warning: loop not entered at top
+ Reported by Bruno Haible. */
+ while (1)
+ {
+ enum read_header status;
+
+ /* Fill in a record. */
+
+ if (current_block == record_end)
+ flush_archive ();
+ status = read_header (0);
+
+ if (status == HEADER_ZERO_BLOCK && ignore_zeros_option)
+ {
+ set_next_block_after (current_header);
+ continue;
+ }
+ if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK)
+ {
+ logical_status = HEADER_END_OF_FILE;
+ break;
+ }
+
+ if (status == HEADER_FAILURE)
+ {
+ ERROR ((0, 0, _("Deleting non-header from archive")));
+ set_next_block_after (current_header);
+ continue;
+ }
+
+ /* Found another header. */
+
+ if (name = name_scan (current_file_name), name)
+ {
+ name->found = 1;
+ flush_file:
+ set_next_block_after (current_header);
+ blocks_to_skip = (current_stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+
+ while (record_end - current_block <= blocks_to_skip)
+ {
+ blocks_to_skip -= (record_end - current_block);
+ flush_archive ();
+ }
+ current_block += blocks_to_skip;
+ blocks_to_skip = 0;
+ continue;
+ }
+
+ /* Copy header. */
+
+ write_recent_blocks (recent_long_name, recent_long_name_blocks);
+ write_recent_blocks (recent_long_link, recent_long_link_blocks);
+ new_record[new_blocks] = *current_header;
+ new_blocks++;
+ blocks_to_keep
+ = (current_stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+ set_next_block_after (current_header);
+ if (new_blocks == blocking_factor)
+ write_record (1);
+
+ /* Copy data. */
+
+ kept_blocks_in_record = record_end - current_block;
+ if (kept_blocks_in_record > blocks_to_keep)
+ kept_blocks_in_record = blocks_to_keep;
+
+ while (blocks_to_keep)
+ {
+ int count;
+
+ if (current_block == record_end)
+ {
+ flush_read ();
+ current_block = record_start;
+ kept_blocks_in_record = blocking_factor;
+ if (kept_blocks_in_record > blocks_to_keep)
+ kept_blocks_in_record = blocks_to_keep;
+ }
+ count = kept_blocks_in_record;
+ if (blocking_factor - new_blocks < count)
+ count = blocking_factor - new_blocks;
+
+ if (! count)
+ abort ();
+
+ memcpy (new_record + new_blocks, current_block, count * BLOCKSIZE);
+ new_blocks += count;
+ current_block += count;
+ blocks_to_keep -= count;
+ kept_blocks_in_record -= count;
+
+ if (new_blocks == blocking_factor)
+ write_record (1);
+ }
+ }
+ }
+
+ if (logical_status == HEADER_END_OF_FILE)
+ {
+ /* Write the end of tape. FIXME: we can't use write_eot here,
+ as it gets confused when the input is at end of file. */
+
+ int total_zero_blocks = 0;
+
+ do
+ {
+ int zero_blocks = blocking_factor - new_blocks;
+ memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks);
+ total_zero_blocks += zero_blocks;
+ write_record (total_zero_blocks < 2);
+ }
+ while (total_zero_blocks < 2);
+ }
+
+ free (new_record);
+
+ if (! acting_as_filter && ! _isrmt (archive))
+ {
+#if MSDOS
+ int status = write (archive, "", 0);
+#else
+ off_t pos = lseek (archive, (off_t) 0, SEEK_CUR);
+ int status = pos < 0 ? -1 : ftruncate (archive, pos);
+#endif
+ if (status != 0)
+ truncate_warn (archive_name_array[0]);
+ }
+
+ close_archive ();
+ names_notfound ();
+}
diff --git a/contrib/tar/src/extract.c b/contrib/tar/src/extract.c
new file mode 100644
index 0000000..2a3f9bf
--- /dev/null
+++ b/contrib/tar/src/extract.c
@@ -0,0 +1,1313 @@
+/* Extract files from a tar archive.
+
+ Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
+ 2001 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 1985-11-19.
+
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+#include <quotearg.h>
+
+#if HAVE_UTIME_H
+# include <utime.h>
+#else
+struct utimbuf
+ {
+ long actime;
+ long modtime;
+ };
+#endif
+
+#include "common.h"
+
+int we_are_root; /* true if our effective uid == 0 */
+static mode_t newdir_umask; /* umask when creating new directories */
+static mode_t current_umask; /* current umask (which is set to 0 if -p) */
+
+/* Status of the permissions of a file that we are extracting. */
+enum permstatus
+{
+ /* This file may have existed already; its permissions are unknown. */
+ UNKNOWN_PERMSTATUS,
+
+ /* This file was created using the permissions from the archive. */
+ ARCHIVED_PERMSTATUS,
+
+ /* This is an intermediate directory; the archive did not specify
+ its permissions. */
+ INTERDIR_PERMSTATUS
+};
+
+/* List of directories whose statuses we need to extract after we've
+ finished extracting their subsidiary files. If you consider each
+ contiguous subsequence of elements of the form [D]?[^D]*, where [D]
+ represents an element where AFTER_SYMLINKS is nonzero and [^D]
+ represents an element where AFTER_SYMLINKS is zero, then the head
+ of the subsequence has the longest name, and each non-head element
+ in the prefix is an ancestor (in the directory hierarchy) of the
+ preceding element. */
+
+struct delayed_set_stat
+ {
+ struct delayed_set_stat *next;
+ struct stat stat_info;
+ size_t file_name_len;
+ mode_t invert_permissions;
+ enum permstatus permstatus;
+ bool after_symlinks;
+ char file_name[1];
+ };
+
+static struct delayed_set_stat *delayed_set_stat_head;
+
+/* List of symbolic links whose creation we have delayed. */
+struct delayed_symlink
+ {
+ /* The next delayed symbolic link in the list. */
+ struct delayed_symlink *next;
+
+ /* The device, inode number and last-modified time of the placeholder. */
+ dev_t dev;
+ ino_t ino;
+ time_t mtime;
+
+ /* The desired owner and group of the symbolic link. */
+ uid_t uid;
+ gid_t gid;
+
+ /* A list of sources for this symlink. The sources are all to be
+ hard-linked together. */
+ struct string_list *sources;
+
+ /* The desired target of the desired link. */
+ char target[1];
+ };
+
+static struct delayed_symlink *delayed_symlink_head;
+
+struct string_list
+ {
+ struct string_list *next;
+ char string[1];
+ };
+
+/* Set up to extract files. */
+void
+extr_init (void)
+{
+ we_are_root = geteuid () == 0;
+ same_permissions_option += we_are_root;
+ same_owner_option += we_are_root;
+ xalloc_fail_func = extract_finish;
+
+ /* Option -p clears the kernel umask, so it does not affect proper
+ restoration of file permissions. New intermediate directories will
+ comply with umask at start of program. */
+
+ newdir_umask = umask (0);
+ if (0 < same_permissions_option)
+ current_umask = 0;
+ else
+ {
+ umask (newdir_umask); /* restore the kernel umask */
+ current_umask = newdir_umask;
+ }
+}
+
+/* If restoring permissions, restore the mode for FILE_NAME from
+ information given in *STAT_INFO (where *CURRENT_STAT_INFO gives
+ the current status if CURRENT_STAT_INFO is nonzero); otherwise invert the
+ INVERT_PERMISSIONS bits from the file's current permissions.
+ PERMSTATUS specifies the status of the file's permissions.
+ TYPEFLAG specifies the type of the file. */
+static void
+set_mode (char const *file_name, struct stat const *stat_info,
+ struct stat const *current_stat_info,
+ mode_t invert_permissions, enum permstatus permstatus,
+ char typeflag)
+{
+ mode_t mode;
+
+ if (0 < same_permissions_option
+ && permstatus != INTERDIR_PERMSTATUS)
+ {
+ mode = stat_info->st_mode;
+
+ /* If we created the file and it has a usual mode, then its mode
+ is normally set correctly already. But on many hosts, some
+ directories inherit the setgid bits from their parents, so we
+ we must set directories' modes explicitly. */
+ if (permstatus == ARCHIVED_PERMSTATUS
+ && ! (mode & ~ MODE_RWX)
+ && typeflag != DIRTYPE
+ && typeflag != GNUTYPE_DUMPDIR)
+ return;
+ }
+ else if (! invert_permissions)
+ return;
+ else
+ {
+ /* We must inspect a directory's current permissions, since the
+ directory may have inherited its setgid bit from its parent.
+
+ INVERT_PERMISSIONS happens to be nonzero only for directories
+ that we created, so there's no point optimizing this code for
+ other cases. */
+ struct stat st;
+ if (! current_stat_info)
+ {
+ if (stat (file_name, &st) != 0)
+ {
+ stat_error (file_name);
+ return;
+ }
+ current_stat_info = &st;
+ }
+ mode = current_stat_info->st_mode ^ invert_permissions;
+ }
+
+ if (chmod (file_name, mode) != 0)
+ chmod_error_details (file_name, mode);
+}
+
+/* Check time after successfully setting FILE_NAME's time stamp to T. */
+static void
+check_time (char const *file_name, time_t t)
+{
+ time_t now;
+ if (start_time < t && (now = time (0)) < t)
+ WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"),
+ file_name, tartime (t), (unsigned long) (t - now)));
+}
+
+/* Restore stat attributes (owner, group, mode and times) for
+ FILE_NAME, using information given in *STAT_INFO.
+ If CURRENT_STAT_INFO is nonzero, *CURRENT_STAT_INFO is the
+ file's currernt status.
+ If not restoring permissions, invert the
+ INVERT_PERMISSIONS bits from the file's current permissions.
+ PERMSTATUS specifies the status of the file's permissions.
+ TYPEFLAG specifies the type of the file. */
+
+/* FIXME: About proper restoration of symbolic link attributes, we still do
+ not have it right. Pretesters' reports tell us we need further study and
+ probably more configuration. For now, just use lchown if it exists, and
+ punt for the rest. Sigh! */
+
+static void
+set_stat (char const *file_name, struct stat const *stat_info,
+ struct stat const *current_stat_info,
+ mode_t invert_permissions, enum permstatus permstatus,
+ char typeflag)
+{
+ struct utimbuf utimbuf;
+
+ if (typeflag != SYMTYPE)
+ {
+ /* We do the utime before the chmod because some versions of utime are
+ broken and trash the modes of the file. */
+
+ if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
+ {
+ /* We set the accessed time to `now', which is really the time we
+ started extracting files, unless incremental_option is used, in
+ which case .st_atime is used. */
+
+ /* FIXME: incremental_option should set ctime too, but how? */
+
+ if (incremental_option)
+ utimbuf.actime = stat_info->st_atime;
+ else
+ utimbuf.actime = start_time;
+
+ utimbuf.modtime = stat_info->st_mtime;
+
+ if (utime (file_name, &utimbuf) < 0)
+ utime_error (file_name);
+ else
+ {
+ check_time (file_name, stat_info->st_atime);
+ check_time (file_name, stat_info->st_mtime);
+ }
+ }
+
+ /* Some systems allow non-root users to give files away. Once this
+ done, it is not possible anymore to change file permissions, so we
+ have to set permissions prior to possibly giving files away. */
+
+ set_mode (file_name, stat_info, current_stat_info,
+ invert_permissions, permstatus, typeflag);
+ }
+
+ if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
+ {
+ /* When lchown exists, it should be used to change the attributes of
+ the symbolic link itself. In this case, a mere chown would change
+ the attributes of the file the symbolic link is pointing to, and
+ should be avoided. */
+
+ if (typeflag == SYMTYPE)
+ {
+#if HAVE_LCHOWN
+ if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
+ chown_error_details (file_name,
+ stat_info->st_uid, stat_info->st_gid);
+#endif
+ }
+ else
+ {
+ if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
+ chown_error_details (file_name,
+ stat_info->st_uid, stat_info->st_gid);
+
+ /* On a few systems, and in particular, those allowing to give files
+ away, changing the owner or group destroys the suid or sgid bits.
+ So let's attempt setting these bits once more. */
+ if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
+ set_mode (file_name, stat_info, 0,
+ invert_permissions, permstatus, typeflag);
+ }
+ }
+}
+
+/* Remember to restore stat attributes (owner, group, mode and times)
+ for the directory FILE_NAME, using information given in *STAT_INFO,
+ once we stop extracting files into that directory.
+ If not restoring permissions, remember to invert the
+ INVERT_PERMISSIONS bits from the file's current permissions.
+ PERMSTATUS specifies the status of the file's permissions. */
+static void
+delay_set_stat (char const *file_name, struct stat const *stat_info,
+ mode_t invert_permissions, enum permstatus permstatus)
+{
+ size_t file_name_len = strlen (file_name);
+ struct delayed_set_stat *data =
+ xmalloc (offsetof (struct delayed_set_stat, file_name)
+ + file_name_len + 1);
+ data->file_name_len = file_name_len;
+ strcpy (data->file_name, file_name);
+ data->invert_permissions = invert_permissions;
+ data->permstatus = permstatus;
+ data->after_symlinks = 0;
+ data->stat_info = *stat_info;
+ data->next = delayed_set_stat_head;
+ delayed_set_stat_head = data;
+}
+
+/* Update the delayed_set_stat info for an intermediate directory
+ created on the path to DIR_NAME. The intermediate directory turned
+ out to be the same as this directory, e.g. due to ".." or symbolic
+ links. *DIR_STAT_INFO is the status of the directory. */
+static void
+repair_delayed_set_stat (char const *dir_name,
+ struct stat const *dir_stat_info)
+{
+ struct delayed_set_stat *data;
+ for (data = delayed_set_stat_head; data; data = data->next)
+ {
+ struct stat st;
+ if (stat (data->file_name, &st) != 0)
+ {
+ stat_error (data->file_name);
+ return;
+ }
+
+ if (st.st_dev == dir_stat_info->st_dev
+ && st.st_ino == dir_stat_info->st_ino)
+ {
+ data->stat_info = current_stat;
+ data->invert_permissions = (MODE_RWX
+ & (current_stat.st_mode ^ st.st_mode));
+ data->permstatus = ARCHIVED_PERMSTATUS;
+ return;
+ }
+ }
+
+ ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
+ quotearg_colon (dir_name)));
+}
+
+/* After a file/link/symlink/directory creation has failed, see if
+ it's because some required directory was not present, and if so,
+ create all required directories. Return non-zero if a directory
+ was created. */
+static int
+make_directories (char *file_name)
+{
+ char *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name);
+ char *cursor; /* points into path */
+ int did_something = 0; /* did we do anything yet? */
+ int mode;
+ int invert_permissions;
+ int status;
+
+
+ for (cursor = cursor0; *cursor; cursor++)
+ {
+ if (! ISSLASH (*cursor))
+ continue;
+
+ /* Avoid mkdir of empty string, if leading or double '/'. */
+
+ if (cursor == cursor0 || ISSLASH (cursor[-1]))
+ continue;
+
+ /* Avoid mkdir where last part of path is "." or "..". */
+
+ if (cursor[-1] == '.'
+ && (cursor == cursor0 + 1 || ISSLASH (cursor[-2])
+ || (cursor[-2] == '.'
+ && (cursor == cursor0 + 2 || ISSLASH (cursor[-3])))))
+ continue;
+
+ *cursor = '\0'; /* truncate the path there */
+ mode = MODE_RWX & ~ newdir_umask;
+ invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode;
+ status = mkdir (file_name, mode ^ invert_permissions);
+
+ if (status == 0)
+ {
+ /* Create a struct delayed_set_stat even if
+ invert_permissions is zero, because
+ repair_delayed_set_stat may need to update the struct. */
+ delay_set_stat (file_name,
+ &current_stat /* ignored */,
+ invert_permissions, INTERDIR_PERMSTATUS);
+
+ print_for_mkdir (file_name, cursor - file_name, mode);
+ did_something = 1;
+
+ *cursor = '/';
+ continue;
+ }
+
+ *cursor = '/';
+
+ if (errno == EEXIST
+#if MSDOS
+ /* Turbo C mkdir gives a funny errno. */
+ || errno == EACCES
+#endif
+ )
+ /* Directory already exists. */
+ continue;
+
+ /* Some other error in the mkdir. We return to the caller. */
+ break;
+ }
+
+ return did_something; /* tell them to retry if we made one */
+}
+
+/* Prepare to extract a file.
+ Return zero if extraction should not proceed. */
+
+static int
+prepare_to_extract (char const *file_name)
+{
+ if (to_stdout_option)
+ return 0;
+
+ if (old_files_option == UNLINK_FIRST_OLD_FILES
+ && !remove_any_file (file_name, recursive_unlink_option)
+ && errno && errno != ENOENT)
+ {
+ unlink_error (file_name);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Attempt repairing what went wrong with the extraction. Delete an
+ already existing file or create missing intermediate directories.
+ Return nonzero if we somewhat increased our chances at a successful
+ extraction. errno is properly restored on zero return. */
+static int
+maybe_recoverable (char *file_name, int *interdir_made)
+{
+ if (*interdir_made)
+ return 0;
+
+ switch (errno)
+ {
+ case EEXIST:
+ /* Remove an old file, if the options allow this. */
+
+ switch (old_files_option)
+ {
+ default:
+ return 0;
+
+ case DEFAULT_OLD_FILES:
+ case OVERWRITE_OLD_DIRS:
+ case OVERWRITE_OLD_FILES:
+ {
+ int r = remove_any_file (file_name, 0);
+ errno = EEXIST;
+ return r;
+ }
+ }
+
+ case ENOENT:
+ /* Attempt creating missing intermediate directories. */
+ if (! make_directories (file_name))
+ {
+ errno = ENOENT;
+ return 0;
+ }
+ *interdir_made = 1;
+ return 1;
+
+ default:
+ /* Just say we can't do anything about it... */
+
+ return 0;
+ }
+}
+
+static void
+extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name)
+{
+ int sparse_ind = 0;
+
+ /* assuming sizeleft is initially totalsize */
+
+ while (*sizeleft > 0)
+ {
+ size_t written;
+ size_t count;
+ union block *data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return;
+ }
+ if (lseek (fd, sparsearray[sparse_ind].offset, SEEK_SET) < 0)
+ {
+ seek_error_details (name, sparsearray[sparse_ind].offset);
+ return;
+ }
+ written = sparsearray[sparse_ind++].numbytes;
+ while (written > BLOCKSIZE)
+ {
+ count = full_write (fd, data_block->buffer, BLOCKSIZE);
+ written -= count;
+ *sizeleft -= count;
+ if (count != BLOCKSIZE)
+ {
+ write_error_details (name, count, BLOCKSIZE);
+ return;
+ }
+ set_next_block_after (data_block);
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return;
+ }
+ }
+
+ count = full_write (fd, data_block->buffer, written);
+ *sizeleft -= count;
+
+ if (count != written)
+ {
+ write_error_details (name, count, written);
+ return;
+ }
+
+ set_next_block_after (data_block);
+ }
+}
+
+/* Fix the statuses of all directories whose statuses need fixing, and
+ which are not ancestors of FILE_NAME. If AFTER_SYMLINKS is
+ nonzero, do this for all such directories; otherwise, stop at the
+ first directory that is marked to be fixed up only after delayed
+ symlinks are applied. */
+static void
+apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
+{
+ size_t file_name_len = strlen (file_name);
+ bool check_for_renamed_directories = 0;
+
+ while (delayed_set_stat_head)
+ {
+ struct delayed_set_stat *data = delayed_set_stat_head;
+ bool skip_this_one = 0;
+ struct stat st;
+ struct stat const *current_stat_info = 0;
+
+ check_for_renamed_directories |= data->after_symlinks;
+
+ if (after_symlinks < data->after_symlinks
+ || (data->file_name_len < file_name_len
+ && file_name[data->file_name_len]
+ && (ISSLASH (file_name[data->file_name_len])
+ || ISSLASH (file_name[data->file_name_len - 1]))
+ && memcmp (file_name, data->file_name, data->file_name_len) == 0))
+ break;
+
+ if (check_for_renamed_directories)
+ {
+ current_stat_info = &st;
+ if (stat (data->file_name, &st) != 0)
+ {
+ stat_error (data->file_name);
+ skip_this_one = 1;
+ }
+ else if (! (st.st_dev == data->stat_info.st_dev
+ && (st.st_ino == data->stat_info.st_ino)))
+ {
+ ERROR ((0, 0,
+ _("%s: Directory renamed before its status could be extracted"),
+ quotearg_colon (data->file_name)));
+ skip_this_one = 1;
+ }
+ }
+
+ if (! skip_this_one)
+ set_stat (data->file_name, &data->stat_info, current_stat_info,
+ data->invert_permissions, data->permstatus, DIRTYPE);
+
+ delayed_set_stat_head = data->next;
+ free (data);
+ }
+}
+
+/* Extract a file from the archive. */
+void
+extract_archive (void)
+{
+ union block *data_block;
+ int fd;
+ int status;
+ size_t count;
+ size_t name_length;
+ size_t written;
+ int openflag;
+ mode_t mode;
+ off_t size;
+ size_t skipcrud;
+ int counter;
+ int interdir_made = 0;
+ char typeflag;
+ union block *exhdr;
+
+#define CURRENT_FILE_NAME (skipcrud + current_file_name)
+
+ set_next_block_after (current_header);
+ decode_header (current_header, &current_stat, &current_format, 1);
+
+ if (interactive_option && !confirm ("extract", current_file_name))
+ {
+ skip_member ();
+ return;
+ }
+
+ /* Print the block from current_header and current_stat. */
+
+ if (verbose_option)
+ print_header ();
+
+ /* Check for fully specified file names and other atrocities. */
+
+ skipcrud = 0;
+ if (! absolute_names_option)
+ {
+ if (contains_dot_dot (CURRENT_FILE_NAME))
+ {
+ ERROR ((0, 0, _("%s: Member name contains `..'"),
+ quotearg_colon (CURRENT_FILE_NAME)));
+ skip_member ();
+ return;
+ }
+
+ skipcrud = FILESYSTEM_PREFIX_LEN (current_file_name);
+ while (ISSLASH (CURRENT_FILE_NAME[0]))
+ skipcrud++;
+
+ if (skipcrud)
+ {
+ static int warned_once;
+
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Removing leading `%.*s' from member names"),
+ (int) skipcrud, current_file_name));
+ }
+ }
+ }
+
+ apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME, 0);
+
+ /* Take a safety backup of a previously existing file. */
+
+ if (backup_option && !to_stdout_option)
+ if (!maybe_backup_file (CURRENT_FILE_NAME, 0))
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Was unable to backup this file"),
+ quotearg_colon (CURRENT_FILE_NAME)));
+ skip_member ();
+ return;
+ }
+
+ /* Extract the archive entry according to its type. */
+
+ typeflag = current_header->header.typeflag;
+ switch (typeflag)
+ {
+ /* JK - What we want to do if the file is sparse is loop through
+ the array of sparse structures in the header and read in and
+ translate the character strings representing 1) the offset at
+ which to write and 2) how many bytes to write into numbers,
+ which we store into the scratch array, "sparsearray". This
+ array makes our life easier the same way it did in creating the
+ tar file that had to deal with a sparse file.
+
+ After we read in the first five (at most) sparse structures, we
+ check to see if the file has an extended header, i.e., if more
+ sparse structures are needed to describe the contents of the new
+ file. If so, we read in the extended headers and continue to
+ store their contents into the sparsearray. */
+
+ case GNUTYPE_SPARSE:
+ sp_array_size = 10;
+ sparsearray =
+ xmalloc (sp_array_size * sizeof (struct sp_array));
+
+ for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
+ {
+ struct sparse const *s = &current_header->oldgnu_header.sp[counter];
+ sparsearray[counter].offset = OFF_FROM_HEADER (s->offset);
+ sparsearray[counter].numbytes = SIZE_FROM_HEADER (s->numbytes);
+ if (!sparsearray[counter].numbytes)
+ break;
+ }
+
+ if (current_header->oldgnu_header.isextended)
+ {
+ /* Read in the list of extended headers and translate them
+ into the sparsearray as before. Note that this
+ invalidates current_header. */
+
+ /* static */ int ind = SPARSES_IN_OLDGNU_HEADER;
+
+ while (1)
+ {
+ exhdr = find_next_block ();
+ if (! exhdr)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return;
+ }
+ for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
+ {
+ struct sparse const *s = &exhdr->sparse_header.sp[counter];
+ if (counter + ind > sp_array_size - 1)
+ {
+ /* Realloc the scratch area since we've run out of
+ room. */
+
+ sp_array_size *= 2;
+ sparsearray =
+ xrealloc (sparsearray,
+ sp_array_size * sizeof (struct sp_array));
+ }
+ if (s->numbytes[0] == 0)
+ break;
+ sparsearray[counter + ind].offset =
+ OFF_FROM_HEADER (s->offset);
+ sparsearray[counter + ind].numbytes =
+ SIZE_FROM_HEADER (s->numbytes);
+ }
+ if (!exhdr->sparse_header.isextended)
+ break;
+ else
+ {
+ ind += SPARSES_IN_SPARSE_HEADER;
+ set_next_block_after (exhdr);
+ }
+ }
+ set_next_block_after (exhdr);
+ }
+ /* Fall through. */
+
+ case AREGTYPE:
+ case REGTYPE:
+ case CONTTYPE:
+
+ /* Appears to be a file. But BSD tar uses the convention that a slash
+ suffix means a directory. */
+
+ name_length = strlen (CURRENT_FILE_NAME);
+ if (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length
+ && CURRENT_FILE_NAME[name_length - 1] == '/')
+ goto really_dir;
+
+ /* FIXME: deal with protection issues. */
+
+ again_file:
+ openflag = (O_WRONLY | O_BINARY | O_CREAT
+ | (old_files_option == OVERWRITE_OLD_FILES
+ ? O_TRUNC
+ : O_EXCL));
+ mode = current_stat.st_mode & MODE_RWX & ~ current_umask;
+
+ if (to_stdout_option)
+ {
+ fd = STDOUT_FILENO;
+ goto extract_file;
+ }
+
+ if (! prepare_to_extract (CURRENT_FILE_NAME))
+ {
+ skip_member ();
+ if (backup_option)
+ undo_last_backup ();
+ break;
+ }
+
+#if O_CTG
+ /* Contiguous files (on the Masscomp) have to specify the size in
+ the open call that creates them. */
+
+ if (typeflag == CONTTYPE)
+ fd = open (CURRENT_FILE_NAME, openflag | O_CTG,
+ mode, current_stat.st_size);
+ else
+ fd = open (CURRENT_FILE_NAME, openflag, mode);
+
+#else /* not O_CTG */
+ if (typeflag == CONTTYPE)
+ {
+ static int conttype_diagnosed;
+
+ if (!conttype_diagnosed)
+ {
+ conttype_diagnosed = 1;
+ WARN ((0, 0, _("Extracting contiguous files as regular files")));
+ }
+ }
+ fd = open (CURRENT_FILE_NAME, openflag, mode);
+
+#endif /* not O_CTG */
+
+ if (fd < 0)
+ {
+ if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ goto again_file;
+
+ open_error (CURRENT_FILE_NAME);
+ skip_member ();
+ if (backup_option)
+ undo_last_backup ();
+ break;
+ }
+
+ extract_file:
+ if (typeflag == GNUTYPE_SPARSE)
+ {
+ char *name;
+ size_t name_length_bis;
+
+ /* Kludge alert. NAME is assigned to header.name because
+ during the extraction, the space that contains the header
+ will get scribbled on, and the name will get munged, so any
+ error messages that happen to contain the filename will look
+ REAL interesting unless we do this. */
+
+ name_length_bis = strlen (CURRENT_FILE_NAME) + 1;
+ name = xmalloc (name_length_bis);
+ memcpy (name, CURRENT_FILE_NAME, name_length_bis);
+ size = current_stat.st_size;
+ extract_sparse_file (fd, &size, current_stat.st_size, name);
+ free (sparsearray);
+ }
+ else
+ for (size = current_stat.st_size; size > 0; )
+ {
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, current_file_name);
+ save_totsize = current_stat.st_size;
+ save_sizeleft = size;
+ }
+
+ /* Locate data, determine max length writeable, write it,
+ block that we have used the data, then check if the write
+ worked. */
+
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break; /* FIXME: What happens, then? */
+ }
+
+ written = available_space_after (data_block);
+
+ if (written > size)
+ written = size;
+ errno = 0;
+ count = full_write (fd, data_block->buffer, written);
+ size -= count;
+
+ set_next_block_after ((union block *)
+ (data_block->buffer + written - 1));
+ if (count != written)
+ {
+ write_error_details (CURRENT_FILE_NAME, count, written);
+ break;
+ }
+ }
+
+ skip_file (size);
+
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+
+ /* If writing to stdout, don't try to do anything to the filename;
+ it doesn't exist, or we don't want to touch it anyway. */
+
+ if (to_stdout_option)
+ break;
+
+ status = close (fd);
+ if (status < 0)
+ {
+ close_error (CURRENT_FILE_NAME);
+ if (backup_option)
+ undo_last_backup ();
+ }
+
+ set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0,
+ (old_files_option == OVERWRITE_OLD_FILES
+ ? UNKNOWN_PERMSTATUS
+ : ARCHIVED_PERMSTATUS),
+ typeflag);
+ break;
+
+ case SYMTYPE:
+#ifdef HAVE_SYMLINK
+ if (! prepare_to_extract (CURRENT_FILE_NAME))
+ break;
+
+ if (absolute_names_option
+ || ! (ISSLASH (current_link_name
+ [FILESYSTEM_PREFIX_LEN (current_link_name)])
+ || contains_dot_dot (current_link_name)))
+ {
+ while (status = symlink (current_link_name, CURRENT_FILE_NAME),
+ status != 0)
+ if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ break;
+
+ if (status == 0)
+ set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0, 0, SYMTYPE);
+ else
+ symlink_error (current_link_name, CURRENT_FILE_NAME);
+ }
+ else
+ {
+ /* This symbolic link is potentially dangerous. Don't
+ create it now; instead, create a placeholder file, which
+ will be replaced after other extraction is done. */
+ struct stat st;
+
+ while (fd = open (CURRENT_FILE_NAME, O_WRONLY | O_CREAT | O_EXCL, 0),
+ fd < 0)
+ if (! maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ break;
+
+ status = -1;
+ if (fd < 0)
+ open_error (CURRENT_FILE_NAME);
+ else if (fstat (fd, &st) != 0)
+ {
+ stat_error (CURRENT_FILE_NAME);
+ close (fd);
+ }
+ else if (close (fd) != 0)
+ close_error (CURRENT_FILE_NAME);
+ else
+ {
+ struct delayed_set_stat *h;
+ struct delayed_symlink *p =
+ xmalloc (offsetof (struct delayed_symlink, target)
+ + strlen (current_link_name) + 1);
+ p->next = delayed_symlink_head;
+ delayed_symlink_head = p;
+ p->dev = st.st_dev;
+ p->ino = st.st_ino;
+ p->mtime = st.st_mtime;
+ p->uid = current_stat.st_uid;
+ p->gid = current_stat.st_gid;
+ p->sources = xmalloc (offsetof (struct string_list, string)
+ + strlen (CURRENT_FILE_NAME) + 1);
+ p->sources->next = 0;
+ strcpy (p->sources->string, CURRENT_FILE_NAME);
+ strcpy (p->target, current_link_name);
+
+ h = delayed_set_stat_head;
+ if (h && ! h->after_symlinks
+ && strncmp (CURRENT_FILE_NAME, h->file_name, h->file_name_len) == 0
+ && ISSLASH (CURRENT_FILE_NAME[h->file_name_len])
+ && (base_name (CURRENT_FILE_NAME)
+ == CURRENT_FILE_NAME + h->file_name_len + 1))
+ {
+ do
+ {
+ h->after_symlinks = 1;
+
+ if (stat (h->file_name, &st) != 0)
+ stat_error (h->file_name);
+ else
+ {
+ h->stat_info.st_dev = st.st_dev;
+ h->stat_info.st_ino = st.st_ino;
+ }
+ }
+ while ((h = h->next) && ! h->after_symlinks);
+ }
+
+ status = 0;
+ }
+ }
+
+ if (status != 0 && backup_option)
+ undo_last_backup ();
+ break;
+
+#else
+ {
+ static int warned_once;
+
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0,
+ _("Attempting extraction of symbolic links as hard links")));
+ }
+ }
+ typeflag = LNKTYPE;
+ /* Fall through. */
+#endif
+
+ case LNKTYPE:
+ if (! prepare_to_extract (CURRENT_FILE_NAME))
+ break;
+
+ again_link:
+ {
+ struct stat st1, st2;
+ int e;
+
+ /* MSDOS does not implement links. However, djgpp's link() actually
+ copies the file. */
+ status = link (current_link_name, CURRENT_FILE_NAME);
+
+ if (status == 0)
+ {
+ struct delayed_symlink *ds = delayed_symlink_head;
+ if (ds && stat (current_link_name, &st1) == 0)
+ for (; ds; ds = ds->next)
+ if (ds->dev == st1.st_dev
+ && ds->ino == st1.st_ino
+ && ds->mtime == st1.st_mtime)
+ {
+ struct string_list *p =
+ xmalloc (offsetof (struct string_list, string)
+ + strlen (CURRENT_FILE_NAME) + 1);
+ strcpy (p->string, CURRENT_FILE_NAME);
+ p->next = ds->sources;
+ ds->sources = p;
+ break;
+ }
+ break;
+ }
+ if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ goto again_link;
+
+ if (incremental_option && errno == EEXIST)
+ break;
+ e = errno;
+ if (stat (current_link_name, &st1) == 0
+ && stat (CURRENT_FILE_NAME, &st2) == 0
+ && st1.st_dev == st2.st_dev
+ && st1.st_ino == st2.st_ino)
+ break;
+
+ link_error (current_link_name, CURRENT_FILE_NAME);
+ if (backup_option)
+ undo_last_backup ();
+ }
+ break;
+
+#if S_IFCHR
+ case CHRTYPE:
+ current_stat.st_mode |= S_IFCHR;
+ goto make_node;
+#endif
+
+#if S_IFBLK
+ case BLKTYPE:
+ current_stat.st_mode |= S_IFBLK;
+#endif
+
+#if S_IFCHR || S_IFBLK
+ make_node:
+ if (! prepare_to_extract (CURRENT_FILE_NAME))
+ break;
+
+ status = mknod (CURRENT_FILE_NAME, current_stat.st_mode,
+ current_stat.st_rdev);
+ if (status != 0)
+ {
+ if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ goto make_node;
+ mknod_error (CURRENT_FILE_NAME);
+ if (backup_option)
+ undo_last_backup ();
+ break;
+ };
+ set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0,
+ ARCHIVED_PERMSTATUS, typeflag);
+ break;
+#endif
+
+#if HAVE_MKFIFO || defined mkfifo
+ case FIFOTYPE:
+ if (! prepare_to_extract (CURRENT_FILE_NAME))
+ break;
+
+ while (status = mkfifo (CURRENT_FILE_NAME, current_stat.st_mode),
+ status != 0)
+ if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ break;
+
+ if (status == 0)
+ set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0,
+ ARCHIVED_PERMSTATUS, typeflag);
+ else
+ {
+ mkfifo_error (CURRENT_FILE_NAME);
+ if (backup_option)
+ undo_last_backup ();
+ }
+ break;
+#endif
+
+ case DIRTYPE:
+ case GNUTYPE_DUMPDIR:
+ name_length = strlen (CURRENT_FILE_NAME);
+
+ really_dir:
+ /* Remove any redundant trailing "/"s. */
+ while (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length
+ && CURRENT_FILE_NAME[name_length - 1] == '/')
+ name_length--;
+ CURRENT_FILE_NAME[name_length] = '\0';
+
+ if (incremental_option)
+ {
+ /* Read the entry and delete files that aren't listed in the
+ archive. */
+
+ gnu_restore (skipcrud);
+ }
+ else if (typeflag == GNUTYPE_DUMPDIR)
+ skip_member ();
+
+ if (! prepare_to_extract (CURRENT_FILE_NAME))
+ break;
+
+ mode = ((current_stat.st_mode
+ | (we_are_root ? 0 : MODE_WXUSR))
+ & MODE_RWX);
+
+ again_dir:
+ status = mkdir (CURRENT_FILE_NAME, mode);
+
+ if (status != 0)
+ {
+ if (errno == EEXIST
+ && (interdir_made
+ || old_files_option == OVERWRITE_OLD_DIRS
+ || old_files_option == OVERWRITE_OLD_FILES))
+ {
+ struct stat st;
+ if (stat (CURRENT_FILE_NAME, &st) == 0)
+ {
+ if (interdir_made)
+ {
+ repair_delayed_set_stat (CURRENT_FILE_NAME, &st);
+ break;
+ }
+ if (S_ISDIR (st.st_mode))
+ {
+ mode = st.st_mode & ~ current_umask;
+ goto directory_exists;
+ }
+ }
+ errno = EEXIST;
+ }
+
+ if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+ goto again_dir;
+
+ if (errno != EEXIST)
+ {
+ mkdir_error (CURRENT_FILE_NAME);
+ if (backup_option)
+ undo_last_backup ();
+ break;
+ }
+ }
+
+ directory_exists:
+ if (status == 0
+ || old_files_option == OVERWRITE_OLD_DIRS
+ || old_files_option == OVERWRITE_OLD_FILES)
+ delay_set_stat (CURRENT_FILE_NAME, &current_stat,
+ MODE_RWX & (mode ^ current_stat.st_mode),
+ (status == 0
+ ? ARCHIVED_PERMSTATUS
+ : UNKNOWN_PERMSTATUS));
+ break;
+
+ case GNUTYPE_VOLHDR:
+ if (verbose_option)
+ fprintf (stdlis, _("Reading %s\n"), quote (current_file_name));
+ break;
+
+ case GNUTYPE_NAMES:
+ extract_mangle ();
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ ERROR ((0, 0,
+ _("%s: Cannot extract -- file is continued from another volume"),
+ quotearg_colon (current_file_name)));
+ skip_member ();
+ if (backup_option)
+ undo_last_backup ();
+ break;
+
+ case GNUTYPE_LONGNAME:
+ case GNUTYPE_LONGLINK:
+ ERROR ((0, 0, _("Visible long name error")));
+ skip_member ();
+ if (backup_option)
+ undo_last_backup ();
+ break;
+
+ default:
+ WARN ((0, 0,
+ _("%s: Unknown file type '%c', extracted as normal file"),
+ quotearg_colon (CURRENT_FILE_NAME), typeflag));
+ goto again_file;
+ }
+
+#undef CURRENT_FILE_NAME
+}
+
+/* Extract the symbolic links whose final extraction were delayed. */
+static void
+apply_delayed_symlinks (void)
+{
+ struct delayed_symlink *ds;
+
+ for (ds = delayed_symlink_head; ds; )
+ {
+ struct string_list *sources = ds->sources;
+ char const *valid_source = 0;
+
+ for (sources = ds->sources; sources; sources = sources->next)
+ {
+ char const *source = sources->string;
+ struct stat st;
+
+ /* Make sure the placeholder file is still there. If not,
+ don't create a symlink, as the placeholder was probably
+ removed by a later extraction. */
+ if (lstat (source, &st) == 0
+ && st.st_dev == ds->dev
+ && st.st_ino == ds->ino
+ && st.st_mtime == ds->mtime)
+ {
+ /* Unlink the placeholder, then create a hard link if possible,
+ a symbolic link otherwise. */
+ if (unlink (source) != 0)
+ unlink_error (source);
+ else if (valid_source && link (valid_source, source) == 0)
+ ;
+ else if (symlink (ds->target, source) != 0)
+ symlink_error (ds->target, source);
+ else
+ {
+ valid_source = source;
+ st.st_uid = ds->uid;
+ st.st_gid = ds->gid;
+ set_stat (source, &st, 0, 0, 0, SYMTYPE);
+ }
+ }
+ }
+
+ for (sources = ds->sources; sources; )
+ {
+ struct string_list *next = sources->next;
+ free (sources);
+ sources = next;
+ }
+
+ {
+ struct delayed_symlink *next = ds->next;
+ free (ds);
+ ds = next;
+ }
+ }
+
+ delayed_symlink_head = 0;
+}
+
+/* Finish the extraction of an archive. */
+void
+extract_finish (void)
+{
+ /* First, fix the status of ordinary directories that need fixing. */
+ apply_nonancestor_delayed_set_stat ("", 0);
+
+ /* Then, apply delayed symlinks, so that they don't affect delayed
+ directory status-setting for ordinary directories. */
+ apply_delayed_symlinks ();
+
+ /* Finally, fix the status of directories that are ancestors
+ of delayed symlinks. */
+ apply_nonancestor_delayed_set_stat ("", 1);
+}
+
+void
+fatal_exit (void)
+{
+ extract_finish ();
+ error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
+ abort ();
+}
diff --git a/contrib/tar/src/incremen.c b/contrib/tar/src/incremen.c
new file mode 100644
index 0000000..7dd43e9
--- /dev/null
+++ b/contrib/tar/src/incremen.c
@@ -0,0 +1,584 @@
+/* GNU dump extensions to tar.
+
+ Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+#include <getline.h>
+#include <hash.h>
+#include <quotearg.h>
+#include "common.h"
+
+/* Variable sized generic character buffers. */
+
+struct accumulator
+{
+ size_t allocated;
+ size_t length;
+ char *pointer;
+};
+
+/* Amount of space guaranteed just after a reallocation. */
+#define ACCUMULATOR_SLACK 50
+
+/* Return the accumulated data from an ACCUMULATOR buffer. */
+static char *
+get_accumulator (struct accumulator *accumulator)
+{
+ return accumulator->pointer;
+}
+
+/* Allocate and return a new accumulator buffer. */
+static struct accumulator *
+new_accumulator (void)
+{
+ struct accumulator *accumulator
+ = xmalloc (sizeof (struct accumulator));
+
+ accumulator->allocated = ACCUMULATOR_SLACK;
+ accumulator->pointer = xmalloc (ACCUMULATOR_SLACK);
+ accumulator->length = 0;
+ return accumulator;
+}
+
+/* Deallocate an ACCUMULATOR buffer. */
+static void
+delete_accumulator (struct accumulator *accumulator)
+{
+ free (accumulator->pointer);
+ free (accumulator);
+}
+
+/* At the end of an ACCUMULATOR buffer, add a DATA block of SIZE bytes. */
+static void
+add_to_accumulator (struct accumulator *accumulator,
+ const char *data, size_t size)
+{
+ if (accumulator->length + size > accumulator->allocated)
+ {
+ accumulator->allocated = accumulator->length + size + ACCUMULATOR_SLACK;
+ accumulator->pointer =
+ xrealloc (accumulator->pointer, accumulator->allocated);
+ }
+ memcpy (accumulator->pointer + accumulator->length, data, size);
+ accumulator->length += size;
+}
+
+/* Incremental dump specialities. */
+
+/* Which child files to save under a directory. */
+enum children {NO_CHILDREN, CHANGED_CHILDREN, ALL_CHILDREN};
+
+/* Directory attributes. */
+struct directory
+ {
+ dev_t device_number; /* device number for directory */
+ ino_t inode_number; /* inode number for directory */
+ enum children children;
+ char nfs;
+ char found;
+ char name[1]; /* path name of directory */
+ };
+
+static Hash_table *directory_table;
+
+#if HAVE_ST_FSTYPE_STRING
+ static char const nfs_string[] = "nfs";
+# define NFS_FILE_STAT(st) (strcmp ((st).st_fstype, nfs_string) == 0)
+#else
+# define ST_DEV_MSB(st) (~ (dev_t) 0 << (sizeof (st).st_dev * CHAR_BIT - 1))
+# define NFS_FILE_STAT(st) (((st).st_dev & ST_DEV_MSB (st)) != 0)
+#endif
+
+/* Calculate the hash of a directory. */
+static unsigned
+hash_directory (void const *entry, unsigned n_buckets)
+{
+ struct directory const *directory = entry;
+ return hash_string (directory->name, n_buckets);
+}
+
+/* Compare two directories for equality. */
+static bool
+compare_directories (void const *entry1, void const *entry2)
+{
+ struct directory const *directory1 = entry1;
+ struct directory const *directory2 = entry2;
+ return strcmp (directory1->name, directory2->name) == 0;
+}
+
+/* Create and link a new directory entry for directory NAME, having a
+ device number DEV and an inode number INO, with NFS indicating
+ whether it is an NFS device and FOUND indicating whether we have
+ found that the directory exists. */
+static struct directory *
+note_directory (char const *name, dev_t dev, ino_t ino, bool nfs, bool found)
+{
+ size_t size = offsetof (struct directory, name) + strlen (name) + 1;
+ struct directory *directory = xmalloc (size);
+
+ directory->device_number = dev;
+ directory->inode_number = ino;
+ directory->children = CHANGED_CHILDREN;
+ directory->nfs = nfs;
+ directory->found = found;
+ strcpy (directory->name, name);
+
+ if (! ((directory_table
+ || (directory_table = hash_initialize (0, 0, hash_directory,
+ compare_directories, 0)))
+ && hash_insert (directory_table, directory)))
+ xalloc_die ();
+
+ return directory;
+}
+
+/* Return a directory entry for a given path NAME, or zero if none found. */
+static struct directory *
+find_directory (char *name)
+{
+ if (! directory_table)
+ return 0;
+ else
+ {
+ size_t size = offsetof (struct directory, name) + strlen (name) + 1;
+ struct directory *dir = alloca (size);
+ strcpy (dir->name, name);
+ return hash_lookup (directory_table, dir);
+ }
+}
+
+static int
+compare_dirents (const void *first, const void *second)
+{
+ return strcmp ((*(char *const *) first) + 1,
+ (*(char *const *) second) + 1);
+}
+
+char *
+get_directory_contents (char *path, dev_t device)
+{
+ struct accumulator *accumulator;
+
+ /* Recursively scan the given PATH. */
+
+ {
+ char *dirp = savedir (path); /* for scanning directory */
+ char const *entry; /* directory entry being scanned */
+ size_t entrylen; /* length of directory entry */
+ char *name_buffer; /* directory, `/', and directory member */
+ size_t name_buffer_size; /* allocated size of name_buffer, minus 2 */
+ size_t name_length; /* used length in name_buffer */
+ struct directory *directory; /* for checking if already already seen */
+ enum children children;
+
+ if (! dirp)
+ savedir_error (path);
+ errno = 0;
+
+ name_buffer_size = strlen (path) + NAME_FIELD_SIZE;
+ name_buffer = xmalloc (name_buffer_size + 2);
+ strcpy (name_buffer, path);
+ if (! ISSLASH (path[strlen (path) - 1]))
+ strcat (name_buffer, "/");
+ name_length = strlen (name_buffer);
+
+ directory = find_directory (path);
+ children = directory ? directory->children : CHANGED_CHILDREN;
+
+ accumulator = new_accumulator ();
+
+ if (children != NO_CHILDREN)
+ for (entry = dirp;
+ (entrylen = strlen (entry)) != 0;
+ entry += entrylen + 1)
+ {
+ if (name_buffer_size <= entrylen + name_length)
+ {
+ do
+ name_buffer_size += NAME_FIELD_SIZE;
+ while (name_buffer_size <= entrylen + name_length);
+ name_buffer = xrealloc (name_buffer, name_buffer_size + 2);
+ }
+ strcpy (name_buffer + name_length, entry);
+
+ if (excluded_name (name_buffer))
+ add_to_accumulator (accumulator, "N", 1);
+ else
+ {
+ struct stat stat_data;
+
+ if (deref_stat (dereference_option, name_buffer, &stat_data))
+ {
+ if (ignore_failed_read_option)
+ stat_warn (name_buffer);
+ else
+ stat_error (name_buffer);
+ continue;
+ }
+
+ if (S_ISDIR (stat_data.st_mode))
+ {
+ bool nfs = NFS_FILE_STAT (stat_data);
+
+ if (directory = find_directory (name_buffer), directory)
+ {
+ /* With NFS, the same file can have two different devices
+ if an NFS directory is mounted in multiple locations,
+ which is relatively common when automounting.
+ To avoid spurious incremental redumping of
+ directories, consider all NFS devices as equal,
+ relying on the i-node to establish differences. */
+
+ if (! (((directory->nfs & nfs)
+ || directory->device_number == stat_data.st_dev)
+ && directory->inode_number == stat_data.st_ino))
+ {
+ if (verbose_option)
+ WARN ((0, 0, _("%s: Directory has been renamed"),
+ quotearg_colon (name_buffer)));
+ directory->children = ALL_CHILDREN;
+ directory->nfs = nfs;
+ directory->device_number = stat_data.st_dev;
+ directory->inode_number = stat_data.st_ino;
+ }
+ directory->found = 1;
+ }
+ else
+ {
+ if (verbose_option)
+ WARN ((0, 0, _("%s: Directory is new"),
+ quotearg_colon (name_buffer)));
+ directory = note_directory (name_buffer,
+ stat_data.st_dev,
+ stat_data.st_ino, nfs, 1);
+ directory->children =
+ ((listed_incremental_option
+ || newer_mtime_option <= stat_data.st_mtime
+ || (after_date_option &&
+ newer_ctime_option <= stat_data.st_ctime))
+ ? ALL_CHILDREN
+ : CHANGED_CHILDREN);
+ }
+
+ if (one_file_system_option && device != stat_data.st_dev)
+ directory->children = NO_CHILDREN;
+ else if (children == ALL_CHILDREN)
+ directory->children = ALL_CHILDREN;
+
+ add_to_accumulator (accumulator, "D", 1);
+ }
+
+ else if (one_file_system_option && device != stat_data.st_dev)
+ add_to_accumulator (accumulator, "N", 1);
+
+#ifdef S_ISHIDDEN
+ else if (S_ISHIDDEN (stat_data.st_mode))
+ {
+ add_to_accumulator (accumulator, "D", 1);
+ add_to_accumulator (accumulator, entry, entrylen);
+ add_to_accumulator (accumulator, "A", 2);
+ continue;
+ }
+#endif
+
+ else
+ if (children == CHANGED_CHILDREN
+ && stat_data.st_mtime < newer_mtime_option
+ && (!after_date_option
+ || stat_data.st_ctime < newer_ctime_option))
+ add_to_accumulator (accumulator, "N", 1);
+ else
+ add_to_accumulator (accumulator, "Y", 1);
+ }
+
+ add_to_accumulator (accumulator, entry, entrylen + 1);
+ }
+
+ add_to_accumulator (accumulator, "\000\000", 2);
+
+ free (name_buffer);
+ free (dirp);
+ }
+
+ /* Sort the contents of the directory, now that we have it all. */
+
+ {
+ char *pointer = get_accumulator (accumulator);
+ size_t counter;
+ char *cursor;
+ char *buffer;
+ char **array;
+ char **array_cursor;
+
+ counter = 0;
+ for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
+ counter++;
+
+ if (! counter)
+ {
+ delete_accumulator (accumulator);
+ return 0;
+ }
+
+ array = xmalloc (sizeof (char *) * (counter + 1));
+
+ array_cursor = array;
+ for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
+ *array_cursor++ = cursor;
+ *array_cursor = 0;
+
+ qsort (array, counter, sizeof (char *), compare_dirents);
+
+ buffer = xmalloc (cursor - pointer + 2);
+
+ cursor = buffer;
+ for (array_cursor = array; *array_cursor; array_cursor++)
+ {
+ char *string = *array_cursor;
+
+ while ((*cursor++ = *string++))
+ continue;
+ }
+ *cursor = '\0';
+
+ delete_accumulator (accumulator);
+ free (array);
+ return buffer;
+ }
+}
+
+static FILE *listed_incremental_stream;
+
+void
+read_directory_file (void)
+{
+ int fd;
+ FILE *fp;
+ char *buf = 0;
+ size_t bufsize;
+
+ /* Open the file for both read and write. That way, we can write
+ it later without having to reopen it, and don't have to worry if
+ we chdir in the meantime. */
+ fd = open (listed_incremental_option, O_RDWR | O_CREAT, MODE_RW);
+ if (fd < 0)
+ {
+ open_error (listed_incremental_option);
+ return;
+ }
+
+ fp = fdopen (fd, "r+");
+ if (! fp)
+ {
+ open_error (listed_incremental_option);
+ close (fd);
+ return;
+ }
+
+ listed_incremental_stream = fp;
+
+ if (0 < getline (&buf, &bufsize, fp))
+ {
+ char *ebuf;
+ int n;
+ long lineno = 1;
+ unsigned long u = (errno = 0, strtoul (buf, &ebuf, 10));
+ time_t t = u;
+ if (buf == ebuf || (u == 0 && errno == EINVAL))
+ ERROR ((0, 0, "%s:1: %s", quotearg_colon (listed_incremental_option),
+ _("Invalid time stamp")));
+ else if (t != u || (u == -1 && errno == ERANGE))
+ ERROR ((0, 0, "%s:1: %s", quotearg_colon (listed_incremental_option),
+ _("Time stamp out of range")));
+ else
+ newer_mtime_option = t;
+
+ while (0 < (n = getline (&buf, &bufsize, fp)))
+ {
+ dev_t dev;
+ ino_t ino;
+ int nfs = buf[0] == '+';
+ char *strp = buf + nfs;
+
+ lineno++;
+
+ if (buf[n - 1] == '\n')
+ buf[n - 1] = '\0';
+
+ errno = 0;
+ dev = u = strtoul (strp, &ebuf, 10);
+ if (strp == ebuf || (u == 0 && errno == EINVAL))
+ ERROR ((0, 0, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid device number")));
+ else if (dev != u || (u == -1 && errno == ERANGE))
+ ERROR ((0, 0, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Device number out of range")));
+ strp = ebuf;
+
+ errno = 0;
+ ino = u = strtoul (strp, &ebuf, 10);
+ if (strp == ebuf || (u == 0 && errno == EINVAL))
+ ERROR ((0, 0, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid inode number")));
+ else if (ino != u || (u == -1 && errno == ERANGE))
+ ERROR ((0, 0, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Inode number out of range")));
+ strp = ebuf;
+
+ strp++;
+ unquote_string (strp);
+ note_directory (strp, dev, ino, nfs, 0);
+ }
+ }
+
+ if (ferror (fp))
+ read_error (listed_incremental_option);
+ if (buf)
+ free (buf);
+}
+
+/* Output incremental data for the directory ENTRY to the file DATA.
+ Return nonzero if successful, preserving errno on write failure. */
+static bool
+write_directory_file_entry (void *entry, void *data)
+{
+ struct directory const *directory = entry;
+ FILE *fp = data;
+
+ if (directory->found)
+ {
+ int e;
+ char *str = quote_copy_string (directory->name);
+ fprintf (fp, "+%lu %lu %s\n" + ! directory->nfs,
+ (unsigned long) directory->device_number,
+ (unsigned long) directory->inode_number,
+ str ? str : directory->name);
+ e = errno;
+ if (str)
+ free (str);
+ errno = e;
+ }
+
+ return ! ferror (fp);
+}
+
+void
+write_directory_file (void)
+{
+ FILE *fp = listed_incremental_stream;
+
+ if (! fp)
+ return;
+
+ if (fseek (fp, 0L, SEEK_SET) != 0)
+ seek_error (listed_incremental_option);
+ if (ftruncate (fileno (fp), (off_t) 0) != 0)
+ truncate_error (listed_incremental_option);
+
+ fprintf (fp, "%lu\n", (unsigned long) start_time);
+ if (! ferror (fp) && directory_table)
+ hash_do_for_each (directory_table, write_directory_file_entry, fp);
+ if (ferror (fp))
+ write_error (listed_incremental_option);
+ if (fclose (fp) != 0)
+ close_error (listed_incremental_option);
+}
+
+/* Restoration of incremental dumps. */
+
+void
+gnu_restore (size_t skipcrud)
+{
+ char *archive_dir;
+ char *current_dir;
+ char *cur, *arc;
+ size_t size;
+ size_t copied;
+ union block *data_block;
+ char *to;
+
+#define CURRENT_FILE_NAME (skipcrud + current_file_name)
+
+ current_dir = savedir (CURRENT_FILE_NAME);
+
+ if (!current_dir)
+ {
+ /* The directory doesn't exist now. It'll be created. In any
+ case, we don't have to delete any files out of it. */
+
+ skip_member ();
+ return;
+ }
+
+ size = current_stat.st_size;
+ if (size != current_stat.st_size)
+ xalloc_die ();
+ archive_dir = xmalloc (size);
+ to = archive_dir;
+ for (; size > 0; size -= copied)
+ {
+ data_block = find_next_block ();
+ if (!data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break; /* FIXME: What happens then? */
+ }
+ copied = available_space_after (data_block);
+ if (copied > size)
+ copied = size;
+ memcpy (to, data_block->buffer, copied);
+ to += copied;
+ set_next_block_after ((union block *)
+ (data_block->buffer + copied - 1));
+ }
+
+ for (cur = current_dir; *cur; cur += strlen (cur) + 1)
+ {
+ for (arc = archive_dir; *arc; arc += strlen (arc) + 1)
+ {
+ arc++;
+ if (!strcmp (arc, cur))
+ break;
+ }
+ if (*arc == '\0')
+ {
+ char *p = new_name (CURRENT_FILE_NAME, cur);
+ if (! interactive_option || confirm ("delete", p))
+ {
+ if (verbose_option)
+ fprintf (stdlis, _("%s: Deleting %s\n"),
+ program_name, quote (p));
+ if (! remove_any_file (p, 1))
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p)));
+ }
+ }
+ free (p);
+ }
+
+ }
+ free (current_dir);
+ free (archive_dir);
+
+#undef CURRENT_FILE_NAME
+}
diff --git a/contrib/tar/src/list.c b/contrib/tar/src/list.c
new file mode 100644
index 0000000..e88d53b
--- /dev/null
+++ b/contrib/tar/src/list.c
@@ -0,0 +1,1191 @@
+/* List a tar archive, with support routines for reading a tar archive.
+
+ Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
+ 2001 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 1985-08-26.
+
+ This program is free software; you can redistribute it and/or modify it
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* Define to non-zero for forcing old ctime format instead of ISO format. */
+#undef USE_OLD_CTIME
+
+#include "system.h"
+#include <quotearg.h>
+
+#include "common.h"
+
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+union block *current_header; /* points to current archive header */
+struct stat current_stat; /* stat struct corresponding */
+enum archive_format current_format; /* recognized format */
+union block *recent_long_name; /* recent long name header and contents */
+union block *recent_long_link; /* likewise, for long link */
+size_t recent_long_name_blocks; /* number of blocks in recent_long_name */
+size_t recent_long_link_blocks; /* likewise, for long link */
+
+static uintmax_t from_header PARAMS ((const char *, size_t, const char *,
+ uintmax_t, uintmax_t));
+
+/* Base 64 digits; see Internet RFC 2045 Table 1. */
+static char const base_64_digits[64] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/* Table of base-64 digit values indexed by unsigned chars.
+ The value is 64 for unsigned chars that are not base-64 digits. */
+static char base64_map[UCHAR_MAX + 1];
+
+static void
+base64_init (void)
+{
+ int i;
+ memset (base64_map, 64, sizeof base64_map);
+ for (i = 0; i < 64; i++)
+ base64_map[(int) base_64_digits[i]] = i;
+}
+
+/* Main loop for reading an archive. */
+void
+read_and (void (*do_something) ())
+{
+ enum read_header status = HEADER_STILL_UNREAD;
+ enum read_header prev_status;
+
+ base64_init ();
+ name_gather ();
+ open_archive (ACCESS_READ);
+
+ while (1)
+ {
+ prev_status = status;
+ status = read_header (0);
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ abort ();
+
+ case HEADER_SUCCESS:
+
+ /* Valid header. We should decode next field (mode) first.
+ Ensure incoming names are null terminated. */
+
+ if (! name_match (current_file_name)
+ || (newer_mtime_option != TYPE_MINIMUM (time_t)
+ /* FIXME: We get mtime now, and again later; this causes
+ duplicate diagnostics if header.mtime is bogus. */
+ && ((current_stat.st_mtime
+ = TIME_FROM_HEADER (current_header->header.mtime))
+ < newer_mtime_option))
+ || excluded_name (current_file_name))
+ {
+ switch (current_header->header.typeflag)
+ {
+ case GNUTYPE_VOLHDR:
+ case GNUTYPE_MULTIVOL:
+ case GNUTYPE_NAMES:
+ break;
+
+ case DIRTYPE:
+ if (show_omitted_dirs_option)
+ WARN ((0, 0, _("%s: Omitting"),
+ quotearg_colon (current_file_name)));
+ /* Fall through. */
+ default:
+ skip_member ();
+ continue;
+ }
+ }
+
+ (*do_something) ();
+ continue;
+
+ case HEADER_ZERO_BLOCK:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: ** Block of NULs **\n"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+
+ set_next_block_after (current_header);
+ status = prev_status;
+ if (ignore_zeros_option)
+ continue;
+ break;
+
+ case HEADER_END_OF_FILE:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: ** End of File **\n"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+ break;
+
+ case HEADER_FAILURE:
+ /* If the previous header was good, tell them that we are
+ skipping bad ones. */
+ set_next_block_after (current_header);
+ switch (prev_status)
+ {
+ case HEADER_STILL_UNREAD:
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_ZERO_BLOCK:
+ case HEADER_SUCCESS:
+ ERROR ((0, 0, _("Skipping to next header")));
+ break;
+
+ case HEADER_END_OF_FILE:
+ case HEADER_FAILURE:
+ /* We are in the middle of a cascade of errors. */
+ break;
+ }
+ continue;
+ }
+ break;
+ }
+
+ close_archive ();
+ names_notfound (); /* print names not found */
+}
+
+/* Print a header block, based on tar options. */
+void
+list_archive (void)
+{
+ /* Print the header block. */
+
+ if (verbose_option)
+ {
+ if (verbose_option > 1)
+ decode_header (current_header, &current_stat, &current_format, 0);
+ print_header ();
+ }
+
+ if (incremental_option && current_header->header.typeflag == GNUTYPE_DUMPDIR)
+ {
+ off_t size;
+ size_t written, check;
+ union block *data_block;
+
+ set_next_block_after (current_header);
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, current_file_name);
+ save_totsize = current_stat.st_size;
+ }
+ for (size = current_stat.st_size; size > 0; size -= written)
+ {
+ if (multi_volume_option)
+ save_sizeleft = size;
+ data_block = find_next_block ();
+ if (!data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break; /* FIXME: What happens, then? */
+ }
+ written = available_space_after (data_block);
+ if (written > size)
+ written = size;
+ errno = 0;
+ check = fwrite (data_block->buffer, sizeof (char), written, stdlis);
+ set_next_block_after ((union block *)
+ (data_block->buffer + written - 1));
+ if (check != written)
+ {
+ write_error_details (current_file_name, check, written);
+ skip_file (size - written);
+ break;
+ }
+ }
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+ fputc ('\n', stdlis);
+ fflush (stdlis);
+ return;
+
+ }
+
+ if (multi_volume_option)
+ assign_string (&save_name, current_file_name);
+
+ skip_member ();
+
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+}
+
+/* Read a block that's supposed to be a header block. Return its
+ address in "current_header", and if it is good, the file's size in
+ current_stat.st_size.
+
+ Return 1 for success, 0 if the checksum is bad, EOF on eof, 2 for a
+ block full of zeros (EOF marker).
+
+ If RAW_EXTENDED_HEADERS is nonzero, do not automagically fold the
+ GNU long name and link headers into later headers.
+
+ You must always set_next_block_after(current_header) to skip past
+ the header which this routine reads. */
+
+/* The standard BSD tar sources create the checksum by adding up the
+ bytes in the header as type char. I think the type char was unsigned
+ on the PDP-11, but it's signed on the Next and Sun. It looks like the
+ sources to BSD tar were never changed to compute the checksum
+ correctly, so both the Sun and Next add the bytes of the header as
+ signed chars. This doesn't cause a problem until you get a file with
+ a name containing characters with the high bit set. So read_header
+ computes two checksums -- signed and unsigned. */
+
+enum read_header
+read_header (bool raw_extended_headers)
+{
+ size_t i;
+ int unsigned_sum; /* the POSIX one :-) */
+ int signed_sum; /* the Sun one :-( */
+ int recorded_sum;
+ uintmax_t parsed_sum;
+ char *p;
+ union block *header;
+ union block *header_copy;
+ char *bp;
+ union block *data_block;
+ size_t size, written;
+ union block *next_long_name = 0;
+ union block *next_long_link = 0;
+ size_t next_long_name_blocks;
+ size_t next_long_link_blocks;
+
+ while (1)
+ {
+ header = find_next_block ();
+ current_header = header;
+ if (!header)
+ return HEADER_END_OF_FILE;
+
+ unsigned_sum = 0;
+ signed_sum = 0;
+ p = header->buffer;
+ for (i = sizeof *header; i-- != 0;)
+ {
+ unsigned_sum += (unsigned char) *p;
+ signed_sum += (signed char) (*p++);
+ }
+
+ if (unsigned_sum == 0)
+ return HEADER_ZERO_BLOCK;
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+
+ for (i = sizeof header->header.chksum; i-- != 0;)
+ {
+ unsigned_sum -= (unsigned char) header->header.chksum[i];
+ signed_sum -= (signed char) (header->header.chksum[i]);
+ }
+ unsigned_sum += ' ' * sizeof header->header.chksum;
+ signed_sum += ' ' * sizeof header->header.chksum;
+
+ parsed_sum = from_header (header->header.chksum,
+ sizeof header->header.chksum, 0,
+ (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (int));
+ if (parsed_sum == (uintmax_t) -1)
+ return HEADER_FAILURE;
+
+ recorded_sum = parsed_sum;
+
+ if (unsigned_sum != recorded_sum && signed_sum != recorded_sum)
+ return HEADER_FAILURE;
+
+ /* Good block. Decode file size and return. */
+
+ if (header->header.typeflag == LNKTYPE)
+ current_stat.st_size = 0; /* links 0 size on tape */
+ else
+ current_stat.st_size = OFF_FROM_HEADER (header->header.size);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK)
+ {
+ if (raw_extended_headers)
+ return HEADER_SUCCESS_EXTENDED;
+ else
+ {
+ size_t name_size = current_stat.st_size;
+ size = name_size - name_size % BLOCKSIZE + 2 * BLOCKSIZE;
+ if (name_size != current_stat.st_size || size < name_size)
+ xalloc_die ();
+ }
+
+ header_copy = xmalloc (size + 1);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME)
+ {
+ if (next_long_name)
+ free (next_long_name);
+ next_long_name = header_copy;
+ next_long_name_blocks = size / BLOCKSIZE;
+ }
+ else
+ {
+ if (next_long_link)
+ free (next_long_link);
+ next_long_link = header_copy;
+ next_long_link_blocks = size / BLOCKSIZE;
+ }
+
+ set_next_block_after (header);
+ *header_copy = *header;
+ bp = header_copy->buffer + BLOCKSIZE;
+
+ for (size -= BLOCKSIZE; size > 0; size -= written)
+ {
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break;
+ }
+ written = available_space_after (data_block);
+ if (written > size)
+ written = size;
+
+ memcpy (bp, data_block->buffer, written);
+ bp += written;
+ set_next_block_after ((union block *)
+ (data_block->buffer + written - 1));
+ }
+
+ *bp = '\0';
+
+ /* Loop! */
+
+ }
+ else
+ {
+ char const *name;
+ struct posix_header const *h = &current_header->header;
+ char namebuf[sizeof h->prefix + 1 + NAME_FIELD_SIZE + 1];
+
+ if (recent_long_name)
+ free (recent_long_name);
+
+ if (next_long_name)
+ {
+ name = next_long_name->buffer + BLOCKSIZE;
+ recent_long_name = next_long_name;
+ recent_long_name_blocks = next_long_name_blocks;
+ }
+ else
+ {
+ /* Accept file names as specified by POSIX.1-1996
+ section 10.1.1. */
+ char *np = namebuf;
+
+ if (h->prefix[0] && strcmp (h->magic, TMAGIC) == 0)
+ {
+ memcpy (np, h->prefix, sizeof h->prefix);
+ np[sizeof h->prefix] = '\0';
+ np += strlen (np);
+ *np++ = '/';
+
+ /* Prevent later references to current_header from
+ mistakenly treating this as an old GNU header.
+ This assignment invalidates h->prefix. */
+ current_header->oldgnu_header.isextended = 0;
+ }
+ memcpy (np, h->name, sizeof h->name);
+ np[sizeof h->name] = '\0';
+ name = namebuf;
+ recent_long_name = 0;
+ recent_long_name_blocks = 0;
+ }
+ assign_string (&current_file_name, name);
+
+ if (recent_long_link)
+ free (recent_long_link);
+
+ if (next_long_link)
+ {
+ name = next_long_link->buffer + BLOCKSIZE;
+ recent_long_link = next_long_link;
+ recent_long_link_blocks = next_long_link_blocks;
+ }
+ else
+ {
+ memcpy (namebuf, h->linkname, sizeof h->linkname);
+ namebuf[sizeof h->linkname] = '\0';
+ name = namebuf;
+ recent_long_link = 0;
+ recent_long_link_blocks = 0;
+ }
+ assign_string (&current_link_name, name);
+
+ return HEADER_SUCCESS;
+ }
+ }
+}
+
+/* Decode things from a file HEADER block into STAT_INFO, also setting
+ *FORMAT_POINTER depending on the header block format. If
+ DO_USER_GROUP, decode the user/group information (this is useful
+ for extraction, but waste time when merely listing).
+
+ read_header() has already decoded the checksum and length, so we don't.
+
+ This routine should *not* be called twice for the same block, since
+ the two calls might use different DO_USER_GROUP values and thus
+ might end up with different uid/gid for the two calls. If anybody
+ wants the uid/gid they should decode it first, and other callers
+ should decode it without uid/gid before calling a routine,
+ e.g. print_header, that assumes decoded data. */
+void
+decode_header (union block *header, struct stat *stat_info,
+ enum archive_format *format_pointer, int do_user_group)
+{
+ enum archive_format format;
+
+ if (strcmp (header->header.magic, TMAGIC) == 0)
+ format = POSIX_FORMAT;
+ else if (strcmp (header->header.magic, OLDGNU_MAGIC) == 0)
+ format = OLDGNU_FORMAT;
+ else
+ format = V7_FORMAT;
+ *format_pointer = format;
+
+ stat_info->st_mode = MODE_FROM_HEADER (header->header.mode);
+ stat_info->st_mtime = TIME_FROM_HEADER (header->header.mtime);
+
+ if (format == OLDGNU_FORMAT && incremental_option)
+ {
+ stat_info->st_atime = TIME_FROM_HEADER (header->oldgnu_header.atime);
+ stat_info->st_ctime = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+ }
+
+ if (format == V7_FORMAT)
+ {
+ stat_info->st_uid = UID_FROM_HEADER (header->header.uid);
+ stat_info->st_gid = GID_FROM_HEADER (header->header.gid);
+ stat_info->st_rdev = 0;
+ }
+ else
+ {
+ if (do_user_group)
+ {
+ /* FIXME: Decide if this should somewhat depend on -p. */
+
+ if (numeric_owner_option
+ || !*header->header.uname
+ || !uname_to_uid (header->header.uname, &stat_info->st_uid))
+ stat_info->st_uid = UID_FROM_HEADER (header->header.uid);
+
+ if (numeric_owner_option
+ || !*header->header.gname
+ || !gname_to_gid (header->header.gname, &stat_info->st_gid))
+ stat_info->st_gid = GID_FROM_HEADER (header->header.gid);
+ }
+ switch (header->header.typeflag)
+ {
+ case BLKTYPE:
+ stat_info->st_rdev
+ = makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+ MINOR_FROM_HEADER (header->header.devminor));
+ break;
+
+ case CHRTYPE:
+ stat_info->st_rdev
+ = makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+ MINOR_FROM_HEADER (header->header.devminor));
+ break;
+
+ default:
+ stat_info->st_rdev = 0;
+ }
+ }
+}
+
+/* Convert buffer at WHERE0 of size DIGS from external format to
+ uintmax_t. The data is of type TYPE. The buffer must represent a
+ value in the range -MINUS_MINVAL through MAXVAL. DIGS must be
+ positive. Return -1 on error, diagnosing the error if TYPE is
+ nonzero. */
+static uintmax_t
+from_header (char const *where0, size_t digs, char const *type,
+ uintmax_t minus_minval, uintmax_t maxval)
+{
+ uintmax_t value;
+ char const *where = where0;
+ char const *lim = where + digs;
+ int negative = 0;
+
+ /* Accommodate buggy tar of unknown vintage, which outputs leading
+ NUL if the previous field overflows. */
+ where += !*where;
+
+ /* Accommodate older tars, which output leading spaces. */
+ for (;;)
+ {
+ if (where == lim)
+ {
+ if (type)
+ ERROR ((0, 0,
+ _("Blanks in header where numeric %s value expected"),
+ type));
+ return -1;
+ }
+ if (!ISSPACE ((unsigned char) *where))
+ break;
+ where++;
+ }
+
+ value = 0;
+ if (ISODIGIT (*where))
+ {
+ char const *where1 = where;
+ uintmax_t overflow = 0;
+
+ for (;;)
+ {
+ value += *where++ - '0';
+ if (where == lim || ! ISODIGIT (*where))
+ break;
+ overflow |= value ^ (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ /* Parse the output of older, unportable tars, which generate
+ negative values in two's complement octal. If the leading
+ nonzero digit is 1, we can't recover the original value
+ reliably; so do this only if the digit is 2 or more. This
+ catches the common case of 32-bit negative time stamps. */
+ if ((overflow || maxval < value) && '2' <= *where1 && type)
+ {
+ /* Compute the negative of the input value, assuming two's
+ complement. */
+ int digit = (*where1 - '0') | 4;
+ overflow = 0;
+ value = 0;
+ where = where1;
+ for (;;)
+ {
+ value += 7 - digit;
+ where++;
+ if (where == lim || ! ISODIGIT (*where))
+ break;
+ digit = *where - '0';
+ overflow |= value ^ (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+ value++;
+ overflow |= !value;
+
+ if (!overflow && value <= minus_minval)
+ {
+ WARN ((0, 0,
+ _("Archive octal value %.*s is out of %s range; assuming two's complement"),
+ (int) (where - where1), where1, type));
+ negative = 1;
+ }
+ }
+
+ if (overflow)
+ {
+ if (type)
+ ERROR ((0, 0,
+ _("Archive octal value %.*s is out of %s range"),
+ (int) (where - where1), where1, type));
+ return -1;
+ }
+ }
+ else if (*where == '-' || *where == '+')
+ {
+ /* Parse base-64 output produced only by tar test versions
+ 1.13.6 (1999-08-11) through 1.13.11 (1999-08-23).
+ Support for this will be withdrawn in future releases. */
+ int dig;
+ static int warned_once;
+ if (! warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0,
+ _("Archive contains obsolescent base-64 headers")));
+ }
+ negative = *where++ == '-';
+ while (where != lim
+ && (dig = base64_map[(unsigned char) *where]) < 64)
+ {
+ if (value << LG_64 >> LG_64 != value)
+ {
+ char *string = alloca (digs + 1);
+ memcpy (string, where0, digs);
+ string[digs] = '\0';
+ if (type)
+ ERROR ((0, 0,
+ _("Archive signed base-64 string %s is out of %s range"),
+ quote (string), type));
+ return -1;
+ }
+ value = (value << LG_64) | dig;
+ where++;
+ }
+ }
+ else if (*where == '\200' /* positive base-256 */
+ || *where == '\377' /* negative base-256 */)
+ {
+ /* Parse base-256 output. A nonnegative number N is
+ represented as (256**DIGS)/2 + N; a negative number -N is
+ represented as (256**DIGS) - N, i.e. as two's complement.
+ The representation guarantees that the leading bit is
+ always on, so that we don't confuse this format with the
+ others (assuming ASCII bytes of 8 bits or more). */
+ int signbit = *where & (1 << (LG_256 - 2));
+ uintmax_t topbits = (((uintmax_t) - signbit)
+ << (CHAR_BIT * sizeof (uintmax_t)
+ - LG_256 - (LG_256 - 2)));
+ value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+ for (;;)
+ {
+ value = (value << LG_256) + (unsigned char) *where++;
+ if (where == lim)
+ break;
+ if (((value << LG_256 >> LG_256) | topbits) != value)
+ {
+ if (type)
+ ERROR ((0, 0,
+ _("Archive base-256 value is out of %s range"),
+ type));
+ return -1;
+ }
+ }
+ negative = signbit;
+ if (negative)
+ value = -value;
+ }
+
+ if (where != lim && *where && !ISSPACE ((unsigned char) *where))
+ {
+ if (type)
+ {
+ char buf[1000]; /* Big enough to represent any header. */
+ static struct quoting_options *o;
+
+ if (!o)
+ {
+ o = clone_quoting_options (0);
+ set_quoting_style (o, locale_quoting_style);
+ }
+
+ while (where0 != lim && ! lim[-1])
+ lim--;
+ quotearg_buffer (buf, sizeof buf, where0, lim - where, o);
+ ERROR ((0, 0,
+ _("Archive contains %.*s where numeric %s value expected"),
+ (int) sizeof buf, buf, type));
+ }
+
+ return -1;
+ }
+
+ if (value <= (negative ? minus_minval : maxval))
+ return negative ? -value : value;
+
+ if (type)
+ {
+ char minval_buf[UINTMAX_STRSIZE_BOUND + 1];
+ char maxval_buf[UINTMAX_STRSIZE_BOUND];
+ char value_buf[UINTMAX_STRSIZE_BOUND + 1];
+ char *minval_string = STRINGIFY_BIGINT (minus_minval, minval_buf + 1);
+ char *value_string = STRINGIFY_BIGINT (value, value_buf + 1);
+ if (negative)
+ *--value_string = '-';
+ if (minus_minval)
+ *--minval_string = '-';
+ ERROR ((0, 0, _("Archive value %s is out of %s range %s..%s"),
+ value_string, type,
+ minval_string, STRINGIFY_BIGINT (maxval, maxval_buf)));
+ }
+
+ return -1;
+}
+
+gid_t
+gid_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "gid_t",
+ - (uintmax_t) TYPE_MINIMUM (gid_t),
+ (uintmax_t) TYPE_MAXIMUM (gid_t));
+}
+
+major_t
+major_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "major_t",
+ - (uintmax_t) TYPE_MINIMUM (major_t),
+ (uintmax_t) TYPE_MAXIMUM (major_t));
+}
+
+minor_t
+minor_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "minor_t",
+ - (uintmax_t) TYPE_MINIMUM (minor_t),
+ (uintmax_t) TYPE_MAXIMUM (minor_t));
+}
+
+mode_t
+mode_from_header (const char *p, size_t s)
+{
+ /* Do not complain about unrecognized mode bits. */
+ unsigned u = from_header (p, s, "mode_t",
+ - (uintmax_t) TYPE_MINIMUM (mode_t),
+ TYPE_MAXIMUM (uintmax_t));
+ return ((u & TSUID ? S_ISUID : 0)
+ | (u & TSGID ? S_ISGID : 0)
+ | (u & TSVTX ? S_ISVTX : 0)
+ | (u & TUREAD ? S_IRUSR : 0)
+ | (u & TUWRITE ? S_IWUSR : 0)
+ | (u & TUEXEC ? S_IXUSR : 0)
+ | (u & TGREAD ? S_IRGRP : 0)
+ | (u & TGWRITE ? S_IWGRP : 0)
+ | (u & TGEXEC ? S_IXGRP : 0)
+ | (u & TOREAD ? S_IROTH : 0)
+ | (u & TOWRITE ? S_IWOTH : 0)
+ | (u & TOEXEC ? S_IXOTH : 0));
+}
+
+off_t
+off_from_header (const char *p, size_t s)
+{
+ /* Negative offsets are not allowed in tar files, so invoke
+ from_header with minimum value 0, not TYPE_MINIMUM (off_t). */
+ return from_header (p, s, "off_t", (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (off_t));
+}
+
+size_t
+size_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "size_t", (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (size_t));
+}
+
+time_t
+time_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "time_t",
+ - (uintmax_t) TYPE_MINIMUM (time_t),
+ (uintmax_t) TYPE_MAXIMUM (time_t));
+}
+
+uid_t
+uid_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "uid_t",
+ - (uintmax_t) TYPE_MINIMUM (uid_t),
+ (uintmax_t) TYPE_MAXIMUM (uid_t));
+}
+
+uintmax_t
+uintmax_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "uintmax_t", (uintmax_t) 0,
+ TYPE_MAXIMUM (uintmax_t));
+}
+
+
+/* Format O as a null-terminated decimal string into BUF _backwards_;
+ return pointer to start of result. */
+char *
+stringify_uintmax_t_backwards (uintmax_t o, char *buf)
+{
+ *--buf = '\0';
+ do
+ *--buf = '0' + (int) (o % 10);
+ while ((o /= 10) != 0);
+ return buf;
+}
+
+/* Return a printable representation of T. The result points to
+ static storage that can be reused in the next call to this
+ function, to ctime, or to asctime. */
+char const *
+tartime (time_t t)
+{
+ static char buffer[max (UINTMAX_STRSIZE_BOUND + 1,
+ INT_STRLEN_BOUND (int) + 16)];
+ char *p;
+
+#if USE_OLD_CTIME
+ p = ctime (&t);
+ if (p)
+ {
+ char const *time_stamp = p + 4;
+ for (p += 16; p[3] != '\n'; p++)
+ p[0] = p[3];
+ p[0] = '\0';
+ return time_stamp;
+ }
+#else
+ /* Use ISO 8610 format. See:
+ http://www.cl.cam.ac.uk/~mgk25/iso-time.html */
+ struct tm *tm = localtime (&t);
+ if (tm)
+ {
+ sprintf (buffer, "%04d-%02d-%02d %02d:%02d:%02d",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ return buffer;
+ }
+#endif
+
+ /* The time stamp cannot be broken down, most likely because it
+ is out of range. Convert it as an integer,
+ right-adjusted in a field with the same width as the usual
+ 19-byte 4-year ISO time format. */
+ p = stringify_uintmax_t_backwards (t < 0 ? - (uintmax_t) t : (uintmax_t) t,
+ buffer + sizeof buffer);
+ if (t < 0)
+ *--p = '-';
+ while (buffer + sizeof buffer - 19 - 1 < p)
+ *--p = ' ';
+ return p;
+}
+
+/* Actually print it.
+
+ Plain and fancy file header block logging. Non-verbose just prints
+ the name, e.g. for "tar t" or "tar x". This should just contain
+ file names, so it can be fed back into tar with xargs or the "-T"
+ option. The verbose option can give a bunch of info, one line per
+ file. I doubt anybody tries to parse its format, or if they do,
+ they shouldn't. Unix tar is pretty random here anyway. */
+
+
+/* FIXME: Note that print_header uses the globals HEAD, HSTAT, and
+ HEAD_STANDARD, which must be set up in advance. Not very clean... */
+
+/* UGSWIDTH starts with 18, so with user and group names <= 8 chars, the
+ columns never shift during the listing. */
+#define UGSWIDTH 18
+static int ugswidth = UGSWIDTH; /* maximum width encountered so far */
+
+/* DATEWIDTH is the number of columns taken by the date and time fields. */
+#if USE_OLD_CDATE
+# define DATEWIDTH 19
+#else
+# define DATEWIDTH 18
+#endif
+
+void
+print_header (void)
+{
+ char modes[11];
+ char const *time_stamp;
+ /* These hold formatted ints. */
+ char uform[UINTMAX_STRSIZE_BOUND], gform[UINTMAX_STRSIZE_BOUND];
+ char *user, *group;
+ char size[2 * UINTMAX_STRSIZE_BOUND];
+ /* holds formatted size or major,minor */
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+ int pad;
+
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: "),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+
+ if (verbose_option <= 1)
+ {
+ /* Just the fax, mam. */
+ fprintf (stdlis, "%s\n", quotearg (current_file_name));
+ }
+ else
+ {
+ /* File type and modes. */
+
+ modes[0] = '?';
+ switch (current_header->header.typeflag)
+ {
+ case GNUTYPE_VOLHDR:
+ modes[0] = 'V';
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ modes[0] = 'M';
+ break;
+
+ case GNUTYPE_NAMES:
+ modes[0] = 'N';
+ break;
+
+ case GNUTYPE_LONGNAME:
+ case GNUTYPE_LONGLINK:
+ ERROR ((0, 0, _("Visible longname error")));
+ break;
+
+ case GNUTYPE_SPARSE:
+ case REGTYPE:
+ case AREGTYPE:
+ case LNKTYPE:
+ modes[0] = '-';
+ if (current_file_name[strlen (current_file_name) - 1] == '/')
+ modes[0] = 'd';
+ break;
+ case GNUTYPE_DUMPDIR:
+ modes[0] = 'd';
+ break;
+ case DIRTYPE:
+ modes[0] = 'd';
+ break;
+ case SYMTYPE:
+ modes[0] = 'l';
+ break;
+ case BLKTYPE:
+ modes[0] = 'b';
+ break;
+ case CHRTYPE:
+ modes[0] = 'c';
+ break;
+ case FIFOTYPE:
+ modes[0] = 'p';
+ break;
+ case CONTTYPE:
+ modes[0] = 'C';
+ break;
+ }
+
+ decode_mode (current_stat.st_mode, modes + 1);
+
+ /* Time stamp. */
+
+ time_stamp = tartime (current_stat.st_mtime);
+
+ /* User and group names. */
+
+ if (*current_header->header.uname && current_format != V7_FORMAT
+ && !numeric_owner_option)
+ user = current_header->header.uname;
+ else
+ {
+ /* Try parsing it as an unsigned integer first, and as a
+ uid_t if that fails. This method can list positive user
+ ids that are too large to fit in a uid_t. */
+ uintmax_t u = from_header (current_header->header.uid,
+ sizeof current_header->header.uid, 0,
+ (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (uintmax_t));
+ if (u != -1)
+ user = STRINGIFY_BIGINT (u, uform);
+ else
+ {
+ sprintf (uform, "%ld",
+ (long) UID_FROM_HEADER (current_header->header.uid));
+ user = uform;
+ }
+ }
+
+ if (*current_header->header.gname && current_format != V7_FORMAT
+ && !numeric_owner_option)
+ group = current_header->header.gname;
+ else
+ {
+ /* Try parsing it as an unsigned integer first, and as a
+ gid_t if that fails. This method can list positive group
+ ids that are too large to fit in a gid_t. */
+ uintmax_t g = from_header (current_header->header.gid,
+ sizeof current_header->header.gid, 0,
+ (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (uintmax_t));
+ if (g != -1)
+ group = STRINGIFY_BIGINT (g, gform);
+ else
+ {
+ sprintf (gform, "%ld",
+ (long) GID_FROM_HEADER (current_header->header.gid));
+ group = gform;
+ }
+ }
+
+ /* Format the file size or major/minor device numbers. */
+
+ switch (current_header->header.typeflag)
+ {
+ case CHRTYPE:
+ case BLKTYPE:
+ strcpy (size,
+ STRINGIFY_BIGINT (major (current_stat.st_rdev), uintbuf));
+ strcat (size, ",");
+ strcat (size,
+ STRINGIFY_BIGINT (minor (current_stat.st_rdev), uintbuf));
+ break;
+ case GNUTYPE_SPARSE:
+ strcpy (size,
+ STRINGIFY_BIGINT
+ (UINTMAX_FROM_HEADER (current_header
+ ->oldgnu_header.realsize),
+ uintbuf));
+ break;
+ default:
+ strcpy (size, STRINGIFY_BIGINT (current_stat.st_size, uintbuf));
+ break;
+ }
+
+ /* Figure out padding and print the whole line. */
+
+ pad = strlen (user) + strlen (group) + strlen (size) + 1;
+ if (pad > ugswidth)
+ ugswidth = pad;
+
+ fprintf (stdlis, "%s %s/%s %*s%s %s",
+ modes, user, group, ugswidth - pad, "", size, time_stamp);
+
+ fprintf (stdlis, " %s", quotearg (current_file_name));
+
+ switch (current_header->header.typeflag)
+ {
+ case SYMTYPE:
+ fprintf (stdlis, " -> %s\n", quotearg (current_link_name));
+ break;
+
+ case LNKTYPE:
+ fprintf (stdlis, _(" link to %s\n"), quotearg (current_link_name));
+ break;
+
+ default:
+ {
+ char type_string[2];
+ type_string[0] = current_header->header.typeflag;
+ type_string[1] = '\0';
+ fprintf (stdlis, _(" unknown file type %s\n"),
+ quote (type_string));
+ }
+ break;
+
+ case AREGTYPE:
+ case REGTYPE:
+ case GNUTYPE_SPARSE:
+ case CHRTYPE:
+ case BLKTYPE:
+ case DIRTYPE:
+ case FIFOTYPE:
+ case CONTTYPE:
+ case GNUTYPE_DUMPDIR:
+ putc ('\n', stdlis);
+ break;
+
+ case GNUTYPE_VOLHDR:
+ fprintf (stdlis, _("--Volume Header--\n"));
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ strcpy (size,
+ STRINGIFY_BIGINT
+ (UINTMAX_FROM_HEADER (current_header->oldgnu_header.offset),
+ uintbuf));
+ fprintf (stdlis, _("--Continued at byte %s--\n"), size);
+ break;
+
+ case GNUTYPE_NAMES:
+ fprintf (stdlis, _("--Mangled file names--\n"));
+ break;
+ }
+ }
+ fflush (stdlis);
+}
+
+/* Print a similar line when we make a directory automatically. */
+void
+print_for_mkdir (char *pathname, int length, mode_t mode)
+{
+ char modes[11];
+
+ if (verbose_option > 1)
+ {
+ /* File type and modes. */
+
+ modes[0] = 'd';
+ decode_mode (mode, modes + 1);
+
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: "),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+
+ fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH,
+ _("Creating directory:"), length, quotearg (pathname));
+ }
+}
+
+/* Skip over SIZE bytes of data in blocks in the archive. */
+void
+skip_file (off_t size)
+{
+ union block *x;
+
+ if (multi_volume_option)
+ {
+ save_totsize = size;
+ save_sizeleft = size;
+ }
+
+ while (size > 0)
+ {
+ x = find_next_block ();
+ if (! x)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+
+ set_next_block_after (x);
+ size -= BLOCKSIZE;
+ if (multi_volume_option)
+ save_sizeleft -= BLOCKSIZE;
+ }
+}
+
+/* Skip the current member in the archive. */
+void
+skip_member (void)
+{
+ char save_typeflag = current_header->header.typeflag;
+ set_next_block_after (current_header);
+
+ if (current_header->oldgnu_header.isextended)
+ {
+ union block *exhdr;
+ do
+ {
+ exhdr = find_next_block ();
+ if (!exhdr)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+ set_next_block_after (exhdr);
+ }
+ while (exhdr->sparse_header.isextended);
+ }
+
+ if (save_typeflag != DIRTYPE)
+ skip_file (current_stat.st_size);
+}
diff --git a/contrib/tar/src/mangle.c b/contrib/tar/src/mangle.c
new file mode 100644
index 0000000..204bf7c
--- /dev/null
+++ b/contrib/tar/src/mangle.c
@@ -0,0 +1,121 @@
+/* Encode long filenames for GNU tar.
+ Copyright 1988, 92, 94, 96, 97, 99, 2000 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+#include "common.h"
+#include <quotearg.h>
+
+struct mangled
+ {
+ struct mangled *next;
+ int type;
+ char mangled[NAME_FIELD_SIZE];
+ char *linked_to;
+ char normal[1];
+ };
+
+/* Extract a GNUTYPE_NAMES record contents. It seems that such are
+ not produced anymore by GNU tar, but we leave the reading code
+ around nevertheless, for salvaging old tapes. */
+void
+extract_mangle (void)
+{
+ off_t size = current_stat.st_size;
+ char *buffer = xmalloc ((size_t) (size + 1));
+ char *copy = buffer;
+ char *cursor = buffer;
+
+ if (size != (size_t) size || size == (size_t) -1)
+ xalloc_die ();
+
+ buffer[size] = '\0';
+
+ while (size > 0)
+ {
+ union block *block = find_next_block ();
+ size_t available;
+
+ if (!block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in mangled names")));
+ return;
+ }
+ available = available_space_after (block);
+ if (available > size)
+ available = size;
+ memcpy (copy, block->buffer, available);
+ copy += available;
+ size -= available;
+ set_next_block_after ((union block *) (block->buffer + available - 1));
+ }
+
+ while (*cursor)
+ {
+ char *next_cursor;
+ char *name;
+ char *name_end;
+
+ next_cursor = strchr (cursor, '\n');
+ *next_cursor++ = '\0';
+
+ if (!strncmp (cursor, "Rename ", 7))
+ {
+
+ name = cursor + 7;
+ name_end = strchr (name, ' ');
+ while (strncmp (name_end, " to ", 4))
+ {
+ name_end++;
+ name_end = strchr (name_end, ' ');
+ }
+ *name_end = '\0';
+ if (next_cursor[-2] == '/')
+ next_cursor[-2] = '\0';
+ unquote_string (name_end + 4);
+ if (rename (name, name_end + 4))
+ ERROR ((0, errno, _("%s: Cannot rename to %s"),
+ quotearg_colon (name), quote_n (1, name_end + 4)));
+ else if (verbose_option)
+ WARN ((0, 0, _("Renamed %s to %s"), name, name_end + 4));
+ }
+#ifdef HAVE_SYMLINK
+ else if (!strncmp (cursor, "Symlink ", 8))
+ {
+ name = cursor + 8;
+ name_end = strchr (name, ' ');
+ while (strncmp (name_end, " to ", 4))
+ {
+ name_end++;
+ name_end = strchr (name_end, ' ');
+ }
+ *name_end = '\0';
+ unquote_string (name);
+ unquote_string (name_end + 4);
+ if (symlink (name, name_end + 4)
+ && (unlink (name_end + 4) || symlink (name, name_end + 4)))
+ ERROR ((0, errno, _("%s: Cannot symlink to %s"),
+ quotearg_colon (name), quote_n (1, name_end + 4)));
+ else if (verbose_option)
+ WARN ((0, 0, _("Symlinked %s to %s"), name, name_end + 4));
+ }
+#endif
+ else
+ ERROR ((0, 0, _("Unknown demangling command %s"), cursor));
+
+ cursor = next_cursor;
+ }
+}
diff --git a/contrib/tar/src/misc.c b/contrib/tar/src/misc.c
new file mode 100644
index 0000000..e90c248
--- /dev/null
+++ b/contrib/tar/src/misc.c
@@ -0,0 +1,847 @@
+/* Miscellaneous functions, not really specific to GNU tar.
+
+ Copyright 1988, 1992, 1994, 1995, 1996, 1997, 1999, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+#include "rmt.h"
+#include "common.h"
+#include <quotearg.h>
+#include <save-cwd.h>
+
+static void call_arg_fatal PARAMS ((char const *, char const *))
+ __attribute__ ((noreturn));
+
+/* Handling strings. */
+
+/* Assign STRING to a copy of VALUE if not zero, or to zero. If
+ STRING was nonzero, it is freed first. */
+void
+assign_string (char **string, const char *value)
+{
+ if (*string)
+ free (*string);
+ *string = value ? xstrdup (value) : 0;
+}
+
+/* Allocate a copy of the string quoted as in C, and returns that. If
+ the string does not have to be quoted, it returns a null pointer.
+ The allocated copy should normally be freed with free() after the
+ caller is done with it.
+
+ This is used in one context only: generating the directory file in
+ incremental dumps. The quoted string is not intended for human
+ consumption; it is intended only for unquote_string. The quoting
+ is locale-independent, so that users needn't worry about locale
+ when reading directory files. This means that we can't use
+ quotearg, as quotearg is locale-dependent and is meant for human
+ consumption. */
+char *
+quote_copy_string (const char *string)
+{
+ const char *source = string;
+ char *destination = 0;
+ char *buffer = 0;
+ int copying = 0;
+
+ while (*source)
+ {
+ int character = *source++;
+
+ switch (character)
+ {
+ case '\n': case '\\':
+ if (!copying)
+ {
+ size_t length = (source - string) - 1;
+
+ copying = 1;
+ buffer = xmalloc (length + 2 + 2 * strlen (source) + 1);
+ memcpy (buffer, string, length);
+ destination = buffer + length;
+ }
+ *destination++ = '\\';
+ *destination++ = character == '\\' ? '\\' : 'n';
+ break;
+
+ default:
+ if (copying)
+ *destination++ = character;
+ break;
+ }
+ }
+ if (copying)
+ {
+ *destination = '\0';
+ return buffer;
+ }
+ return 0;
+}
+
+/* Takes a quoted C string (like those produced by quote_copy_string)
+ and turns it back into the un-quoted original. This is done in
+ place. Returns 0 only if the string was not properly quoted, but
+ completes the unquoting anyway.
+
+ This is used for reading the saved directory file in incremental
+ dumps. It is used for decoding old `N' records (demangling names).
+ But also, it is used for decoding file arguments, would they come
+ from the shell or a -T file, and for decoding the --exclude
+ argument. */
+int
+unquote_string (char *string)
+{
+ int result = 1;
+ char *source = string;
+ char *destination = string;
+
+ /* Escape sequences other than \\ and \n are no longer generated by
+ quote_copy_string, but accept them for backwards compatibility,
+ and also because unquote_string is used for purposes other than
+ parsing the output of quote_copy_string. */
+
+ while (*source)
+ if (*source == '\\')
+ switch (*++source)
+ {
+ case '\\':
+ *destination++ = '\\';
+ source++;
+ break;
+
+ case 'n':
+ *destination++ = '\n';
+ source++;
+ break;
+
+ case 't':
+ *destination++ = '\t';
+ source++;
+ break;
+
+ case 'f':
+ *destination++ = '\f';
+ source++;
+ break;
+
+ case 'b':
+ *destination++ = '\b';
+ source++;
+ break;
+
+ case 'r':
+ *destination++ = '\r';
+ source++;
+ break;
+
+ case '?':
+ *destination++ = 0177;
+ source++;
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int value = *source++ - '0';
+
+ if (*source < '0' || *source > '7')
+ {
+ *destination++ = value;
+ break;
+ }
+ value = value * 8 + *source++ - '0';
+ if (*source < '0' || *source > '7')
+ {
+ *destination++ = value;
+ break;
+ }
+ value = value * 8 + *source++ - '0';
+ *destination++ = value;
+ break;
+ }
+
+ default:
+ result = 0;
+ *destination++ = '\\';
+ if (*source)
+ *destination++ = *source++;
+ break;
+ }
+ else if (source != destination)
+ *destination++ = *source++;
+ else
+ source++, destination++;
+
+ if (source != destination)
+ *destination = '\0';
+ return result;
+}
+
+/* Return nonzero if NAME contains ".." as a path name component. */
+int
+contains_dot_dot (char const *name)
+{
+ char const *p = name + FILESYSTEM_PREFIX_LEN (name);
+
+ for (;;)
+ {
+ if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))
+ return 1;
+
+ do
+ {
+ if (! *p++)
+ return 0;
+ }
+ while (! ISSLASH (*p));
+ }
+}
+
+/* File handling. */
+
+/* Saved names in case backup needs to be undone. */
+static char *before_backup_name;
+static char *after_backup_name;
+
+/* Some implementations of rmdir let you remove the working directory.
+ Report an error with errno set to zero for obvious cases of this;
+ otherwise call rmdir. */
+static int
+safer_rmdir (const char *path)
+{
+ while (path[0] == '.' && ISSLASH (path[1]))
+ {
+ path++;
+
+ do path++;
+ while (ISSLASH (*path));
+ }
+
+ if (! path[0] || (path[0] == '.' && ! path[1]))
+ {
+ errno = 0;
+ return -1;
+ }
+
+ return rmdir (path);
+}
+
+/* Remove PATH. If PATH is a directory, then if RECURSE is set remove
+ it recursively; otherwise, remove it only if it is empty. Return 0
+ on error, with errno set; if PATH is obviously the working
+ directory return zero with errno set to zero. */
+int
+remove_any_file (const char *path, int recurse)
+{
+ /* Try unlink first if we are not root, as this saves us a system
+ call in the common case where we're removing a non-directory. */
+ if (! we_are_root)
+ {
+ if (unlink (path) == 0)
+ return 1;
+ if (errno != EPERM)
+ return 0;
+ }
+
+ if (safer_rmdir (path) == 0)
+ return 1;
+
+ switch (errno)
+ {
+ case ENOTDIR:
+ return we_are_root && unlink (path) == 0;
+
+ case 0:
+ case EEXIST:
+#if defined ENOTEMPTY && ENOTEMPTY != EEXIST
+ case ENOTEMPTY:
+#endif
+ if (recurse)
+ {
+ char *directory = savedir (path);
+ char const *entry;
+ size_t entrylen;
+
+ if (! directory)
+ return 0;
+
+ for (entry = directory;
+ (entrylen = strlen (entry)) != 0;
+ entry += entrylen + 1)
+ {
+ char *path_buffer = new_name (path, entry);
+ int r = remove_any_file (path_buffer, 1);
+ int e = errno;
+ free (path_buffer);
+
+ if (! r)
+ {
+ free (directory);
+ errno = e;
+ return 0;
+ }
+ }
+
+ free (directory);
+ return safer_rmdir (path) == 0;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* Check if PATH already exists and make a backup of it right now.
+ Return success (nonzero) only if the backup in either unneeded, or
+ successful. For now, directories are considered to never need
+ backup. If ARCHIVE is nonzero, this is the archive and so, we do
+ not have to backup block or character devices, nor remote entities. */
+int
+maybe_backup_file (const char *path, int archive)
+{
+ struct stat file_stat;
+
+ /* Check if we really need to backup the file. */
+
+ if (archive && _remdev (path))
+ return 1;
+
+ if (stat (path, &file_stat))
+ {
+ if (errno == ENOENT)
+ return 1;
+
+ stat_error (path);
+ return 0;
+ }
+
+ if (S_ISDIR (file_stat.st_mode))
+ return 1;
+
+ if (archive && (S_ISBLK (file_stat.st_mode) || S_ISCHR (file_stat.st_mode)))
+ return 1;
+
+ assign_string (&before_backup_name, path);
+
+ /* A run situation may exist between Emacs or other GNU programs trying to
+ make a backup for the same file simultaneously. If theoretically
+ possible, real problems are unlikely. Doing any better would require a
+ convention, GNU-wide, for all programs doing backups. */
+
+ assign_string (&after_backup_name, 0);
+ after_backup_name = find_backup_file_name (path, backup_type);
+ if (! after_backup_name)
+ xalloc_die ();
+
+ if (rename (before_backup_name, after_backup_name) == 0)
+ {
+ if (verbose_option)
+ fprintf (stdlis, _("Renaming %s to %s\n"),
+ quote_n (0, before_backup_name),
+ quote_n (1, after_backup_name));
+ return 1;
+ }
+ else
+ {
+ /* The backup operation failed. */
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot rename to %s"),
+ quotearg_colon (before_backup_name),
+ quote_n (1, after_backup_name)));
+ assign_string (&after_backup_name, 0);
+ return 0;
+ }
+}
+
+/* Try to restore the recently backed up file to its original name.
+ This is usually only needed after a failed extraction. */
+void
+undo_last_backup (void)
+{
+ if (after_backup_name)
+ {
+ if (rename (after_backup_name, before_backup_name) != 0)
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot rename to %s"),
+ quotearg_colon (after_backup_name),
+ quote_n (1, before_backup_name)));
+ }
+ if (verbose_option)
+ fprintf (stdlis, _("Renaming %s back to %s\n"),
+ quote_n (0, after_backup_name),
+ quote_n (1, before_backup_name));
+ assign_string (&after_backup_name, 0);
+ }
+}
+
+/* Depending on DEREF, apply either stat or lstat to (NAME, BUF). */
+int
+deref_stat (int deref, char const *name, struct stat *buf)
+{
+ return deref ? stat (name, buf) : lstat (name, buf);
+}
+
+/* A description of a working directory. */
+struct wd
+{
+ char const *name;
+ int saved;
+ struct saved_cwd saved_cwd;
+};
+
+/* A vector of chdir targets. wd[0] is the initial working directory. */
+static struct wd *wd;
+
+/* The number of working directories in the vector. */
+static size_t wds;
+
+/* The allocated size of the vector. */
+static size_t wd_alloc;
+
+/* DIR is the operand of a -C option; add it to vector of chdir targets,
+ and return the index of its location. */
+int
+chdir_arg (char const *dir)
+{
+ if (wds == wd_alloc)
+ {
+ wd_alloc = 2 * (wd_alloc + 1);
+ wd = xrealloc (wd, sizeof *wd * wd_alloc);
+ if (! wds)
+ {
+ wd[wds].name = ".";
+ wd[wds].saved = 0;
+ wds++;
+ }
+ }
+
+ /* Optimize the common special case of the working directory,
+ or the working directory as a prefix. */
+ if (dir[0])
+ {
+ while (dir[0] == '.' && ISSLASH (dir[1]))
+ for (dir += 2; ISSLASH (*dir); dir++)
+ continue;
+ if (! dir[dir[0] == '.'])
+ return wds - 1;
+ }
+
+ wd[wds].name = dir;
+ wd[wds].saved = 0;
+ return wds++;
+}
+
+/* Change to directory I. If I is 0, change to the initial working
+ directory; otherwise, I must be a value returned by chdir_arg. */
+void
+chdir_do (int i)
+{
+ static int previous;
+
+ if (previous != i)
+ {
+ struct wd *prev = &wd[previous];
+ struct wd *curr = &wd[i];
+
+ if (! prev->saved)
+ {
+ prev->saved = 1;
+ if (save_cwd (&prev->saved_cwd) != 0)
+ FATAL_ERROR ((0, 0, _("Cannot save working directory")));
+ }
+
+ if (curr->saved)
+ {
+ if (restore_cwd (&curr->saved_cwd, curr->name, prev->name))
+ FATAL_ERROR ((0, 0, _("Cannot change working directory")));
+ }
+ else
+ {
+ if (i && ! ISSLASH (curr->name[0]))
+ chdir_do (i - 1);
+ if (chdir (curr->name) != 0)
+ chdir_fatal (curr->name);
+ }
+
+ previous = i;
+ }
+}
+
+/* Decode MODE from its binary form in a stat structure, and encode it
+ into a 9-byte string STRING, terminated with a NUL. */
+
+void
+decode_mode (mode_t mode, char *string)
+{
+ *string++ = mode & S_IRUSR ? 'r' : '-';
+ *string++ = mode & S_IWUSR ? 'w' : '-';
+ *string++ = (mode & S_ISUID
+ ? (mode & S_IXUSR ? 's' : 'S')
+ : (mode & S_IXUSR ? 'x' : '-'));
+ *string++ = mode & S_IRGRP ? 'r' : '-';
+ *string++ = mode & S_IWGRP ? 'w' : '-';
+ *string++ = (mode & S_ISGID
+ ? (mode & S_IXGRP ? 's' : 'S')
+ : (mode & S_IXGRP ? 'x' : '-'));
+ *string++ = mode & S_IROTH ? 'r' : '-';
+ *string++ = mode & S_IWOTH ? 'w' : '-';
+ *string++ = (mode & S_ISVTX
+ ? (mode & S_IXOTH ? 't' : 'T')
+ : (mode & S_IXOTH ? 'x' : '-'));
+ *string = '\0';
+}
+
+/* Report an error associated with the system call CALL and the
+ optional name NAME. */
+static void
+call_arg_error (char const *call, char const *name)
+{
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot %s"), quotearg_colon (name), call));
+}
+
+/* Report a fatal error associated with the system call CALL and
+ the optional file name NAME. */
+static void
+call_arg_fatal (char const *call, char const *name)
+{
+ int e = errno;
+ FATAL_ERROR ((0, e, _("%s: Cannot %s"), quotearg_colon (name), call));
+}
+
+/* Report a warning associated with the system call CALL and
+ the optional file name NAME. */
+static void
+call_arg_warn (char const *call, char const *name)
+{
+ int e = errno;
+ WARN ((0, e, _("%s: Warning: Cannot %s"), quotearg_colon (name), call));
+}
+
+void
+chdir_fatal (char const *name)
+{
+ call_arg_fatal ("chdir", name);
+}
+
+void
+chmod_error_details (char const *name, mode_t mode)
+{
+ int e = errno;
+ char buf[10];
+ decode_mode (mode, buf);
+ ERROR ((0, e, _("%s: Cannot change mode to %s"),
+ quotearg_colon (name), buf));
+}
+
+void
+chown_error_details (char const *name, uid_t uid, gid_t gid)
+{
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot change ownership to uid %lu, gid %lu"),
+ quotearg_colon (name), (unsigned long) uid, (unsigned long) gid));
+}
+
+void
+close_error (char const *name)
+{
+ call_arg_error ("close", name);
+}
+
+void
+close_fatal (char const *name)
+{
+ call_arg_fatal ("close", name);
+}
+
+void
+close_warn (char const *name)
+{
+ call_arg_warn ("close", name);
+}
+
+void
+exec_fatal (char const *name)
+{
+ call_arg_fatal ("exec", name);
+}
+
+void
+link_error (char const *target, char const *source)
+{
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot hard link to %s"),
+ quotearg_colon (source), quote_n (1, target)));
+}
+
+void
+mkdir_error (char const *name)
+{
+ call_arg_error ("mkdir", name);
+}
+
+void
+mkfifo_error (char const *name)
+{
+ call_arg_error ("mkfifo", name);
+}
+
+void
+mknod_error (char const *name)
+{
+ call_arg_error ("mknod", name);
+}
+
+void
+open_error (char const *name)
+{
+ call_arg_error ("open", name);
+}
+
+void
+open_fatal (char const *name)
+{
+ call_arg_fatal ("open", name);
+}
+
+void
+open_warn (char const *name)
+{
+ call_arg_warn ("open", name);
+}
+
+void
+read_error (char const *name)
+{
+ call_arg_error ("read", name);
+}
+
+void
+read_error_details (char const *name, off_t offset, size_t size)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ int e = errno;
+ ERROR ((0, e,
+ _("%s: Read error at byte %s, reading %lu bytes"),
+ quotearg_colon (name), STRINGIFY_BIGINT (offset, buf),
+ (unsigned long) size));
+}
+
+void
+read_warn_details (char const *name, off_t offset, size_t size)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ int e = errno;
+ WARN ((0, e,
+ _("%s: Warning: Read error at byte %s, reading %lu bytes"),
+ quotearg_colon (name), STRINGIFY_BIGINT (offset, buf),
+ (unsigned long) size));
+}
+
+void
+read_fatal (char const *name)
+{
+ call_arg_fatal ("read", name);
+}
+
+void
+read_fatal_details (char const *name, off_t offset, size_t size)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ int e = errno;
+ FATAL_ERROR ((0, e,
+ _("%s: Read error at byte %s, reading %lu bytes"),
+ quotearg_colon (name), STRINGIFY_BIGINT (offset, buf),
+ (unsigned long) size));
+}
+
+void
+readlink_error (char const *name)
+{
+ call_arg_error ("readlink", name);
+}
+
+void
+readlink_warn (char const *name)
+{
+ call_arg_warn ("readlink", name);
+}
+
+void
+savedir_error (char const *name)
+{
+ call_arg_error ("savedir", name);
+}
+
+void
+savedir_warn (char const *name)
+{
+ call_arg_warn ("savedir", name);
+}
+
+void
+seek_error (char const *name)
+{
+ call_arg_error ("seek", name);
+}
+
+void
+seek_error_details (char const *name, off_t offset)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot seek to %s"),
+ quotearg_colon (name),
+ STRINGIFY_BIGINT (offset, buf)));
+}
+
+void
+seek_warn (char const *name)
+{
+ call_arg_warn ("seek", name);
+}
+
+void
+seek_warn_details (char const *name, off_t offset)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ int e = errno;
+ WARN ((0, e, _("%s: Warning: Cannot seek to %s"),
+ quotearg_colon (name),
+ STRINGIFY_BIGINT (offset, buf)));
+}
+
+void
+symlink_error (char const *contents, char const *name)
+{
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot create symlink to %s"),
+ quotearg_colon (name), quote_n (1, contents)));
+}
+
+void
+stat_error (char const *name)
+{
+ call_arg_error ("stat", name);
+}
+
+void
+stat_warn (char const *name)
+{
+ call_arg_warn ("stat", name);
+}
+
+void
+truncate_error (char const *name)
+{
+ call_arg_error ("truncate", name);
+}
+
+void
+truncate_warn (char const *name)
+{
+ call_arg_warn ("truncate", name);
+}
+
+void
+unlink_error (char const *name)
+{
+ call_arg_error ("unlink", name);
+}
+
+void
+utime_error (char const *name)
+{
+ call_arg_error ("utime", name);
+}
+
+void
+waitpid_error (char const *name)
+{
+ call_arg_error ("waitpid", name);
+}
+
+void
+write_error (char const *name)
+{
+ call_arg_error ("write", name);
+}
+
+void
+write_error_details (char const *name, ssize_t status, size_t size)
+{
+ if (status < 0)
+ write_error (name);
+ else
+ ERROR ((0, 0, _("%s: Wrote only %lu of %lu bytes"),
+ name, (unsigned long) status, (unsigned long) record_size));
+}
+
+void
+write_fatal (char const *name)
+{
+ call_arg_fatal ("write", name);
+}
+
+void
+write_fatal_details (char const *name, ssize_t status, size_t size)
+{
+ write_error_details (name, status, size);
+ fatal_exit ();
+}
+
+
+/* Fork, aborting if unsuccessful. */
+pid_t
+xfork (void)
+{
+ pid_t p = fork ();
+ if (p == (pid_t) -1)
+ call_arg_fatal ("fork", _("child process"));
+ return p;
+}
+
+/* Create a pipe, aborting if unsuccessful. */
+void
+xpipe (int fd[2])
+{
+ if (pipe (fd) < 0)
+ call_arg_fatal ("pipe", _("interprocess channel"));
+}
+
+/* Return an unambiguous printable representation, allocated in slot N,
+ for NAME, suitable for diagnostics. */
+char const *
+quote_n (int n, char const *name)
+{
+ return quotearg_n_style (n, locale_quoting_style, name);
+}
+
+/* Return an unambiguous printable representation of NAME, suitable
+ for diagnostics. */
+char const *
+quote (char const *name)
+{
+ return quote_n (0, name);
+}
diff --git a/contrib/tar/src/names.c b/contrib/tar/src/names.c
new file mode 100644
index 0000000..eb17636
--- /dev/null
+++ b/contrib/tar/src/names.c
@@ -0,0 +1,941 @@
+/* Various processing of names.
+
+ Copyright 1988, 1992, 1994, 1996, 1997, 1998, 1999, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+
+#include <fnmatch.h>
+#include <grp.h>
+#include <hash.h>
+#include <pwd.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+/* User and group names. */
+
+struct group *getgrnam ();
+struct passwd *getpwnam ();
+#if ! HAVE_DECL_GETPWUID
+struct passwd *getpwuid ();
+#endif
+#if ! HAVE_DECL_GETGRGID
+struct group *getgrgid ();
+#endif
+
+/* Make sure you link with the proper libraries if you are running the
+ Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS.
+ This code should also be modified for non-UNIX systems to do something
+ reasonable. */
+
+static char cached_uname[UNAME_FIELD_SIZE];
+static char cached_gname[GNAME_FIELD_SIZE];
+
+static uid_t cached_uid; /* valid only if cached_uname is not empty */
+static gid_t cached_gid; /* valid only if cached_gname is not empty */
+
+/* These variables are valid only if nonempty. */
+static char cached_no_such_uname[UNAME_FIELD_SIZE];
+static char cached_no_such_gname[GNAME_FIELD_SIZE];
+
+/* These variables are valid only if nonzero. It's not worth optimizing
+ the case for weird systems where 0 is not a valid uid or gid. */
+static uid_t cached_no_such_uid;
+static gid_t cached_no_such_gid;
+
+/* Given UID, find the corresponding UNAME. */
+void
+uid_to_uname (uid_t uid, char uname[UNAME_FIELD_SIZE])
+{
+ struct passwd *passwd;
+
+ if (uid != 0 && uid == cached_no_such_uid)
+ {
+ *uname = '\0';
+ return;
+ }
+
+ if (!cached_uname[0] || uid != cached_uid)
+ {
+ passwd = getpwuid (uid);
+ if (passwd)
+ {
+ cached_uid = uid;
+ strncpy (cached_uname, passwd->pw_name, UNAME_FIELD_SIZE);
+ }
+ else
+ {
+ cached_no_such_uid = uid;
+ *uname = '\0';
+ return;
+ }
+ }
+ strncpy (uname, cached_uname, UNAME_FIELD_SIZE);
+}
+
+/* Given GID, find the corresponding GNAME. */
+void
+gid_to_gname (gid_t gid, char gname[GNAME_FIELD_SIZE])
+{
+ struct group *group;
+
+ if (gid != 0 && gid == cached_no_such_gid)
+ {
+ *gname = '\0';
+ return;
+ }
+
+ if (!cached_gname[0] || gid != cached_gid)
+ {
+ group = getgrgid (gid);
+ if (group)
+ {
+ cached_gid = gid;
+ strncpy (cached_gname, group->gr_name, GNAME_FIELD_SIZE);
+ }
+ else
+ {
+ cached_no_such_gid = gid;
+ *gname = '\0';
+ return;
+ }
+ }
+ strncpy (gname, cached_gname, GNAME_FIELD_SIZE);
+}
+
+/* Given UNAME, set the corresponding UID and return 1, or else, return 0. */
+int
+uname_to_uid (char uname[UNAME_FIELD_SIZE], uid_t *uidp)
+{
+ struct passwd *passwd;
+
+ if (cached_no_such_uname[0]
+ && strncmp (uname, cached_no_such_uname, UNAME_FIELD_SIZE) == 0)
+ return 0;
+
+ if (!cached_uname[0]
+ || uname[0] != cached_uname[0]
+ || strncmp (uname, cached_uname, UNAME_FIELD_SIZE) != 0)
+ {
+ passwd = getpwnam (uname);
+ if (passwd)
+ {
+ cached_uid = passwd->pw_uid;
+ strncpy (cached_uname, uname, UNAME_FIELD_SIZE);
+ }
+ else
+ {
+ strncpy (cached_no_such_uname, uname, UNAME_FIELD_SIZE);
+ return 0;
+ }
+ }
+ *uidp = cached_uid;
+ return 1;
+}
+
+/* Given GNAME, set the corresponding GID and return 1, or else, return 0. */
+int
+gname_to_gid (char gname[GNAME_FIELD_SIZE], gid_t *gidp)
+{
+ struct group *group;
+
+ if (cached_no_such_gname[0]
+ && strncmp (gname, cached_no_such_gname, GNAME_FIELD_SIZE) == 0)
+ return 0;
+
+ if (!cached_gname[0]
+ || gname[0] != cached_gname[0]
+ || strncmp (gname, cached_gname, GNAME_FIELD_SIZE) != 0)
+ {
+ group = getgrnam (gname);
+ if (group)
+ {
+ cached_gid = group->gr_gid;
+ strncpy (cached_gname, gname, GNAME_FIELD_SIZE);
+ }
+ else
+ {
+ strncpy (cached_no_such_gname, gname, GNAME_FIELD_SIZE);
+ return 0;
+ }
+ }
+ *gidp = cached_gid;
+ return 1;
+}
+
+/* Names from the command call. */
+
+static struct name *namelist; /* first name in list, if any */
+static struct name **nametail = &namelist; /* end of name list */
+static const char **name_array; /* store an array of names */
+static int allocated_names; /* how big is the array? */
+static int names; /* how many entries does it have? */
+static int name_index; /* how many of the entries have we scanned? */
+
+/* Initialize structures. */
+void
+init_names (void)
+{
+ allocated_names = 10;
+ name_array = xmalloc (sizeof (const char *) * allocated_names);
+ names = 0;
+}
+
+/* Add NAME at end of name_array, reallocating it as necessary. */
+void
+name_add (const char *name)
+{
+ if (names == allocated_names)
+ {
+ allocated_names *= 2;
+ name_array =
+ xrealloc (name_array, sizeof (const char *) * allocated_names);
+ }
+ name_array[names++] = name;
+}
+
+/* Names from external name file. */
+
+static FILE *name_file; /* file to read names from */
+static char *name_buffer; /* buffer to hold the current file name */
+static size_t name_buffer_length; /* allocated length of name_buffer */
+
+/* FIXME: I should better check more closely. It seems at first glance that
+ is_pattern is only used when reading a file, and ignored for all
+ command line arguments. */
+
+static inline int
+is_pattern (const char *string)
+{
+ return strchr (string, '*') || strchr (string, '[') || strchr (string, '?');
+}
+
+/* Set up to gather file names for tar. They can either come from a
+ file or were saved from decoding arguments. */
+void
+name_init (int argc, char *const *argv)
+{
+ name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
+ name_buffer_length = NAME_FIELD_SIZE;
+
+ if (files_from_option)
+ {
+ if (!strcmp (files_from_option, "-"))
+ {
+ request_stdin ("-T");
+ name_file = stdin;
+ }
+ else if (name_file = fopen (files_from_option, "r"), !name_file)
+ open_fatal (files_from_option);
+ }
+}
+
+void
+name_term (void)
+{
+ free (name_buffer);
+ free (name_array);
+}
+
+/* Read the next filename from name_file and null-terminate it. Put
+ it into name_buffer, reallocating and adjusting name_buffer_length
+ if necessary. Return 0 at end of file, 1 otherwise. */
+static int
+read_name_from_file (void)
+{
+ int character;
+ size_t counter = 0;
+
+ /* FIXME: getc may be called even if character was EOF the last time here. */
+
+ /* FIXME: This + 2 allocation might serve no purpose. */
+
+ while (character = getc (name_file),
+ character != EOF && character != filename_terminator)
+ {
+ if (counter == name_buffer_length)
+ {
+ if (name_buffer_length * 2 < name_buffer_length)
+ xalloc_die ();
+ name_buffer_length *= 2;
+ name_buffer = xrealloc (name_buffer, name_buffer_length + 2);
+ }
+ name_buffer[counter++] = character;
+ }
+
+ if (counter == 0 && character == EOF)
+ return 0;
+
+ if (counter == name_buffer_length)
+ {
+ if (name_buffer_length * 2 < name_buffer_length)
+ xalloc_die ();
+ name_buffer_length *= 2;
+ name_buffer = xrealloc (name_buffer, name_buffer_length + 2);
+ }
+ name_buffer[counter] = '\0';
+
+ return 1;
+}
+
+/* Get the next name from ARGV or the file of names. Result is in
+ static storage and can't be relied upon across two calls.
+
+ If CHANGE_DIRS is true, treat a filename of the form "-C" as
+ meaning that the next filename is the name of a directory to change
+ to. If filename_terminator is NUL, CHANGE_DIRS is effectively
+ always false. */
+char *
+name_next (int change_dirs)
+{
+ const char *source;
+ char *cursor;
+ int chdir_flag = 0;
+
+ if (filename_terminator == '\0')
+ change_dirs = 0;
+
+ while (1)
+ {
+ /* Get a name, either from file or from saved arguments. */
+
+ if (name_index == names)
+ {
+ if (! name_file)
+ break;
+ if (! read_name_from_file ())
+ break;
+ }
+ else
+ {
+ size_t source_len;
+ source = name_array[name_index++];
+ source_len = strlen (source);
+ if (name_buffer_length < source_len)
+ {
+ do
+ {
+ name_buffer_length *= 2;
+ if (! name_buffer_length)
+ xalloc_die ();
+ }
+ while (name_buffer_length < source_len);
+
+ free (name_buffer);
+ name_buffer = xmalloc (name_buffer_length + 2);
+ }
+ strcpy (name_buffer, source);
+ }
+
+ /* Zap trailing slashes. */
+
+ cursor = name_buffer + strlen (name_buffer) - 1;
+ while (cursor > name_buffer && ISSLASH (*cursor))
+ *cursor-- = '\0';
+
+ if (chdir_flag)
+ {
+ if (chdir (name_buffer) < 0)
+ chdir_fatal (name_buffer);
+ chdir_flag = 0;
+ }
+ else if (change_dirs && strcmp (name_buffer, "-C") == 0)
+ chdir_flag = 1;
+ else
+ {
+ unquote_string (name_buffer);
+ return name_buffer;
+ }
+ }
+
+ /* No more names in file. */
+
+ if (name_file && chdir_flag)
+ FATAL_ERROR ((0, 0, _("Missing file name after -C")));
+
+ return 0;
+}
+
+/* Close the name file, if any. */
+void
+name_close (void)
+{
+ if (name_file && name_file != stdin)
+ if (fclose (name_file) != 0)
+ close_error (name_buffer);
+}
+
+/* Gather names in a list for scanning. Could hash them later if we
+ really care.
+
+ If the names are already sorted to match the archive, we just read
+ them one by one. name_gather reads the first one, and it is called
+ by name_match as appropriate to read the next ones. At EOF, the
+ last name read is just left in the buffer. This option lets users
+ of small machines extract an arbitrary number of files by doing
+ "tar t" and editing down the list of files. */
+
+void
+name_gather (void)
+{
+ /* Buffer able to hold a single name. */
+ static struct name *buffer;
+ static size_t allocated_size;
+
+ char const *name;
+
+ if (same_order_option)
+ {
+ static int change_dir;
+
+ if (allocated_size == 0)
+ {
+ allocated_size = offsetof (struct name, name) + NAME_FIELD_SIZE + 1;
+ buffer = xmalloc (allocated_size);
+ /* FIXME: This memset is overkill, and ugly... */
+ memset (buffer, 0, allocated_size);
+ }
+
+ while ((name = name_next (0)) && strcmp (name, "-C") == 0)
+ {
+ char const *dir = name_next (0);
+ if (! dir)
+ FATAL_ERROR ((0, 0, _("Missing file name after -C")));
+ change_dir = chdir_arg (xstrdup (dir));
+ }
+
+ if (name)
+ {
+ size_t needed_size;
+ buffer->length = strlen (name);
+ needed_size = offsetof (struct name, name) + buffer->length + 1;
+ if (allocated_size < needed_size)
+ {
+ do
+ {
+ allocated_size *= 2;
+ if (! allocated_size)
+ xalloc_die ();
+ }
+ while (allocated_size < needed_size);
+
+ buffer = xrealloc (buffer, allocated_size);
+ }
+ buffer->change_dir = change_dir;
+ strcpy (buffer->name, name);
+ buffer->next = 0;
+ buffer->found = 0;
+
+ namelist = buffer;
+ nametail = &namelist->next;
+ }
+ }
+ else
+ {
+ /* Non sorted names -- read them all in. */
+ int change_dir = 0;
+
+ for (;;)
+ {
+ int change_dir0 = change_dir;
+ while ((name = name_next (0)) && strcmp (name, "-C") == 0)
+ {
+ char const *dir = name_next (0);
+ if (! dir)
+ FATAL_ERROR ((0, 0, _("Missing file name after -C")));
+ change_dir = chdir_arg (xstrdup (dir));
+ }
+ if (name)
+ addname (name, change_dir);
+ else
+ {
+ if (change_dir != change_dir0)
+ addname (0, change_dir);
+ break;
+ }
+ }
+ }
+}
+
+/* Add a name to the namelist. */
+struct name *
+addname (char const *string, int change_dir)
+{
+ size_t length = string ? strlen (string) : 0;
+ struct name *name = xmalloc (offsetof (struct name, name) + length + 1);
+
+ if (string)
+ {
+ name->fake = 0;
+ strcpy (name->name, string);
+ }
+ else
+ {
+ name->fake = 1;
+
+ /* FIXME: This initialization (and the byte of memory that it
+ initializes) is probably not needed, but we are currently in
+ bug-fix mode so we'll leave it in for now. */
+ name->name[0] = 0;
+ }
+
+ name->next = 0;
+ name->length = length;
+ name->found = 0;
+ name->regexp = 0; /* assume not a regular expression */
+ name->firstch = 1; /* assume first char is literal */
+ name->change_dir = change_dir;
+ name->dir_contents = 0;
+
+ if (string && is_pattern (string))
+ {
+ name->regexp = 1;
+ if (string[0] == '*' || string[0] == '[' || string[0] == '?')
+ name->firstch = 0;
+ }
+
+ *nametail = name;
+ nametail = &name->next;
+ return name;
+}
+
+/* Find a match for PATH (whose string length is LENGTH) in the name
+ list. */
+static struct name *
+namelist_match (char const *path, size_t length)
+{
+ struct name *p;
+
+ for (p = namelist; p; p = p->next)
+ {
+ /* If first chars don't match, quick skip. */
+
+ if (p->firstch && p->name[0] != path[0])
+ continue;
+
+ if (p->regexp
+ ? fnmatch (p->name, path, recursion_option) == 0
+ : (p->length <= length
+ && (path[p->length] == '\0' || ISSLASH (path[p->length]))
+ && memcmp (path, p->name, p->length) == 0))
+ return p;
+ }
+
+ return 0;
+}
+
+/* Return true if and only if name PATH (from an archive) matches any
+ name from the namelist. */
+int
+name_match (const char *path)
+{
+ size_t length = strlen (path);
+
+ while (1)
+ {
+ struct name *cursor = namelist;
+
+ if (!cursor)
+ return ! files_from_option;
+
+ if (cursor->fake)
+ {
+ chdir_do (cursor->change_dir);
+ namelist = 0;
+ nametail = &namelist;
+ return ! files_from_option;
+ }
+
+ cursor = namelist_match (path, length);
+ if (cursor)
+ {
+ cursor->found = 1; /* remember it matched */
+ if (starting_file_option)
+ {
+ free (namelist);
+ namelist = 0;
+ nametail = &namelist;
+ }
+ chdir_do (cursor->change_dir);
+
+ /* We got a match. */
+ return 1;
+ }
+
+ /* Filename from archive not found in namelist. If we have the whole
+ namelist here, just return 0. Otherwise, read the next name in and
+ compare it. If this was the last name, namelist->found will remain
+ on. If not, we loop to compare the newly read name. */
+
+ if (same_order_option && namelist->found)
+ {
+ name_gather (); /* read one more */
+ if (namelist->found)
+ return 0;
+ }
+ else
+ return 0;
+ }
+}
+
+/* Print the names of things in the namelist that were not matched. */
+void
+names_notfound (void)
+{
+ struct name const *cursor;
+
+ for (cursor = namelist; cursor; cursor = cursor->next)
+ if (!cursor->found && !cursor->fake)
+ ERROR ((0, 0, _("%s: Not found in archive"),
+ quotearg_colon (cursor->name)));
+
+ /* Don't bother freeing the name list; we're about to exit. */
+ namelist = 0;
+ nametail = &namelist;
+
+ if (same_order_option)
+ {
+ char *name;
+
+ while (name = name_next (1), name)
+ ERROR ((0, 0, _("%s: Not found in archive"),
+ quotearg_colon (name)));
+ }
+}
+
+/* Sorting name lists. */
+
+/* Sort linked LIST of names, of given LENGTH, using COMPARE to order
+ names. Return the sorted list. Apart from the type `struct name'
+ and the definition of SUCCESSOR, this is a generic list-sorting
+ function, but it's too painful to make it both generic and portable
+ in C. */
+
+static struct name *
+merge_sort (struct name *list, int length,
+ int (*compare) (struct name const*, struct name const*))
+{
+ struct name *first_list;
+ struct name *second_list;
+ int first_length;
+ int second_length;
+ struct name *result;
+ struct name **merge_point;
+ struct name *cursor;
+ int counter;
+
+# define SUCCESSOR(name) ((name)->next)
+
+ if (length == 1)
+ return list;
+
+ if (length == 2)
+ {
+ if ((*compare) (list, SUCCESSOR (list)) > 0)
+ {
+ result = SUCCESSOR (list);
+ SUCCESSOR (result) = list;
+ SUCCESSOR (list) = 0;
+ return result;
+ }
+ return list;
+ }
+
+ first_list = list;
+ first_length = (length + 1) / 2;
+ second_length = length / 2;
+ for (cursor = list, counter = first_length - 1;
+ counter;
+ cursor = SUCCESSOR (cursor), counter--)
+ continue;
+ second_list = SUCCESSOR (cursor);
+ SUCCESSOR (cursor) = 0;
+
+ first_list = merge_sort (first_list, first_length, compare);
+ second_list = merge_sort (second_list, second_length, compare);
+
+ merge_point = &result;
+ while (first_list && second_list)
+ if ((*compare) (first_list, second_list) < 0)
+ {
+ cursor = SUCCESSOR (first_list);
+ *merge_point = first_list;
+ merge_point = &SUCCESSOR (first_list);
+ first_list = cursor;
+ }
+ else
+ {
+ cursor = SUCCESSOR (second_list);
+ *merge_point = second_list;
+ merge_point = &SUCCESSOR (second_list);
+ second_list = cursor;
+ }
+ if (first_list)
+ *merge_point = first_list;
+ else
+ *merge_point = second_list;
+
+ return result;
+
+#undef SUCCESSOR
+}
+
+/* A comparison function for sorting names. Put found names last;
+ break ties by string comparison. */
+
+static int
+compare_names (struct name const *n1, struct name const *n2)
+{
+ int found_diff = n2->found - n1->found;
+ return found_diff ? found_diff : strcmp (n1->name, n2->name);
+}
+
+/* Add all the dirs under NAME, which names a directory, to the namelist.
+ If any of the files is a directory, recurse on the subdirectory.
+ DEVICE is the device not to leave, if the -l option is specified. */
+
+static void
+add_hierarchy_to_namelist (struct name *name, dev_t device)
+{
+ char *path = name->name;
+ char *buffer = get_directory_contents (path, device);
+
+ if (! buffer)
+ name->dir_contents = "\0\0\0\0";
+ else
+ {
+ size_t name_length = name->length;
+ size_t allocated_length = (name_length >= NAME_FIELD_SIZE
+ ? name_length + NAME_FIELD_SIZE
+ : NAME_FIELD_SIZE);
+ char *name_buffer = xmalloc (allocated_length + 1);
+ /* FIXME: + 2 above? */
+ char *string;
+ size_t string_length;
+ int change_dir = name->change_dir;
+
+ name->dir_contents = buffer;
+ strcpy (name_buffer, path);
+ if (! ISSLASH (name_buffer[name_length - 1]))
+ {
+ name_buffer[name_length++] = '/';
+ name_buffer[name_length] = '\0';
+ }
+
+ for (string = buffer; *string; string += string_length + 1)
+ {
+ string_length = strlen (string);
+ if (*string == 'D')
+ {
+ if (allocated_length <= name_length + string_length)
+ {
+ do
+ {
+ allocated_length *= 2;
+ if (! allocated_length)
+ xalloc_die ();
+ }
+ while (allocated_length <= name_length + string_length);
+
+ name_buffer = xrealloc (name_buffer, allocated_length + 1);
+ }
+ strcpy (name_buffer + name_length, string + 1);
+ add_hierarchy_to_namelist (addname (name_buffer, change_dir),
+ device);
+ }
+ }
+
+ free (name_buffer);
+ }
+}
+
+/* Collect all the names from argv[] (or whatever), expand them into a
+ directory tree, and sort them. This gets only subdirectories, not
+ all files. */
+
+void
+collect_and_sort_names (void)
+{
+ struct name *name;
+ struct name *next_name;
+ int num_names;
+ struct stat statbuf;
+
+ name_gather ();
+
+ if (listed_incremental_option)
+ read_directory_file ();
+
+ if (!namelist)
+ addname (".", 0);
+
+ for (name = namelist; name; name = next_name)
+ {
+ next_name = name->next;
+ if (name->found || name->dir_contents)
+ continue;
+ if (name->regexp) /* FIXME: just skip regexps for now */
+ continue;
+ chdir_do (name->change_dir);
+ if (name->fake)
+ continue;
+
+ if (deref_stat (dereference_option, name->name, &statbuf) != 0)
+ {
+ if (ignore_failed_read_option)
+ stat_warn (name->name);
+ else
+ stat_error (name->name);
+ continue;
+ }
+ if (S_ISDIR (statbuf.st_mode))
+ {
+ name->found = 1;
+ add_hierarchy_to_namelist (name, statbuf.st_dev);
+ }
+ }
+
+ num_names = 0;
+ for (name = namelist; name; name = name->next)
+ num_names++;
+ namelist = merge_sort (namelist, num_names, compare_names);
+
+ for (name = namelist; name; name = name->next)
+ name->found = 0;
+}
+
+/* This is like name_match, except that it returns a pointer to the
+ name it matched, and doesn't set FOUND in structure. The caller
+ will have to do that if it wants to. Oh, and if the namelist is
+ empty, it returns null, unlike name_match, which returns TRUE. */
+struct name *
+name_scan (const char *path)
+{
+ size_t length = strlen (path);
+
+ while (1)
+ {
+ struct name *cursor = namelist_match (path, length);
+ if (cursor)
+ return cursor;
+
+ /* Filename from archive not found in namelist. If we have the whole
+ namelist here, just return 0. Otherwise, read the next name in and
+ compare it. If this was the last name, namelist->found will remain
+ on. If not, we loop to compare the newly read name. */
+
+ if (same_order_option && namelist && namelist->found)
+ {
+ name_gather (); /* read one more */
+ if (namelist->found)
+ return 0;
+ }
+ else
+ return 0;
+ }
+}
+
+/* This returns a name from the namelist which doesn't have ->found
+ set. It sets ->found before returning, so successive calls will
+ find and return all the non-found names in the namelist. */
+struct name *gnu_list_name;
+
+char *
+name_from_list (void)
+{
+ if (!gnu_list_name)
+ gnu_list_name = namelist;
+ while (gnu_list_name && (gnu_list_name->found | gnu_list_name->fake))
+ gnu_list_name = gnu_list_name->next;
+ if (gnu_list_name)
+ {
+ gnu_list_name->found = 1;
+ chdir_do (gnu_list_name->change_dir);
+ return gnu_list_name->name;
+ }
+ return 0;
+}
+
+void
+blank_name_list (void)
+{
+ struct name *name;
+
+ gnu_list_name = 0;
+ for (name = namelist; name; name = name->next)
+ name->found = 0;
+}
+
+/* Yield a newly allocated file name consisting of PATH concatenated to
+ NAME, with an intervening slash if PATH does not already end in one. */
+char *
+new_name (const char *path, const char *name)
+{
+ size_t pathlen = strlen (path);
+ size_t namesize = strlen (name) + 1;
+ int slash = pathlen && ! ISSLASH (path[pathlen - 1]);
+ char *buffer = xmalloc (pathlen + slash + namesize);
+ memcpy (buffer, path, pathlen);
+ buffer[pathlen] = '/';
+ memcpy (buffer + pathlen + slash, name, namesize);
+ return buffer;
+}
+
+/* Return nonzero if file NAME is excluded. Exclude a name if its
+ prefix matches a pattern that contains slashes, or if one of its
+ components matches a pattern that contains no slashes. */
+bool
+excluded_name (char const *name)
+{
+ return excluded_filename (excluded, name + FILESYSTEM_PREFIX_LEN (name));
+}
+
+/* Names to avoid dumping. */
+static Hash_table *avoided_name_table;
+
+/* Calculate the hash of an avoided name. */
+static unsigned
+hash_avoided_name (void const *name, unsigned n_buckets)
+{
+ return hash_string (name, n_buckets);
+}
+
+/* Compare two avoided names for equality. */
+static bool
+compare_avoided_names (void const *name1, void const *name2)
+{
+ return strcmp (name1, name2) == 0;
+}
+
+/* Remember to not archive NAME. */
+void
+add_avoided_name (char const *name)
+{
+ if (! ((avoided_name_table
+ || (avoided_name_table = hash_initialize (0, 0, hash_avoided_name,
+ compare_avoided_names, 0)))
+ && hash_insert (avoided_name_table, xstrdup (name))))
+ xalloc_die ();
+}
+
+/* Should NAME be avoided when archiving? */
+int
+is_avoided_name (char const *name)
+{
+ return avoided_name_table && hash_lookup (avoided_name_table, name);
+}
diff --git a/contrib/tar/src/rmt.c b/contrib/tar/src/rmt.c
new file mode 100644
index 0000000..0fe166b
--- /dev/null
+++ b/contrib/tar/src/rmt.c
@@ -0,0 +1,576 @@
+/* Remote connection server.
+
+ Copyright (C) 1994, 1995, 1996, 1997, 1999, 2000, 2001 Free Software
+ Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* Copyright (C) 1983 Regents of the University of California.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms are permitted provided
+ that the above copyright notice and this paragraph are duplicated in all
+ such forms and that any documentation, advertising materials, and other
+ materials related to such distribution and use acknowledge that the
+ software was developed by the University of California, Berkeley. The
+ name of the University may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */
+
+#include "system.h"
+#include <print-copyr.h>
+#include <localedir.h>
+#include <safe-read.h>
+#include <full-write.h>
+
+#include <getopt.h>
+#include <sys/socket.h>
+
+#ifndef EXIT_FAILURE
+# define EXIT_FAILURE 1
+#endif
+#ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+#endif
+
+/* Maximum size of a string from the requesting program. */
+#define STRING_SIZE 64
+
+/* Name of executing program. */
+const char *program_name;
+
+/* File descriptor of the tape device, or negative if none open. */
+static int tape = -1;
+
+/* Buffer containing transferred data, and its allocated size. */
+static char *record_buffer;
+static size_t allocated_size;
+
+/* Buffer for constructing the reply. */
+static char reply_buffer[BUFSIZ];
+
+/* Debugging tools. */
+
+static FILE *debug_file;
+
+#define DEBUG(File) \
+ if (debug_file) fprintf(debug_file, File)
+
+#define DEBUG1(File, Arg) \
+ if (debug_file) fprintf(debug_file, File, Arg)
+
+#define DEBUG2(File, Arg1, Arg2) \
+ if (debug_file) fprintf(debug_file, File, Arg1, Arg2)
+
+/* Return an error string, given an error number. */
+#if HAVE_STRERROR
+# ifndef strerror
+char *strerror ();
+# endif
+#else
+static char *
+private_strerror (int errnum)
+{
+ extern char *sys_errlist[];
+ extern int sys_nerr;
+
+ if (errnum > 0 && errnum <= sys_nerr)
+ return _(sys_errlist[errnum]);
+ return _("Unknown system error");
+}
+# define strerror private_strerror
+#endif
+
+static void
+report_error_message (const char *string)
+{
+ DEBUG1 ("rmtd: E 0 (%s)\n", string);
+
+ sprintf (reply_buffer, "E0\n%s\n", string);
+ full_write (STDOUT_FILENO, reply_buffer, strlen (reply_buffer));
+}
+
+static void
+report_numbered_error (int num)
+{
+ DEBUG2 ("rmtd: E %d (%s)\n", num, strerror (num));
+
+ sprintf (reply_buffer, "E%d\n%s\n", num, strerror (num));
+ full_write (STDOUT_FILENO, reply_buffer, strlen (reply_buffer));
+}
+
+static void
+get_string (char *string)
+{
+ int counter;
+
+ for (counter = 0; counter < STRING_SIZE; counter++)
+ {
+ if (safe_read (STDIN_FILENO, string + counter, 1) != 1)
+ exit (EXIT_SUCCESS);
+
+ if (string[counter] == '\n')
+ break;
+ }
+ string[counter] = '\0';
+}
+
+static void
+prepare_record_buffer (size_t size)
+{
+ if (size <= allocated_size)
+ return;
+
+ if (record_buffer)
+ free (record_buffer);
+
+ record_buffer = malloc (size);
+
+ if (! record_buffer)
+ {
+ DEBUG (_("rmtd: Cannot allocate buffer space\n"));
+
+ report_error_message (N_("Cannot allocate buffer space"));
+ exit (EXIT_FAILURE); /* exit status used to be 4 */
+ }
+
+ allocated_size = size;
+
+#ifdef SO_RCVBUF
+ while (size > 1024 &&
+ (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_RCVBUF,
+ (char *) &size, sizeof size)
+ < 0))
+ size -= 1024;
+#else
+ /* FIXME: I do not see any purpose to the following line... Sigh! */
+ size = 1 + ((size - 1) % 1024);
+#endif
+}
+
+/* Decode OFLAG_STRING, which represents the 2nd argument to `open'.
+ OFLAG_STRING should contain an optional integer, followed by an optional
+ symbolic representation of an open flag using only '|' to separate its
+ components (e.g. "O_WRONLY|O_CREAT|O_TRUNC"). Prefer the symbolic
+ representation if available, falling back on the numeric
+ representation, or to zero if both formats are absent.
+
+ This function should be the inverse of encode_oflag. The numeric
+ representation is not portable from one host to another, but it is
+ for backward compatibility with old-fashioned clients that do not
+ emit symbolic open flags. */
+
+static int
+decode_oflag (char const *oflag_string)
+{
+ char *oflag_num_end;
+ int numeric_oflag = strtol (oflag_string, &oflag_num_end, 10);
+ int symbolic_oflag = 0;
+
+ oflag_string = oflag_num_end;
+ while (ISSPACE ((unsigned char) *oflag_string))
+ oflag_string++;
+
+ do
+ {
+ struct name_value_pair { char const *name; int value; };
+ static struct name_value_pair const table[] =
+ {
+#ifdef O_APPEND
+ {"APPEND", O_APPEND},
+#endif
+ {"CREAT", O_CREAT},
+#ifdef O_DSYNC
+ {"DSYNC", O_DSYNC},
+#endif
+ {"EXCL", O_EXCL},
+#ifdef O_LARGEFILE
+ {"LARGEFILE", O_LARGEFILE}, /* LFS extension for opening large files */
+#endif
+#ifdef O_NOCTTY
+ {"NOCTTY", O_NOCTTY},
+#endif
+#ifdef O_NONBLOCK
+ {"NONBLOCK", O_NONBLOCK},
+#endif
+ {"RDONLY", O_RDONLY},
+ {"RDWR", O_RDWR},
+#ifdef O_RSYNC
+ {"RSYNC", O_RSYNC},
+#endif
+#ifdef O_SYNC
+ {"SYNC", O_SYNC},
+#endif
+ {"TRUNC", O_TRUNC},
+ {"WRONLY", O_WRONLY}
+ };
+ struct name_value_pair const *t;
+ size_t s;
+
+ if (*oflag_string++ != 'O' || *oflag_string++ != '_')
+ return numeric_oflag;
+
+ for (t = table;
+ (strncmp (oflag_string, t->name, s = strlen (t->name)) != 0
+ || (oflag_string[s]
+ && strchr ("ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789",
+ oflag_string[s])));
+ t++)
+ if (t == table + sizeof table / sizeof *table - 1)
+ return numeric_oflag;
+
+ symbolic_oflag |= t->value;
+ oflag_string += s;
+ }
+ while (*oflag_string++ == '|');
+
+ return symbolic_oflag;
+}
+
+static struct option const long_opts[] =
+{
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {0, 0, 0, 0}
+};
+
+static void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ printf (_("\
+Usage: %s [OPTION]\n\
+Manipulate a tape drive, accepting commands from a remote process.\n\
+\n\
+ --version Output version info.\n\
+ --help Output this help.\n"),
+ program_name);
+ fputs (_("\nReport bugs to <bug-tar@gnu.org>.\n"), stdout);
+ }
+
+ exit (status);
+}
+
+int
+main (int argc, char *const *argv)
+{
+ char command;
+ ssize_t status;
+
+ /* FIXME: Localization is meaningless, unless --help and --version are
+ locally used. Localization would be best accomplished by the calling
+ tar, on messages found within error packets. */
+
+ program_name = argv[0];
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ switch (getopt_long (argc, argv, "", long_opts, NULL))
+ {
+ default:
+ usage (EXIT_FAILURE);
+
+ case 'h':
+ usage (EXIT_SUCCESS);
+
+ case 'v':
+ {
+ printf ("rmt (GNU %s) %s\n", PACKAGE, VERSION);
+ print_copyright ("2001 Free Software Foundation, Inc.");
+ puts (_("\
+This program comes with NO WARRANTY, to the extent permitted by law.\n\
+You may redistribute it under the terms of the GNU General Public License;\n\
+see the file named COPYING for details."));
+ }
+ return EXIT_SUCCESS;
+
+ case -1:
+ break;
+ }
+
+ if (optind < argc)
+ {
+ if (optind != argc - 1)
+ usage (EXIT_FAILURE);
+ debug_file = fopen (argv[optind], "w");
+ if (debug_file == 0)
+ {
+ report_numbered_error (errno);
+ exit (EXIT_FAILURE);
+ }
+ setbuf (debug_file, 0);
+ }
+
+top:
+ errno = 0;
+ status = 0;
+ if (safe_read (STDIN_FILENO, &command, 1) != 1)
+ return EXIT_SUCCESS;
+
+ switch (command)
+ {
+ /* FIXME: Maybe 'H' and 'V' for --help and --version output? */
+
+ case 'O':
+ {
+ char device_string[STRING_SIZE];
+ char oflag_string[STRING_SIZE];
+
+ get_string (device_string);
+ get_string (oflag_string);
+ DEBUG2 ("rmtd: O %s %s\n", device_string, oflag_string);
+
+ if (tape >= 0)
+ close (tape);
+
+ tape = open (device_string, decode_oflag (oflag_string), MODE_RW);
+ if (tape < 0)
+ goto ioerror;
+ goto respond;
+ }
+
+ case 'C':
+ {
+ char device_string[STRING_SIZE];
+
+ get_string (device_string); /* discard */
+ DEBUG ("rmtd: C\n");
+
+ if (close (tape) < 0)
+ goto ioerror;
+ tape = -1;
+ goto respond;
+ }
+
+ case 'L':
+ {
+ char count_string[STRING_SIZE];
+ char position_string[STRING_SIZE];
+ off_t count = 0;
+ int negative;
+ int whence;
+ char *p;
+
+ get_string (count_string);
+ get_string (position_string);
+ DEBUG2 ("rmtd: L %s %s\n", count_string, position_string);
+
+ /* Parse count_string, taking care to check for overflow.
+ We can't use standard functions,
+ since off_t might be longer than long. */
+
+ for (p = count_string; *p == ' ' || *p == '\t'; p++)
+ continue;
+
+ negative = *p == '-';
+ p += negative || *p == '+';
+
+ for (;;)
+ {
+ int digit = *p++ - '0';
+ if (9 < (unsigned) digit)
+ break;
+ else
+ {
+ off_t c10 = 10 * count;
+ off_t nc = negative ? c10 - digit : c10 + digit;
+ if (c10 / 10 != count || (negative ? c10 < nc : nc < c10))
+ {
+ report_error_message (N_("Seek offset out of range"));
+ exit (EXIT_FAILURE);
+ }
+ count = nc;
+ }
+ }
+
+ switch (atoi (position_string))
+ {
+ case 0: whence = SEEK_SET; break;
+ case 1: whence = SEEK_CUR; break;
+ case 2: whence = SEEK_END; break;
+ default:
+ report_error_message (N_("Seek direction out of range"));
+ exit (EXIT_FAILURE);
+ }
+ count = lseek (tape, count, whence);
+ if (count < 0)
+ goto ioerror;
+
+ /* Convert count back to string for reply.
+ We can't use sprintf, since off_t might be longer than long. */
+ p = count_string + sizeof count_string;
+ *--p = '\0';
+ do
+ *--p = '0' + (int) (count % 10);
+ while ((count /= 10) != 0);
+
+ DEBUG1 ("rmtd: A %s\n", p);
+
+ sprintf (reply_buffer, "A%s\n", p);
+ full_write (STDOUT_FILENO, reply_buffer, strlen (reply_buffer));
+ goto top;
+ }
+
+ case 'W':
+ {
+ char count_string[STRING_SIZE];
+ size_t size;
+ size_t counter;
+
+ get_string (count_string);
+ size = atol (count_string);
+ DEBUG1 ("rmtd: W %s\n", count_string);
+
+ prepare_record_buffer (size);
+ for (counter = 0; counter < size; counter += status)
+ {
+ status = safe_read (STDIN_FILENO, &record_buffer[counter],
+ size - counter);
+ if (status <= 0)
+ {
+ DEBUG (_("rmtd: Premature eof\n"));
+
+ report_error_message (N_("Premature end of file"));
+ exit (EXIT_FAILURE); /* exit status used to be 2 */
+ }
+ }
+ status = full_write (tape, record_buffer, size);
+ if (status < 0)
+ goto ioerror;
+ goto respond;
+ }
+
+ case 'R':
+ {
+ char count_string[STRING_SIZE];
+ size_t size;
+
+ get_string (count_string);
+ DEBUG1 ("rmtd: R %s\n", count_string);
+
+ size = atol (count_string);
+ prepare_record_buffer (size);
+ status = safe_read (tape, record_buffer, size);
+ if (status < 0)
+ goto ioerror;
+ sprintf (reply_buffer, "A%ld\n", (long) status);
+ full_write (STDOUT_FILENO, reply_buffer, strlen (reply_buffer));
+ full_write (STDOUT_FILENO, record_buffer, status);
+ goto top;
+ }
+
+ case 'I':
+ {
+ char operation_string[STRING_SIZE];
+ char count_string[STRING_SIZE];
+
+ get_string (operation_string);
+ get_string (count_string);
+ DEBUG2 ("rmtd: I %s %s\n", operation_string, count_string);
+
+#ifdef MTIOCTOP
+ {
+ struct mtop mtop;
+ const char *p;
+ off_t count = 0;
+ int negative;
+
+ /* Parse count_string, taking care to check for overflow.
+ We can't use standard functions,
+ since off_t might be longer than long. */
+
+ for (p = count_string; *p == ' ' || *p == '\t'; p++)
+ continue;
+
+ negative = *p == '-';
+ p += negative || *p == '+';
+
+ for (;;)
+ {
+ int digit = *p++ - '0';
+ if (9 < (unsigned) digit)
+ break;
+ else
+ {
+ off_t c10 = 10 * count;
+ off_t nc = negative ? c10 - digit : c10 + digit;
+ if (c10 / 10 != count || (negative ? c10 < nc : nc < c10))
+ {
+ report_error_message (N_("Seek offset out of range"));
+ exit (EXIT_FAILURE);
+ }
+ count = nc;
+ }
+ }
+
+ mtop.mt_count = count;
+ if (mtop.mt_count != count)
+ {
+ report_error_message (N_("Seek offset out of range"));
+ exit (EXIT_FAILURE);
+ }
+ mtop.mt_op = atoi (operation_string);
+
+ if (ioctl (tape, MTIOCTOP, (char *) &mtop) < 0)
+ goto ioerror;
+ }
+#endif
+ goto respond;
+ }
+
+ case 'S': /* status */
+ {
+ DEBUG ("rmtd: S\n");
+
+#ifdef MTIOCGET
+ {
+ struct mtget operation;
+
+ if (ioctl (tape, MTIOCGET, (char *) &operation) < 0)
+ goto ioerror;
+ status = sizeof operation;
+ sprintf (reply_buffer, "A%ld\n", (long) status);
+ full_write (STDOUT_FILENO, reply_buffer, strlen (reply_buffer));
+ full_write (STDOUT_FILENO, (char *) &operation, sizeof operation);
+ }
+#endif
+ goto top;
+ }
+
+ default:
+ DEBUG1 (_("rmtd: Garbage command %c\n"), command);
+
+ report_error_message (N_("Garbage command"));
+ exit (EXIT_FAILURE); /* exit status used to be 3 */
+ }
+
+respond:
+ DEBUG1 ("rmtd: A %ld\n", (long) status);
+
+ sprintf (reply_buffer, "A%ld\n", (long) status);
+ full_write (STDOUT_FILENO, reply_buffer, strlen (reply_buffer));
+ goto top;
+
+ioerror:
+ report_numbered_error (errno);
+ goto top;
+}
diff --git a/contrib/tar/src/rmt.h b/contrib/tar/src/rmt.h
new file mode 100644
index 0000000..b851045
--- /dev/null
+++ b/contrib/tar/src/rmt.h
@@ -0,0 +1,93 @@
+/* Definitions for communicating with a remote tape drive.
+ Copyright 1988, 1992, 1996, 1997, 2001 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+extern char *rmt_path__;
+
+int rmt_open__ PARAMS ((const char *, int, int, const char *));
+int rmt_close__ PARAMS ((int));
+ssize_t rmt_read__ PARAMS ((int, char *, size_t));
+ssize_t rmt_write__ PARAMS ((int, char *, size_t));
+off_t rmt_lseek__ PARAMS ((int, off_t, int));
+int rmt_ioctl__ PARAMS ((int, int, char *));
+
+/* A filename is remote if it contains a colon not preceded by a slash,
+ to take care of `/:/' which is a shorthand for `/.../<CELL-NAME>/fs'
+ on machines running OSF's Distributing Computing Environment (DCE) and
+ Distributed File System (DFS). However, when --force-local, a
+ filename is never remote. */
+
+#define _remdev(Path) \
+ (!force_local_option && (rmt_path__ = strchr (Path, ':')) \
+ && rmt_path__ > (Path) && ! memchr (Path, rmt_path__ - (Path), '/'))
+
+#define _isrmt(Fd) \
+ ((Fd) >= __REM_BIAS)
+
+#define __REM_BIAS (1 << 30)
+
+#ifndef O_CREAT
+# define O_CREAT 01000
+#endif
+
+#define rmtopen(Path, Oflag, Mode, Command) \
+ (_remdev (Path) ? rmt_open__ (Path, Oflag, __REM_BIAS, Command) \
+ : open (Path, Oflag, Mode))
+
+#define rmtaccess(Path, Amode) \
+ (_remdev (Path) ? 0 : access (Path, Amode))
+
+#define rmtstat(Path, Buffer) \
+ (_remdev (Path) ? (errno = EOPNOTSUPP), -1 : stat (Path, Buffer))
+
+#define rmtcreat(Path, Mode, Command) \
+ (_remdev (Path) \
+ ? rmt_open__ (Path, 1 | O_CREAT, __REM_BIAS, Command) \
+ : creat (Path, Mode))
+
+#define rmtlstat(Path, Buffer) \
+ (_remdev (Path) ? (errno = EOPNOTSUPP), -1 : lstat (Path, Buffer))
+
+#define rmtread(Fd, Buffer, Length) \
+ (_isrmt (Fd) ? rmt_read__ (Fd - __REM_BIAS, Buffer, Length) \
+ : safe_read (Fd, Buffer, Length))
+
+#define rmtwrite(Fd, Buffer, Length) \
+ (_isrmt (Fd) ? rmt_write__ (Fd - __REM_BIAS, Buffer, Length) \
+ : full_write (Fd, Buffer, Length))
+
+#define rmtlseek(Fd, Offset, Where) \
+ (_isrmt (Fd) ? rmt_lseek__ (Fd - __REM_BIAS, Offset, Where) \
+ : lseek (Fd, Offset, Where))
+
+#define rmtclose(Fd) \
+ (_isrmt (Fd) ? rmt_close__ (Fd - __REM_BIAS) : close (Fd))
+
+#define rmtioctl(Fd, Request, Argument) \
+ (_isrmt (Fd) ? rmt_ioctl__ (Fd - __REM_BIAS, Request, Argument) \
+ : ioctl (Fd, Request, Argument))
+
+#define rmtdup(Fd) \
+ (_isrmt (Fd) ? (errno = EOPNOTSUPP), -1 : dup (Fd))
+
+#define rmtfstat(Fd, Buffer) \
+ (_isrmt (Fd) ? (errno = EOPNOTSUPP), -1 : fstat (Fd, Buffer))
+
+#define rmtfcntl(Fd, Command, Argument) \
+ (_isrmt (Fd) ? (errno = EOPNOTSUPP), -1 : fcntl (Fd, Command, Argument))
+
+#define rmtisatty(Fd) \
+ (_isrmt (Fd) ? 0 : isatty (Fd))
diff --git a/contrib/tar/src/rtapelib.c b/contrib/tar/src/rtapelib.c
new file mode 100644
index 0000000..ce141b4
--- /dev/null
+++ b/contrib/tar/src/rtapelib.c
@@ -0,0 +1,718 @@
+/* Functions for communicating with a remote tape drive.
+
+ Copyright 1988, 1992, 1994, 1996, 1997, 1999, 2000, 2001 Free Software
+ Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* The man page rmt(8) for /etc/rmt documents the remote mag tape protocol
+ which rdump and rrestore use. Unfortunately, the man page is *WRONG*.
+ The author of the routines I'm including originally wrote his code just
+ based on the man page, and it didn't work, so he went to the rdump source
+ to figure out why. The only thing he had to change was to check for the
+ 'F' return code in addition to the 'E', and to separate the various
+ arguments with \n instead of a space. I personally don't think that this
+ is much of a problem, but I wanted to point it out. -- Arnold Robbins
+
+ Originally written by Jeff Lee, modified some by Arnold Robbins. Redone
+ as a library that can replace open, read, write, etc., by Fred Fish, with
+ some additional work by Arnold Robbins. Modified to make all rmt* calls
+ into macros for speed by Jay Fenlason. Use -DWITH_REXEC for rexec
+ code, courtesy of Dan Kegel. */
+
+#include "system.h"
+
+#include <safe-read.h>
+#include <full-write.h>
+
+/* Try hard to get EOPNOTSUPP defined. 486/ISC has it in net/errno.h,
+ 3B2/SVR3 has it in sys/inet.h. Otherwise, like on MSDOS, use EINVAL. */
+
+#ifndef EOPNOTSUPP
+# if HAVE_NET_ERRNO_H
+# include <net/errno.h>
+# endif
+# if HAVE_SYS_INET_H
+# include <sys/inet.h>
+# endif
+# ifndef EOPNOTSUPP
+# define EOPNOTSUPP EINVAL
+# endif
+#endif
+
+#include <signal.h>
+
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+
+#include "rmt.h"
+
+/* Exit status if exec errors. */
+#define EXIT_ON_EXEC_ERROR 128
+
+/* FIXME: Size of buffers for reading and writing commands to rmt. */
+#define COMMAND_BUFFER_SIZE 64
+
+#ifndef RETSIGTYPE
+# define RETSIGTYPE void
+#endif
+
+/* FIXME: Maximum number of simultaneous remote tape connections. */
+#define MAXUNIT 4
+
+#define PREAD 0 /* read file descriptor from pipe() */
+#define PWRITE 1 /* write file descriptor from pipe() */
+
+/* Return the parent's read side of remote tape connection Fd. */
+#define READ_SIDE(Fd) (from_remote[Fd][PREAD])
+
+/* Return the parent's write side of remote tape connection Fd. */
+#define WRITE_SIDE(Fd) (to_remote[Fd][PWRITE])
+
+/* The pipes for receiving data from remote tape drives. */
+static int from_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
+
+/* The pipes for sending data to remote tape drives. */
+static int to_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
+
+/* Temporary variable used by macros in rmt.h. */
+char *rmt_path__;
+
+
+/* Close remote tape connection HANDLE, and reset errno to ERRNO_VALUE. */
+static void
+_rmt_shutdown (int handle, int errno_value)
+{
+ close (READ_SIDE (handle));
+ close (WRITE_SIDE (handle));
+ READ_SIDE (handle) = -1;
+ WRITE_SIDE (handle) = -1;
+ errno = errno_value;
+}
+
+/* Attempt to perform the remote tape command specified in BUFFER on
+ remote tape connection HANDLE. Return 0 if successful, -1 on
+ error. */
+static int
+do_command (int handle, const char *buffer)
+{
+ /* Save the current pipe handler and try to make the request. */
+
+ size_t length = strlen (buffer);
+ RETSIGTYPE (*pipe_handler) () = signal (SIGPIPE, SIG_IGN);
+ ssize_t written = full_write (WRITE_SIDE (handle), buffer, length);
+ signal (SIGPIPE, pipe_handler);
+
+ if (written == length)
+ return 0;
+
+ /* Something went wrong. Close down and go home. */
+
+ _rmt_shutdown (handle, EIO);
+ return -1;
+}
+
+static char *
+get_status_string (int handle, char *command_buffer)
+{
+ char *cursor;
+ int counter;
+
+ /* Read the reply command line. */
+
+ for (counter = 0, cursor = command_buffer;
+ counter < COMMAND_BUFFER_SIZE;
+ counter++, cursor++)
+ {
+ if (safe_read (READ_SIDE (handle), cursor, 1) != 1)
+ {
+ _rmt_shutdown (handle, EIO);
+ return 0;
+ }
+ if (*cursor == '\n')
+ {
+ *cursor = '\0';
+ break;
+ }
+ }
+
+ if (counter == COMMAND_BUFFER_SIZE)
+ {
+ _rmt_shutdown (handle, EIO);
+ return 0;
+ }
+
+ /* Check the return status. */
+
+ for (cursor = command_buffer; *cursor; cursor++)
+ if (*cursor != ' ')
+ break;
+
+ if (*cursor == 'E' || *cursor == 'F')
+ {
+ errno = atoi (cursor + 1);
+
+ /* Skip the error message line. */
+
+ /* FIXME: there is better to do than merely ignoring error messages
+ coming from the remote end. Translate them, too... */
+
+ {
+ char character;
+
+ while (safe_read (READ_SIDE (handle), &character, 1) == 1)
+ if (character == '\n')
+ break;
+ }
+
+ if (*cursor == 'F')
+ _rmt_shutdown (handle, errno);
+
+ return 0;
+ }
+
+ /* Check for mis-synced pipes. */
+
+ if (*cursor != 'A')
+ {
+ _rmt_shutdown (handle, EIO);
+ return 0;
+ }
+
+ /* Got an `A' (success) response. */
+
+ return cursor + 1;
+}
+
+/* Read and return the status from remote tape connection HANDLE. If
+ an error occurred, return -1 and set errno. */
+static long
+get_status (int handle)
+{
+ char command_buffer[COMMAND_BUFFER_SIZE];
+ const char *status = get_status_string (handle, command_buffer);
+ return status ? atol (status) : -1L;
+}
+
+static off_t
+get_status_off (int handle)
+{
+ char command_buffer[COMMAND_BUFFER_SIZE];
+ const char *status = get_status_string (handle, command_buffer);
+
+ if (! status)
+ return -1;
+ else
+ {
+ /* Parse status, taking care to check for overflow.
+ We can't use standard functions,
+ since off_t might be longer than long. */
+
+ off_t count = 0;
+ int negative;
+
+ for (; *status == ' ' || *status == '\t'; status++)
+ continue;
+
+ negative = *status == '-';
+ status += negative || *status == '+';
+
+ for (;;)
+ {
+ int digit = *status++ - '0';
+ if (9 < (unsigned) digit)
+ break;
+ else
+ {
+ off_t c10 = 10 * count;
+ off_t nc = negative ? c10 - digit : c10 + digit;
+ if (c10 / 10 != count || (negative ? c10 < nc : nc < c10))
+ return -1;
+ count = nc;
+ }
+ }
+
+ return count;
+ }
+}
+
+#if WITH_REXEC
+
+/* Execute /etc/rmt as user USER on remote system HOST using rexec.
+ Return a file descriptor of a bidirectional socket for stdin and
+ stdout. If USER is zero, use the current username.
+
+ By default, this code is not used, since it requires that the user
+ have a .netrc file in his/her home directory, or that the
+ application designer be willing to have rexec prompt for login and
+ password info. This may be unacceptable, and .rhosts files for use
+ with rsh are much more common on BSD systems. */
+static int
+_rmt_rexec (char *host, char *user)
+{
+ int saved_stdin = dup (STDIN_FILENO);
+ int saved_stdout = dup (STDOUT_FILENO);
+ struct servent *rexecserv;
+ int result;
+
+ /* When using cpio -o < filename, stdin is no longer the tty. But the
+ rexec subroutine reads the login and the passwd on stdin, to allow
+ remote execution of the command. So, reopen stdin and stdout on
+ /dev/tty before the rexec and give them back their original value
+ after. */
+
+ if (! freopen ("/dev/tty", "r", stdin))
+ freopen ("/dev/null", "r", stdin);
+ if (! freopen ("/dev/tty", "w", stdout))
+ freopen ("/dev/null", "w", stdout);
+
+ if (rexecserv = getservbyname ("exec", "tcp"), !rexecserv)
+ error (EXIT_ON_EXEC_ERROR, 0, _("exec/tcp: Service not available"));
+
+ result = rexec (&host, rexecserv->s_port, user, 0, "/etc/rmt", 0);
+ if (fclose (stdin) == EOF)
+ error (0, errno, _("stdin"));
+ fdopen (saved_stdin, "r");
+ if (fclose (stdout) == EOF)
+ error (0, errno, _("stdout"));
+ fdopen (saved_stdout, "w");
+
+ return result;
+}
+
+#endif /* WITH_REXEC */
+
+/* Place into BUF a string representing OFLAG, which must be suitable
+ as argument 2 of `open'. BUF must be large enough to hold the
+ result. This function should generate a string that decode_oflag
+ can parse. */
+static void
+encode_oflag (char *buf, int oflag)
+{
+ sprintf (buf, "%d ", oflag);
+
+ switch (oflag & O_ACCMODE)
+ {
+ case O_RDONLY: strcat (buf, "O_RDONLY"); break;
+ case O_RDWR: strcat (buf, "O_RDWR"); break;
+ case O_WRONLY: strcat (buf, "O_WRONLY"); break;
+ default: abort ();
+ }
+
+#ifdef O_APPEND
+ if (oflag & O_APPEND) strcat (buf, "|O_APPEND");
+#endif
+ if (oflag & O_CREAT) strcat (buf, "|O_CREAT");
+#ifdef O_DSYNC
+ if (oflag & O_DSYNC) strcat (buf, "|O_DSYNC");
+#endif
+ if (oflag & O_EXCL) strcat (buf, "|O_EXCL");
+#ifdef O_LARGEFILE
+ if (oflag & O_LARGEFILE) strcat (buf, "|O_LARGEFILE");
+#endif
+#ifdef O_NOCTTY
+ if (oflag & O_NOCTTY) strcat (buf, "|O_NOCTTY");
+#endif
+#ifdef O_NONBLOCK
+ if (oflag & O_NONBLOCK) strcat (buf, "|O_NONBLOCK");
+#endif
+#ifdef O_RSYNC
+ if (oflag & O_RSYNC) strcat (buf, "|O_RSYNC");
+#endif
+#ifdef O_SYNC
+ if (oflag & O_SYNC) strcat (buf, "|O_SYNC");
+#endif
+ if (oflag & O_TRUNC) strcat (buf, "|O_TRUNC");
+}
+
+/* Open a file (a magnetic tape device?) on the system specified in
+ PATH, as the given user. PATH has the form `[USER@]HOST:FILE'.
+ OPEN_MODE is O_RDONLY, O_WRONLY, etc. If successful, return the
+ remote pipe number plus BIAS. REMOTE_SHELL may be overridden. On
+ error, return -1. */
+int
+rmt_open__ (const char *path, int open_mode, int bias, const char *remote_shell)
+{
+ int remote_pipe_number; /* pseudo, biased file descriptor */
+ char *path_copy ; /* copy of path string */
+ char *remote_host; /* remote host name */
+ char *remote_file; /* remote file name (often a device) */
+ char *remote_user; /* remote user name */
+
+ /* Find an unused pair of file descriptors. */
+
+ for (remote_pipe_number = 0;
+ remote_pipe_number < MAXUNIT;
+ remote_pipe_number++)
+ if (READ_SIDE (remote_pipe_number) == -1
+ && WRITE_SIDE (remote_pipe_number) == -1)
+ break;
+
+ if (remote_pipe_number == MAXUNIT)
+ {
+ errno = EMFILE;
+ return -1;
+ }
+
+ /* Pull apart the system and device, and optional user. */
+
+ {
+ char *cursor;
+
+ path_copy = xstrdup (path);
+ remote_host = path_copy;
+ remote_user = 0;
+ remote_file = 0;
+
+ for (cursor = path_copy; *cursor; cursor++)
+ switch (*cursor)
+ {
+ default:
+ break;
+
+ case '\n':
+ /* Do not allow newlines in the path, since the protocol
+ uses newline delimiters. */
+ free (path_copy);
+ errno = ENOENT;
+ return -1;
+
+ case '@':
+ if (!remote_user)
+ {
+ remote_user = remote_host;
+ *cursor = '\0';
+ remote_host = cursor + 1;
+ }
+ break;
+
+ case ':':
+ if (!remote_file)
+ {
+ *cursor = '\0';
+ remote_file = cursor + 1;
+ }
+ break;
+ }
+ }
+
+ /* FIXME: Should somewhat validate the decoding, here. */
+
+ if (remote_user && *remote_user == '\0')
+ remote_user = 0;
+
+#if WITH_REXEC
+
+ /* Execute the remote command using rexec. */
+
+ READ_SIDE (remote_pipe_number) = _rmt_rexec (remote_host, remote_user);
+ if (READ_SIDE (remote_pipe_number) < 0)
+ {
+ int e = errno;
+ free (path_copy);
+ errno = e;
+ return -1;
+ }
+
+ WRITE_SIDE (remote_pipe_number) = READ_SIDE (remote_pipe_number);
+
+#else /* not WITH_REXEC */
+ {
+ const char *remote_shell_basename;
+ pid_t status;
+
+ /* Identify the remote command to be executed. */
+
+ if (!remote_shell)
+ {
+#ifdef REMOTE_SHELL
+ remote_shell = REMOTE_SHELL;
+#else
+ free (path_copy);
+ errno = EIO;
+ return -1;
+#endif
+ }
+ remote_shell_basename = base_name (remote_shell);
+
+ /* Set up the pipes for the `rsh' command, and fork. */
+
+ if (pipe (to_remote[remote_pipe_number]) == -1
+ || pipe (from_remote[remote_pipe_number]) == -1)
+ {
+ int e = errno;
+ free (path_copy);
+ errno = e;
+ return -1;
+ }
+
+ status = fork ();
+ if (status == -1)
+ {
+ int e = errno;
+ free (path_copy);
+ errno = e;
+ return -1;
+ }
+
+ if (status == 0)
+ {
+ /* Child. */
+
+ close (STDIN_FILENO);
+ dup (to_remote[remote_pipe_number][PREAD]);
+ close (to_remote[remote_pipe_number][PREAD]);
+ close (to_remote[remote_pipe_number][PWRITE]);
+
+ close (STDOUT_FILENO);
+ dup (from_remote[remote_pipe_number][PWRITE]);
+ close (from_remote[remote_pipe_number][PREAD]);
+ close (from_remote[remote_pipe_number][PWRITE]);
+
+#if !MSDOS
+ setuid (getuid ());
+ setgid (getgid ());
+#endif
+
+ if (remote_user)
+ execl (remote_shell, remote_shell_basename, remote_host,
+ "-l", remote_user, "/etc/rmt", (char *) 0);
+ else
+ execl (remote_shell, remote_shell_basename, remote_host,
+ "/etc/rmt", (char *) 0);
+
+ /* Bad problems if we get here. */
+
+ /* In a previous version, _exit was used here instead of exit. */
+ error (EXIT_ON_EXEC_ERROR, errno, _("Cannot execute remote shell"));
+ }
+
+ /* Parent. */
+
+ close (from_remote[remote_pipe_number][PWRITE]);
+ close (to_remote[remote_pipe_number][PREAD]);
+ }
+#endif /* not WITH_REXEC */
+
+ /* Attempt to open the tape device. */
+
+ {
+ size_t remote_file_len = strlen (remote_file);
+ char *command_buffer = xmalloc (remote_file_len + 1000);
+ sprintf (command_buffer, "O%s\n", remote_file);
+ encode_oflag (command_buffer + remote_file_len + 2, open_mode);
+ strcat (command_buffer, "\n");
+ if (do_command (remote_pipe_number, command_buffer) == -1
+ || get_status (remote_pipe_number) == -1)
+ {
+ int e = errno;
+ free (command_buffer);
+ free (path_copy);
+ _rmt_shutdown (remote_pipe_number, e);
+ return -1;
+ }
+ free (command_buffer);
+ }
+
+ free (path_copy);
+ return remote_pipe_number + bias;
+}
+
+/* Close remote tape connection HANDLE and shut down. Return 0 if
+ successful, -1 on error. */
+int
+rmt_close__ (int handle)
+{
+ int status;
+
+ if (do_command (handle, "C\n") == -1)
+ return -1;
+
+ status = get_status (handle);
+ _rmt_shutdown (handle, errno);
+ return status;
+}
+
+/* Read up to LENGTH bytes into BUFFER from remote tape connection HANDLE.
+ Return the number of bytes read on success, -1 on error. */
+ssize_t
+rmt_read__ (int handle, char *buffer, size_t length)
+{
+ char command_buffer[COMMAND_BUFFER_SIZE];
+ ssize_t status, rlen;
+ size_t counter;
+
+ sprintf (command_buffer, "R%lu\n", (unsigned long) length);
+ if (do_command (handle, command_buffer) == -1
+ || (status = get_status (handle)) == -1)
+ return -1;
+
+ for (counter = 0; counter < status; counter += rlen, buffer += rlen)
+ {
+ rlen = safe_read (READ_SIDE (handle), buffer, status - counter);
+ if (rlen <= 0)
+ {
+ _rmt_shutdown (handle, EIO);
+ return -1;
+ }
+ }
+
+ return status;
+}
+
+/* Write LENGTH bytes from BUFFER to remote tape connection HANDLE.
+ Return the number of bytes written on success, -1 on error. */
+ssize_t
+rmt_write__ (int handle, char *buffer, size_t length)
+{
+ char command_buffer[COMMAND_BUFFER_SIZE];
+ RETSIGTYPE (*pipe_handler) ();
+ size_t written;
+
+ sprintf (command_buffer, "W%lu\n", (unsigned long) length);
+ if (do_command (handle, command_buffer) == -1)
+ return -1;
+
+ pipe_handler = signal (SIGPIPE, SIG_IGN);
+ written = full_write (WRITE_SIDE (handle), buffer, length);
+ signal (SIGPIPE, pipe_handler);
+ if (written == length)
+ return get_status (handle);
+
+ /* Write error. */
+
+ _rmt_shutdown (handle, EIO);
+ return -1;
+}
+
+/* Perform an imitation lseek operation on remote tape connection
+ HANDLE. Return the new file offset if successful, -1 if on error. */
+off_t
+rmt_lseek__ (int handle, off_t offset, int whence)
+{
+ char command_buffer[COMMAND_BUFFER_SIZE];
+ char operand_buffer[UINTMAX_STRSIZE_BOUND];
+ uintmax_t u = offset < 0 ? - (uintmax_t) offset : (uintmax_t) offset;
+ char *p = operand_buffer + sizeof operand_buffer;
+
+ do
+ *--p = '0' + (int) (u % 10);
+ while ((u /= 10) != 0);
+ if (offset < 0)
+ *--p = '-';
+
+ switch (whence)
+ {
+ case SEEK_SET: whence = 0; break;
+ case SEEK_CUR: whence = 1; break;
+ case SEEK_END: whence = 2; break;
+ default: abort ();
+ }
+
+ sprintf (command_buffer, "L%s\n%d\n", p, whence);
+
+ if (do_command (handle, command_buffer) == -1)
+ return -1;
+
+ return get_status_off (handle);
+}
+
+/* Perform a raw tape operation on remote tape connection HANDLE.
+ Return the results of the ioctl, or -1 on error. */
+int
+rmt_ioctl__ (int handle, int operation, char *argument)
+{
+ switch (operation)
+ {
+ default:
+ errno = EOPNOTSUPP;
+ return -1;
+
+#ifdef MTIOCTOP
+ case MTIOCTOP:
+ {
+ char command_buffer[COMMAND_BUFFER_SIZE];
+ char operand_buffer[UINTMAX_STRSIZE_BOUND];
+ uintmax_t u = (((struct mtop *) argument)->mt_count < 0
+ ? - (uintmax_t) ((struct mtop *) argument)->mt_count
+ : (uintmax_t) ((struct mtop *) argument)->mt_count);
+ char *p = operand_buffer + sizeof operand_buffer;
+
+ do
+ *--p = '0' + (int) (u % 10);
+ while ((u /= 10) != 0);
+ if (((struct mtop *) argument)->mt_count < 0)
+ *--p = '-';
+
+ /* MTIOCTOP is the easy one. Nothing is transferred in binary. */
+
+ sprintf (command_buffer, "I%d\n%s\n",
+ ((struct mtop *) argument)->mt_op, p);
+ if (do_command (handle, command_buffer) == -1)
+ return -1;
+
+ return get_status (handle);
+ }
+#endif /* MTIOCTOP */
+
+#ifdef MTIOCGET
+ case MTIOCGET:
+ {
+ ssize_t status;
+ ssize_t counter;
+
+ /* Grab the status and read it directly into the structure. This
+ assumes that the status buffer is not padded and that 2 shorts
+ fit in a long without any word alignment problems; i.e., the
+ whole struct is contiguous. NOTE - this is probably NOT a good
+ assumption. */
+
+ if (do_command (handle, "S") == -1
+ || (status = get_status (handle), status == -1))
+ return -1;
+
+ for (; status > 0; status -= counter, argument += counter)
+ {
+ counter = safe_read (READ_SIDE (handle), argument, status);
+ if (counter <= 0)
+ {
+ _rmt_shutdown (handle, EIO);
+ return -1;
+ }
+ }
+
+ /* Check for byte position. mt_type (or mt_model) is a small integer
+ field (normally) so we will check its magnitude. If it is larger
+ than 256, we will assume that the bytes are swapped and go through
+ and reverse all the bytes. */
+
+ if (((struct mtget *) argument)->MTIO_CHECK_FIELD < 256)
+ return 0;
+
+ for (counter = 0; counter < status; counter += 2)
+ {
+ char copy = argument[counter];
+
+ argument[counter] = argument[counter + 1];
+ argument[counter + 1] = copy;
+ }
+
+ return 0;
+ }
+#endif /* MTIOCGET */
+
+ }
+}
diff --git a/contrib/tar/src/system.h b/contrib/tar/src/system.h
new file mode 100644
index 0000000..8e88159
--- /dev/null
+++ b/contrib/tar/src/system.h
@@ -0,0 +1,587 @@
+/* System dependent definitions for GNU tar.
+
+ Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 Free Software
+ Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Declare alloca. AIX requires this to be the first thing in the file. */
+
+#if __GNUC__
+# define alloca __builtin_alloca
+#else
+# if HAVE_ALLOCA_H
+# include <alloca.h>
+# else
+# ifdef _AIX
+ #pragma alloca
+# else
+# ifndef alloca
+char *alloca ();
+# endif
+# endif
+# endif
+#endif
+
+#ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__
+# define __attribute__(Spec) /* empty */
+# endif
+#endif
+
+#include <sys/types.h>
+#include <ctype.h>
+
+#if HAVE_STDDEF_H
+# include <stddef.h>
+#endif
+#ifndef offsetof
+# define offsetof(type, ident) ((size_t) &((type *) 0)->ident)
+#endif
+
+/* IN_CTYPE_DOMAIN (C) is nonzero if the unsigned char C can safely be given
+ as an argument to <ctype.h> macros like `isspace'. */
+#if STDC_HEADERS
+# define IN_CTYPE_DOMAIN(c) 1
+#else
+# define IN_CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177)
+#endif
+
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+#define ISODIGIT(c) ((unsigned) (c) - '0' <= 7)
+#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c))
+#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
+
+/* Declare string and memory handling routines. Take care that an ANSI
+ string.h and pre-ANSI memory.h might conflict, and that memory.h and
+ strings.h conflict on some systems. */
+
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+#else
+# include <strings.h>
+# ifndef strchr
+# define strchr index
+# endif
+# ifndef strrchr
+# define strrchr rindex
+# endif
+# ifndef memcpy
+# define memcpy(d, s, n) bcopy ((char const *) (s), (char *) (d), n)
+# endif
+# ifndef memcmp
+# define memcmp(a, b, n) bcmp ((char const *) (a), (char const *) (b), n)
+# endif
+#endif
+
+/* Declare errno. */
+
+#include <errno.h>
+#ifndef errno
+extern int errno;
+#endif
+
+/* Declare open parameters. */
+
+#if HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+ /* Pick only one of the next three: */
+#ifndef O_RDONLY
+# define O_RDONLY 0 /* only allow read */
+#endif
+#ifndef O_WRONLY
+# define O_WRONLY 1 /* only allow write */
+#endif
+#ifndef O_RDWR
+# define O_RDWR 2 /* both are allowed */
+#endif
+#ifndef O_ACCMODE
+# define O_ACCMODE (O_RDONLY | O_RDWR | O_WRONLY)
+#endif
+ /* The rest can be OR-ed in to the above: */
+#ifndef O_CREAT
+# define O_CREAT 8 /* create file if needed */
+#endif
+#ifndef O_EXCL
+# define O_EXCL 16 /* file cannot already exist */
+#endif
+#ifndef O_TRUNC
+# define O_TRUNC 32 /* truncate file on open */
+#endif
+ /* MS-DOG forever, with my love! */
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+/* Declare file status routines and bits. */
+
+#include <sys/stat.h>
+
+#if !HAVE_LSTAT && !defined lstat
+# define lstat stat
+#endif
+
+#if STX_HIDDEN && !_LARGE_FILES /* AIX */
+# ifdef stat
+# undef stat
+# endif
+# define stat(path, buf) statx (path, buf, STATSIZE, STX_HIDDEN)
+# ifdef lstat
+# undef lstat
+# endif
+# define lstat(path, buf) statx (path, buf, STATSIZE, STX_HIDDEN | STX_LINK)
+#endif
+
+#if STAT_MACROS_BROKEN
+# undef S_ISBLK
+# undef S_ISCHR
+# undef S_ISCTG
+# undef S_ISDIR
+# undef S_ISFIFO
+# undef S_ISLNK
+# undef S_ISREG
+# undef S_ISSOCK
+#endif
+
+/* On MSDOS, there are missing things from <sys/stat.h>. */
+#if MSDOS
+# define S_ISUID 0
+# define S_ISGID 0
+# define S_ISVTX 0
+#endif
+
+#ifndef S_ISDIR
+# define S_ISDIR(Mode) (((Mode) & S_IFMT) == S_IFDIR)
+#endif
+#ifndef S_ISREG
+# define S_ISREG(Mode) (((Mode) & S_IFMT) == S_IFREG)
+#endif
+
+#ifndef S_ISBLK
+# ifdef S_IFBLK
+# define S_ISBLK(Mode) (((Mode) & S_IFMT) == S_IFBLK)
+# else
+# define S_ISBLK(Mode) 0
+# endif
+#endif
+#ifndef S_ISCHR
+# ifdef S_IFCHR
+# define S_ISCHR(Mode) (((Mode) & S_IFMT) == S_IFCHR)
+# else
+# define S_ISCHR(Mode) 0
+# endif
+#endif
+#ifndef S_ISCTG
+# ifdef S_IFCTG
+# define S_ISCTG(Mode) (((Mode) & S_IFMT) == S_IFCTG)
+# else
+# define S_ISCTG(Mode) 0
+# endif
+#endif
+#ifndef S_ISDOOR
+# define S_ISDOOR(Mode) 0
+#endif
+#ifndef S_ISFIFO
+# ifdef S_IFIFO
+# define S_ISFIFO(Mode) (((Mode) & S_IFMT) == S_IFIFO)
+# else
+# define S_ISFIFO(Mode) 0
+# endif
+#endif
+#ifndef S_ISLNK
+# ifdef S_IFLNK
+# define S_ISLNK(Mode) (((Mode) & S_IFMT) == S_IFLNK)
+# else
+# define S_ISLNK(Mode) 0
+# endif
+#endif
+#ifndef S_ISSOCK
+# ifdef S_IFSOCK
+# define S_ISSOCK(Mode) (((Mode) & S_IFMT) == S_IFSOCK)
+# else
+# define S_ISSOCK(Mode) 0
+# endif
+#endif
+
+#if !HAVE_MKFIFO && !defined mkfifo && defined S_IFIFO
+# define mkfifo(Path, Mode) (mknod (Path, (Mode) | S_IFIFO, 0))
+#endif
+
+#ifndef S_ISUID
+# define S_ISUID 0004000
+#endif
+#ifndef S_ISGID
+# define S_ISGID 0002000
+#endif
+#ifndef S_ISVTX
+# define S_ISVTX 0001000
+#endif
+#ifndef S_IRUSR
+# define S_IRUSR 0000400
+#endif
+#ifndef S_IWUSR
+# define S_IWUSR 0000200
+#endif
+#ifndef S_IXUSR
+# define S_IXUSR 0000100
+#endif
+#ifndef S_IRGRP
+# define S_IRGRP 0000040
+#endif
+#ifndef S_IWGRP
+# define S_IWGRP 0000020
+#endif
+#ifndef S_IXGRP
+# define S_IXGRP 0000010
+#endif
+#ifndef S_IROTH
+# define S_IROTH 0000004
+#endif
+#ifndef S_IWOTH
+# define S_IWOTH 0000002
+#endif
+#ifndef S_IXOTH
+# define S_IXOTH 0000001
+#endif
+
+#define MODE_WXUSR (S_IWUSR | S_IXUSR)
+#define MODE_R (S_IRUSR | S_IRGRP | S_IROTH)
+#define MODE_RW (S_IWUSR | S_IWGRP | S_IWOTH | MODE_R)
+#define MODE_RWX (S_IXUSR | S_IXGRP | S_IXOTH | MODE_RW)
+#define MODE_ALL (S_ISUID | S_ISGID | S_ISVTX | MODE_RWX)
+
+#ifndef _POSIX_SOURCE
+# include <sys/param.h>
+#endif
+
+/* Include <unistd.h> before any preprocessor test of _POSIX_VERSION. */
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifndef SEEK_SET
+# define SEEK_SET 0
+#endif
+#ifndef SEEK_CUR
+# define SEEK_CUR 1
+#endif
+#ifndef SEEK_END
+# define SEEK_END 2
+#endif
+
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+#endif
+#ifndef STDOUT_FILENO
+# define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+# define STDERR_FILENO 2
+#endif
+
+/* Declare make device, major and minor. Since major is a function on
+ SVR4, we have to resort to GOT_MAJOR instead of just testing if
+ major is #define'd. */
+
+#if MAJOR_IN_MKDEV
+# include <sys/mkdev.h>
+# define GOT_MAJOR
+#endif
+
+#if MAJOR_IN_SYSMACROS
+# include <sys/sysmacros.h>
+# define GOT_MAJOR
+#endif
+
+/* Some <sys/types.h> defines the macros. */
+#ifdef major
+# define GOT_MAJOR
+#endif
+
+#ifndef GOT_MAJOR
+# if MSDOS
+# define major(Device) (Device)
+# define minor(Device) (Device)
+# define makedev(Major, Minor) (((Major) << 8) | (Minor))
+# define GOT_MAJOR
+# endif
+#endif
+
+/* For HP-UX before HP-UX 8, major/minor are not in <sys/sysmacros.h>. */
+#ifndef GOT_MAJOR
+# if defined(hpux) || defined(__hpux__) || defined(__hpux)
+# include <sys/mknod.h>
+# define GOT_MAJOR
+# endif
+#endif
+
+#ifndef GOT_MAJOR
+# define major(Device) (((Device) >> 8) & 0xff)
+# define minor(Device) ((Device) & 0xff)
+# define makedev(Major, Minor) (((Major) << 8) | (Minor))
+#endif
+
+#undef GOT_MAJOR
+
+/* Declare wait status. */
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(s) (((s) >> 8) & 0xff)
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(s) (((s) & 0xffff) - 1 < (unsigned) 0xff)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(s) ((s) & 0x7f)
+#endif
+
+/* FIXME: It is wrong to use BLOCKSIZE for buffers when the logical block
+ size is greater than 512 bytes; so ST_BLKSIZE code below, in preparation
+ for some cleanup in this area, later. */
+
+/* Get or fake the disk device blocksize. Usually defined by sys/param.h
+ (if at all). */
+
+#if !defined(DEV_BSIZE) && defined(BSIZE)
+# define DEV_BSIZE BSIZE
+#endif
+#if !defined(DEV_BSIZE) && defined(BBSIZE) /* SGI */
+# define DEV_BSIZE BBSIZE
+#endif
+#ifndef DEV_BSIZE
+# define DEV_BSIZE 4096
+#endif
+
+/* Extract or fake data from a `struct stat'. ST_BLKSIZE gives the
+ optimal I/O blocksize for the file, in bytes. Some systems, like
+ Sequents, return st_blksize of 0 on pipes. */
+
+#if !HAVE_ST_BLKSIZE
+# define ST_BLKSIZE(Statbuf) DEV_BSIZE
+#else
+# define ST_BLKSIZE(Statbuf) \
+ ((Statbuf).st_blksize > 0 ? (Statbuf).st_blksize : DEV_BSIZE)
+#endif
+
+/* Extract or fake data from a `struct stat'. ST_NBLOCKS gives the
+ number of ST_NBLOCKSIZE-byte blocks in the file (including indirect blocks).
+ HP-UX counts st_blocks in 1024-byte units,
+ this loses when mixing HP-UX and BSD filesystems with NFS. AIX PS/2
+ counts st_blocks in 4K units. */
+
+#if !HAVE_ST_BLOCKS
+# if defined(_POSIX_SOURCE) || !defined(BSIZE)
+# define ST_NBLOCKS(Statbuf) ((Statbuf).st_size / ST_NBLOCKSIZE + ((Statbuf).st_size % ST_NBLOCKSIZE != 0))
+# else
+ off_t st_blocks ();
+# define ST_NBLOCKS(Statbuf) (st_blocks ((Statbuf).st_size))
+# endif
+#else
+# define ST_NBLOCKS(Statbuf) ((Statbuf).st_blocks)
+# if defined(hpux) || defined(__hpux__) || defined(__hpux)
+# define ST_NBLOCKSIZE 1024
+# else
+# if defined(_AIX) && defined(_I386)
+# define ST_NBLOCKSIZE (4 * 1024)
+# endif
+# endif
+#endif
+
+#ifndef ST_NBLOCKSIZE
+#define ST_NBLOCKSIZE 512
+#endif
+
+/* This is a real challenge to properly get MTIO* symbols :-(. ISC uses
+ <sys/gentape.h>. SCO and BSDi uses <sys/tape.h>; BSDi also requires
+ <sys/tprintf.h> and <sys/device.h> for defining tp_dev and tpr_t. It
+ seems that the rest use <sys/mtio.h>, which itself requires other files,
+ depending on systems. Pyramid defines _IOW in <sgtty.h>, for example. */
+
+#if HAVE_SYS_GENTAPE_H
+# include <sys/gentape.h>
+#else
+# if HAVE_SYS_TAPE_H
+# if HAVE_SYS_DEVICE_H
+# include <sys/device.h>
+# endif
+# if HAVE_SYS_BUF_H
+# include <sys/buf.h>
+# endif
+# if HAVE_SYS_TPRINTF_H
+# include <sys/tprintf.h>
+# endif
+# include <sys/tape.h>
+# else
+# if HAVE_SYS_MTIO_H
+# include <sys/ioctl.h>
+# if HAVE_SGTTY_H
+# include <sgtty.h>
+# endif
+# if HAVE_SYS_IO_TRIOCTL_H
+# include <sys/io/trioctl.h>
+# endif
+# include <sys/mtio.h>
+# endif
+# endif
+#endif
+
+/* Declare standard functions. */
+
+#if STDC_HEADERS
+# include <stdlib.h>
+#else
+void *malloc ();
+void *realloc ();
+char *getenv ();
+#endif
+
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+typedef enum {false = 0, true = 1} bool;
+#endif
+
+#include <stdio.h>
+
+#ifndef _POSIX_VERSION
+# if MSDOS
+# include <io.h>
+# else
+off_t lseek ();
+# endif
+#endif
+
+#if WITH_DMALLOC
+# undef HAVE_VALLOC
+# define DMALLOC_FUNC_CHECK
+# include <dmalloc.h>
+#endif
+
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#ifndef CHAR_BIT
+# define CHAR_BIT 8
+#endif
+
+#ifndef CHAR_MAX
+# define CHAR_MAX TYPE_MAXIMUM (char)
+#endif
+
+#ifndef UCHAR_MAX
+# define UCHAR_MAX TYPE_MAXIMUM (unsigned char)
+#endif
+
+#ifndef LONG_MAX
+# define LONG_MAX TYPE_MAXIMUM (long)
+#endif
+
+#ifndef MB_LEN_MAX
+# define MB_LEN_MAX 1
+#endif
+
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+/* These macros work even on ones'-complement hosts (!).
+ The extra casts work around common compiler bugs. */
+#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+#define TYPE_MINIMUM(t) (TYPE_SIGNED (t) \
+ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) \
+ : (t) 0)
+#define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t)))
+
+/* Bound on length of the string representing an integer value of type t.
+ Subtract one for the sign bit if t is signed;
+ 302 / 1000 is log10 (2) rounded up;
+ add one for integer division truncation;
+ add one more for a minus sign if t is signed. */
+#define INT_STRLEN_BOUND(t) \
+ ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \
+ + 1 + TYPE_SIGNED (t))
+
+#define UINTMAX_STRSIZE_BOUND (INT_STRLEN_BOUND (uintmax_t) + 1)
+
+/* Prototypes for external functions. */
+
+#ifndef PARAMS
+# if PROTOTYPES
+# define PARAMS(Args) Args
+# else
+# define PARAMS(Args) ()
+# endif
+#endif
+
+#if HAVE_LOCALE_H
+# include <locale.h>
+#endif
+#if !HAVE_SETLOCALE
+# define setlocale(Category, Locale) /* empty */
+#endif
+
+#if ENABLE_NLS
+# include <libintl.h>
+# define _(Text) gettext (Text)
+#else
+# undef bindtextdomain
+# define bindtextdomain(Domain, Directory) /* empty */
+# undef textdomain
+# define textdomain(Domain) /* empty */
+# define _(Text) Text
+#endif
+#define N_(Text) Text
+
+#include <time.h>
+#ifndef time
+time_t time ();
+#endif
+
+/* Library modules. */
+
+#include <dirname.h>
+#include <error.h>
+#include <savedir.h>
+#include <xalloc.h>
+
+#if !HAVE_STRSTR
+char *strstr PARAMS ((const char *, const char *));
+#endif
+
+#if HAVE_VALLOC
+# ifndef valloc
+void *valloc ();
+# endif
+#else
+# define valloc(Size) malloc (Size)
+#endif
+
+char *xstrdup PARAMS ((char const *));
diff --git a/contrib/tar/src/tar.c b/contrib/tar/src/tar.c
new file mode 100644
index 0000000..7d87f80
--- /dev/null
+++ b/contrib/tar/src/tar.c
@@ -0,0 +1,1354 @@
+/* A tar (tape archiver) program.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000, 2001
+ Free Software Foundation, Inc.
+
+ Written by John Gilmore, starting 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "system.h"
+
+#include <fnmatch.h>
+#include <getopt.h>
+
+#include <signal.h>
+#if ! defined SIGCHLD && defined SIGCLD
+# define SIGCHLD SIGCLD
+#endif
+
+/* The following causes "common.h" to produce definitions of all the global
+ variables, rather than just "extern" declarations of them. GNU tar does
+ depend on the system loader to preset all GLOBAL variables to neutral (or
+ zero) values; explicit initialization is usually not done. */
+#define GLOBAL
+#include "common.h"
+
+#include <print-copyr.h>
+#include <localedir.h>
+#include <prepargs.h>
+#include <quotearg.h>
+#include <xstrtol.h>
+
+time_t get_date ();
+
+/* Local declarations. */
+
+#ifndef DEFAULT_ARCHIVE
+# define DEFAULT_ARCHIVE "tar.out"
+#endif
+
+#ifndef DEFAULT_BLOCKING
+# define DEFAULT_BLOCKING 20
+#endif
+
+static void usage PARAMS ((int)) __attribute__ ((noreturn));
+
+/* Miscellaneous. */
+
+/* Name of option using stdin. */
+static const char *stdin_used_by;
+
+/* Doesn't return if stdin already requested. */
+void
+request_stdin (const char *option)
+{
+ if (stdin_used_by)
+ USAGE_ERROR ((0, 0, _("Options `-%s' and `-%s' both want standard input"),
+ stdin_used_by, option));
+
+ stdin_used_by = option;
+}
+
+/* Returns true if and only if the user typed 'y' or 'Y'. */
+int
+confirm (const char *message_action, const char *message_name)
+{
+ static FILE *confirm_file;
+ static int confirm_file_EOF;
+
+ if (!confirm_file)
+ {
+ if (archive == 0 || stdin_used_by)
+ {
+ confirm_file = fopen (TTY_NAME, "r");
+ if (! confirm_file)
+ open_fatal (TTY_NAME);
+ }
+ else
+ {
+ request_stdin ("-w");
+ confirm_file = stdin;
+ }
+ }
+
+ fprintf (stdlis, "%s %s?", message_action, quote (message_name));
+ fflush (stdlis);
+
+ {
+ int reply = confirm_file_EOF ? EOF : getc (confirm_file);
+ int character;
+
+ for (character = reply;
+ character != '\n';
+ character = getc (confirm_file))
+ if (character == EOF)
+ {
+ confirm_file_EOF = 1;
+ fputc ('\n', stdlis);
+ fflush (stdlis);
+ break;
+ }
+ return reply == 'y' || reply == 'Y';
+ }
+}
+
+/* Options. */
+
+/* For long options that unconditionally set a single flag, we have getopt
+ do it. For the others, we share the code for the equivalent short
+ named option, the name of which is stored in the otherwise-unused `val'
+ field of the `struct option'; for long options that have no equivalent
+ short option, we use non-characters as pseudo short options,
+ starting at CHAR_MAX + 1 and going upwards. */
+
+enum
+{
+ ANCHORED_OPTION = CHAR_MAX + 1,
+ BACKUP_OPTION,
+ DELETE_OPTION,
+ EXCLUDE_OPTION,
+ GROUP_OPTION,
+ IGNORE_CASE_OPTION,
+ MODE_OPTION,
+ NEWER_MTIME_OPTION,
+ NO_ANCHORED_OPTION,
+ NO_IGNORE_CASE_OPTION,
+ NO_WILDCARDS_OPTION,
+ NO_WILDCARDS_MATCH_SLASH_OPTION,
+ NULL_OPTION,
+ OVERWRITE_OPTION,
+ OVERWRITE_DIR_OPTION,
+ OWNER_OPTION,
+ POSIX_OPTION,
+ PRESERVE_OPTION,
+ RECORD_SIZE_OPTION,
+ RSH_COMMAND_OPTION,
+ SUFFIX_OPTION,
+ USE_COMPRESS_PROGRAM_OPTION,
+ VOLNO_FILE_OPTION,
+ WILDCARDS_OPTION,
+ WILDCARDS_MATCH_SLASH_OPTION,
+
+ /* Some cleanup is being made in GNU tar long options. Using old names is
+ allowed for a while, but will also send a warning to stderr. Take old
+ names out in 1.14, or in summer 1997, whichever happens last. */
+
+ OBSOLETE_ABSOLUTE_NAMES,
+ OBSOLETE_BLOCK_COMPRESS,
+ OBSOLETE_BLOCKING_FACTOR,
+ OBSOLETE_BLOCK_NUMBER,
+ OBSOLETE_READ_FULL_RECORDS,
+ OBSOLETE_TOUCH,
+ OBSOLETE_VERSION_CONTROL
+};
+
+/* If nonzero, display usage information and exit. */
+static int show_help;
+
+/* If nonzero, print the version on standard output and exit. */
+static int show_version;
+
+static struct option long_options[] =
+{
+ {"absolute-names", no_argument, 0, 'P'},
+ {"absolute-paths", no_argument, 0, OBSOLETE_ABSOLUTE_NAMES},
+ {"after-date", required_argument, 0, 'N'},
+ {"anchored", no_argument, 0, ANCHORED_OPTION},
+ {"append", no_argument, 0, 'r'},
+ {"atime-preserve", no_argument, &atime_preserve_option, 1},
+ {"backup", optional_argument, 0, BACKUP_OPTION},
+ {"block-compress", no_argument, 0, OBSOLETE_BLOCK_COMPRESS},
+ {"block-number", no_argument, 0, 'R'},
+ {"block-size", required_argument, 0, OBSOLETE_BLOCKING_FACTOR},
+ {"blocking-factor", required_argument, 0, 'b'},
+ {"bzip2", no_argument, 0, 'j'},
+ {"catenate", no_argument, 0, 'A'},
+ {"checkpoint", no_argument, &checkpoint_option, 1},
+ {"compare", no_argument, 0, 'd'},
+ {"compress", no_argument, 0, 'Z'},
+ {"concatenate", no_argument, 0, 'A'},
+ {"confirmation", no_argument, 0, 'w'},
+ /* FIXME: --selective as a synonym for --confirmation? */
+ {"create", no_argument, 0, 'c'},
+ {"delete", no_argument, 0, DELETE_OPTION},
+ {"dereference", no_argument, 0, 'h'},
+ {"diff", no_argument, 0, 'd'},
+ {"directory", required_argument, 0, 'C'},
+ {"exclude", required_argument, 0, EXCLUDE_OPTION},
+ {"exclude-from", required_argument, 0, 'X'},
+ {"extract", no_argument, 0, 'x'},
+ {"file", required_argument, 0, 'f'},
+ {"files-from", required_argument, 0, 'T'},
+ {"force-local", no_argument, &force_local_option, 1},
+ {"get", no_argument, 0, 'x'},
+ {"group", required_argument, 0, GROUP_OPTION},
+ {"gunzip", no_argument, 0, 'z'},
+ {"gzip", no_argument, 0, 'z'},
+ {"help", no_argument, &show_help, 1},
+ {"ignore-case", no_argument, 0, IGNORE_CASE_OPTION},
+ {"ignore-failed-read", no_argument, &ignore_failed_read_option, 1},
+ {"ignore-zeros", no_argument, 0, 'i'},
+ /* FIXME: --ignore-end as a new name for --ignore-zeros? */
+ {"incremental", no_argument, 0, 'G'},
+ {"info-script", required_argument, 0, 'F'},
+ {"interactive", no_argument, 0, 'w'},
+ {"keep-old-files", no_argument, 0, 'k'},
+ {"label", required_argument, 0, 'V'},
+ {"list", no_argument, 0, 't'},
+ {"listed-incremental", required_argument, 0, 'g'},
+ {"mode", required_argument, 0, MODE_OPTION},
+ {"modification-time", no_argument, 0, OBSOLETE_TOUCH},
+ {"multi-volume", no_argument, 0, 'M'},
+ {"new-volume-script", required_argument, 0, 'F'},
+ {"newer", required_argument, 0, 'N'},
+ {"newer-mtime", required_argument, 0, NEWER_MTIME_OPTION},
+ {"null", no_argument, 0, NULL_OPTION},
+ {"no-anchored", no_argument, 0, NO_ANCHORED_OPTION},
+ {"no-ignore-case", no_argument, 0, NO_IGNORE_CASE_OPTION},
+ {"no-wildcards", no_argument, 0, NO_WILDCARDS_OPTION},
+ {"no-wildcards-match-slash", no_argument, 0, NO_WILDCARDS_MATCH_SLASH_OPTION},
+ {"no-recursion", no_argument, &recursion_option, 0},
+ {"no-same-owner", no_argument, &same_owner_option, -1},
+ {"no-same-permissions", no_argument, &same_permissions_option, -1},
+ {"numeric-owner", no_argument, &numeric_owner_option, 1},
+ {"old-archive", no_argument, 0, 'o'},
+ {"one-file-system", no_argument, 0, 'l'},
+ {"overwrite", no_argument, 0, OVERWRITE_OPTION},
+ {"overwrite-dir", no_argument, 0, OVERWRITE_DIR_OPTION},
+ {"owner", required_argument, 0, OWNER_OPTION},
+ {"portability", no_argument, 0, 'o'},
+ {"posix", no_argument, 0, POSIX_OPTION},
+ {"preserve", no_argument, 0, PRESERVE_OPTION},
+ {"preserve-order", no_argument, 0, 's'},
+ {"preserve-permissions", no_argument, 0, 'p'},
+ {"recursion", no_argument, &recursion_option, FNM_LEADING_DIR},
+ {"recursive-unlink", no_argument, &recursive_unlink_option, 1},
+ {"read-full-blocks", no_argument, 0, OBSOLETE_READ_FULL_RECORDS},
+ {"read-full-records", no_argument, 0, 'B'},
+ /* FIXME: --partial-blocks might be a synonym for --read-full-records? */
+ {"record-number", no_argument, 0, OBSOLETE_BLOCK_NUMBER},
+ {"record-size", required_argument, 0, RECORD_SIZE_OPTION},
+ {"remove-files", no_argument, &remove_files_option, 1},
+ {"rsh-command", required_argument, 0, RSH_COMMAND_OPTION},
+ {"same-order", no_argument, 0, 's'},
+ {"same-owner", no_argument, &same_owner_option, 1},
+ {"same-permissions", no_argument, 0, 'p'},
+ {"show-omitted-dirs", no_argument, &show_omitted_dirs_option, 1},
+ {"sparse", no_argument, 0, 'S'},
+ {"starting-file", required_argument, 0, 'K'},
+ {"suffix", required_argument, 0, SUFFIX_OPTION},
+ {"tape-length", required_argument, 0, 'L'},
+ {"to-stdout", no_argument, 0, 'O'},
+ {"totals", no_argument, &totals_option, 1},
+ {"touch", no_argument, 0, 'm'},
+ {"uncompress", no_argument, 0, 'Z'},
+ {"ungzip", no_argument, 0, 'z'},
+ {"unlink-first", no_argument, 0, 'U'},
+ {"update", no_argument, 0, 'u'},
+ {"use-compress-program", required_argument, 0, USE_COMPRESS_PROGRAM_OPTION},
+ {"verbose", no_argument, 0, 'v'},
+ {"verify", no_argument, 0, 'W'},
+ {"version", no_argument, &show_version, 1},
+ {"version-control", required_argument, 0, OBSOLETE_VERSION_CONTROL},
+ {"volno-file", required_argument, 0, VOLNO_FILE_OPTION},
+ {"wildcards", no_argument, 0, WILDCARDS_OPTION},
+ {"wildcards-match-slash", no_argument, 0, WILDCARDS_MATCH_SLASH_OPTION},
+
+ {0, 0, 0, 0}
+};
+
+/* Print a usage message and exit with STATUS. */
+static void
+usage (int status)
+{
+ if (status != TAREXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ fputs (_("\
+GNU `tar' saves many files together into a single tape or disk archive, and\n\
+can restore individual files from the archive.\n"),
+ stdout);
+ printf (_("\nUsage: %s [OPTION]... [FILE]...\n\
+\n\
+Examples:\n\
+ %s -cf archive.tar foo bar # Create archive.tar from files foo and bar.\n\
+ %s -tvf archive.tar # List all files in archive.tar verbosely.\n\
+ %s -xf archive.tar # Extract all files from archive.tar.\n"),
+ program_name, program_name, program_name, program_name);
+ fputs (_("\
+\n\
+If a long option shows an argument as mandatory, then it is mandatory\n\
+for the equivalent short option also. Similarly for optional arguments.\n"),
+ stdout);
+ fputs(_("\
+\n\
+Main operation mode:\n\
+ -t, --list list the contents of an archive\n\
+ -x, --extract, --get extract files from an archive\n\
+ -c, --create create a new archive\n\
+ -d, --diff, --compare find differences between archive and file system\n\
+ -r, --append append files to the end of an archive\n\
+ -u, --update only append files newer than copy in archive\n\
+ -A, --catenate append tar files to an archive\n\
+ --concatenate same as -A\n\
+ --delete delete from the archive (not on mag tapes!)\n"),
+ stdout);
+ fputs (_("\
+\n\
+Operation modifiers:\n\
+ -W, --verify attempt to verify the archive after writing it\n\
+ --remove-files remove files after adding them to the archive\n\
+ -k, --keep-old-files don't replace existing files when extracting\n\
+ --overwrite overwrite existing files when extracting\n\
+ --overwrite-dir overwrite directory metadata when extracting\n\
+ -U, --unlink-first remove each file prior to extracting over it\n\
+ --recursive-unlink empty hierarchies prior to extracting directory\n\
+ -S, --sparse handle sparse files efficiently\n\
+ -O, --to-stdout extract files to standard output\n\
+ -G, --incremental handle old GNU-format incremental backup\n\
+ -g, --listed-incremental=FILE\n\
+ handle new GNU-format incremental backup\n\
+ --ignore-failed-read do not exit with nonzero on unreadable files\n"),
+ stdout);
+ fputs (_("\
+\n\
+Handling of file attributes:\n\
+ --owner=NAME force NAME as owner for added files\n\
+ --group=NAME force NAME as group for added files\n\
+ --mode=CHANGES force (symbolic) mode CHANGES for added files\n\
+ --atime-preserve don't change access times on dumped files\n\
+ -m, --modification-time don't extract file modified time\n\
+ --same-owner try extracting files with the same ownership\n\
+ --no-same-owner extract files as yourself\n\
+ --numeric-owner always use numbers for user/group names\n\
+ -p, --same-permissions extract permissions information\n\
+ --no-same-permissions do not extract permissions information\n\
+ --preserve-permissions same as -p\n\
+ -s, --same-order sort names to extract to match archive\n\
+ --preserve-order same as -s\n\
+ --preserve same as both -p and -s\n"),
+ stdout);
+ fputs (_("\
+\n\
+Device selection and switching:\n\
+ -f, --file=ARCHIVE use archive file or device ARCHIVE\n\
+ --force-local archive file is local even if has a colon\n\
+ --rsh-command=COMMAND use remote COMMAND instead of rsh\n\
+ -[0-7][lmh] specify drive and density\n\
+ -M, --multi-volume create/list/extract multi-volume archive\n\
+ -L, --tape-length=NUM change tape after writing NUM x 1024 bytes\n\
+ -F, --info-script=FILE run script at end of each tape (implies -M)\n\
+ --new-volume-script=FILE same as -F FILE\n\
+ --volno-file=FILE use/update the volume number in FILE\n"),
+ stdout);
+ fputs (_("\
+\n\
+Device blocking:\n\
+ -b, --blocking-factor=BLOCKS BLOCKS x 512 bytes per record\n\
+ --record-size=SIZE SIZE bytes per record, multiple of 512\n\
+ -i, --ignore-zeros ignore zeroed blocks in archive (means EOF)\n\
+ -B, --read-full-records reblock as we read (for 4.2BSD pipes)\n"),
+ stdout);
+ fputs (_("\
+\n\
+Archive format selection:\n\
+ -V, --label=NAME create archive with volume name NAME\n\
+ PATTERN at list/extract time, a globbing PATTERN\n\
+ -o, --old-archive, --portability write a V7 format archive\n\
+ --posix write a POSIX format archive\n\
+ -j, --bzip2 filter the archive through bzip2\n\
+ -z, --gzip, --ungzip filter the archive through gzip\n\
+ -Z, --compress, --uncompress filter the archive through compress\n\
+ --use-compress-program=PROG filter through PROG (must accept -d)\n"),
+ stdout);
+ fputs (_("\
+\n\
+Local file selection:\n\
+ -C, --directory=DIR change to directory DIR\n\
+ -T, --files-from=NAME get names to extract or create from file NAME\n\
+ --null -T reads null-terminated names, disable -C\n\
+ --exclude=PATTERN exclude files, given as a PATTERN\n\
+ -X, --exclude-from=FILE exclude patterns listed in FILE\n\
+ --anchored exclude patterns match file name start (default)\n\
+ --no-anchored exclude patterns match after any /\n\
+ --ignore-case exclusion ignores case\n\
+ --no-ignore-case exclusion is case sensitive (default)\n\
+ --wildcards exclude patterns use wildcards (default)\n\
+ --no-wildcards exclude patterns are plain strings\n\
+ --wildcards-match-slash exclude pattern wildcards match '/' (default)\n\
+ --no-wildcards-match-slash exclude pattern wildcards do not match '/'\n\
+ -P, --absolute-names don't strip leading `/'s from file names\n\
+ -h, --dereference dump instead the files symlinks point to\n\
+ --no-recursion avoid descending automatically in directories\n\
+ -l, --one-file-system stay in local file system when creating archive\n\
+ -K, --starting-file=NAME begin at file NAME in the archive\n"),
+ stdout);
+#if !MSDOS
+ fputs (_("\
+ -N, --newer=DATE only store files newer than DATE\n\
+ --newer-mtime=DATE compare date and time when data changed only\n\
+ --after-date=DATE same as -N\n"),
+ stdout);
+#endif
+ fputs (_("\
+ --backup[=CONTROL] backup before removal, choose version control\n\
+ --suffix=SUFFIX backup before removal, override usual suffix\n"),
+ stdout);
+ fputs (_("\
+\n\
+Informative output:\n\
+ --help print this help, then exit\n\
+ --version print tar program version number, then exit\n\
+ -v, --verbose verbosely list files processed\n\
+ --checkpoint print directory names while reading the archive\n\
+ --totals print total bytes written while creating archive\n\
+ -R, --block-number show block number within archive with each message\n\
+ -w, --interactive ask for confirmation for every action\n\
+ --confirmation same as -w\n"),
+ stdout);
+ fputs (_("\
+\n\
+The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
+The version control may be set with --backup or VERSION_CONTROL, values are:\n\
+\n\
+ t, numbered make numbered backups\n\
+ nil, existing numbered if numbered backups exist, simple otherwise\n\
+ never, simple always make simple backups\n"),
+ stdout);
+ printf (_("\
+\n\
+GNU tar cannot read nor produce `--posix' archives. If POSIXLY_CORRECT\n\
+is set in the environment, GNU extensions are disallowed with `--posix'.\n\
+Support for POSIX is only partially implemented, don't count on it yet.\n\
+ARCHIVE may be FILE, HOST:FILE or USER@HOST:FILE; DATE may be a textual date\n\
+or a file name starting with `/' or `.', in which case the file's date is used.\n\
+*This* `tar' defaults to `-f%s -b%d'.\n"),
+ DEFAULT_ARCHIVE, DEFAULT_BLOCKING);
+ fputs (_("\nReport bugs to <bug-tar@gnu.org>.\n"), stdout);
+ }
+ exit (status);
+}
+
+/* Parse the options for tar. */
+
+/* Available option letters are DEHIJQY and aenqy. Some are reserved:
+
+ e exit immediately with a nonzero exit status if unexpected errors occur
+ E use extended headers (draft POSIX headers, that is)
+ I same as T (for compatibility with Solaris tar)
+ n the archive is quickly seekable, so don't worry about random seeks
+ q stop after extracting the first occurrence of the named file
+ y per-file gzip compression
+ Y per-block gzip compression */
+
+#define OPTION_STRING \
+ "-01234567ABC:F:GIK:L:MN:OPRST:UV:WX:Zb:cdf:g:hijklmoprstuvwxyz"
+
+static void
+set_subcommand_option (enum subcommand subcommand)
+{
+ if (subcommand_option != UNKNOWN_SUBCOMMAND
+ && subcommand_option != subcommand)
+ USAGE_ERROR ((0, 0,
+ _("You may not specify more than one `-Acdtrux' option")));
+
+ subcommand_option = subcommand;
+}
+
+static void
+set_use_compress_program_option (const char *string)
+{
+ if (use_compress_program_option && strcmp (use_compress_program_option, string) != 0)
+ USAGE_ERROR ((0, 0, _("Conflicting compression options")));
+
+ use_compress_program_option = string;
+}
+
+static void
+decode_options (int argc, char **argv)
+{
+ int optchar; /* option letter */
+ int input_files; /* number of input files */
+ const char *backup_suffix_string;
+ const char *version_control_string = 0;
+ int exclude_options = EXCLUDE_WILDCARDS;
+
+ /* Set some default option values. */
+
+ subcommand_option = UNKNOWN_SUBCOMMAND;
+ archive_format = DEFAULT_FORMAT;
+ blocking_factor = DEFAULT_BLOCKING;
+ record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+ excluded = new_exclude ();
+ newer_mtime_option = TYPE_MINIMUM (time_t);
+ recursion_option = FNM_LEADING_DIR;
+
+ owner_option = -1;
+ group_option = -1;
+
+ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
+
+ /* Convert old-style tar call by exploding option element and rearranging
+ options accordingly. */
+
+ if (argc > 1 && argv[1][0] != '-')
+ {
+ int new_argc; /* argc value for rearranged arguments */
+ char **new_argv; /* argv value for rearranged arguments */
+ char *const *in; /* cursor into original argv */
+ char **out; /* cursor into rearranged argv */
+ const char *letter; /* cursor into old option letters */
+ char buffer[3]; /* constructed option buffer */
+ const char *cursor; /* cursor in OPTION_STRING */
+
+ /* Initialize a constructed option. */
+
+ buffer[0] = '-';
+ buffer[2] = '\0';
+
+ /* Allocate a new argument array, and copy program name in it. */
+
+ new_argc = argc - 1 + strlen (argv[1]);
+ new_argv = xmalloc (new_argc * sizeof (char *));
+ in = argv;
+ out = new_argv;
+ *out++ = *in++;
+
+ /* Copy each old letter option as a separate option, and have the
+ corresponding argument moved next to it. */
+
+ for (letter = *in++; *letter; letter++)
+ {
+ buffer[1] = *letter;
+ *out++ = xstrdup (buffer);
+ cursor = strchr (OPTION_STRING, *letter);
+ if (cursor && cursor[1] == ':')
+ {
+ if (in < argv + argc)
+ *out++ = *in++;
+ else
+ USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."),
+ *letter));
+ }
+ }
+
+ /* Copy all remaining options. */
+
+ while (in < argv + argc)
+ *out++ = *in++;
+
+ /* Replace the old option list by the new one. */
+
+ argc = new_argc;
+ argv = new_argv;
+ }
+
+ /* Parse all options and non-options as they appear. */
+
+ input_files = 0;
+
+ prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
+
+ while (optchar = getopt_long (argc, argv, OPTION_STRING, long_options, 0),
+ optchar != -1)
+ switch (optchar)
+ {
+ case '?':
+ usage (TAREXIT_FAILURE);
+
+ case 0:
+ break;
+
+ case 1:
+ /* File name or non-parsed option, because of RETURN_IN_ORDER
+ ordering triggered by the leading dash in OPTION_STRING. */
+
+ name_add (optarg);
+ input_files++;
+ break;
+
+ case 'A':
+ set_subcommand_option (CAT_SUBCOMMAND);
+ break;
+
+ case OBSOLETE_BLOCK_COMPRESS:
+ WARN ((0, 0, _("Obsolete option, now implied by --blocking-factor")));
+ break;
+
+ case OBSOLETE_BLOCKING_FACTOR:
+ WARN ((0, 0, _("Obsolete option name replaced by --blocking-factor")));
+ /* Fall through. */
+
+ case 'b':
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
+ && u == (blocking_factor = u)
+ && 0 < blocking_factor
+ && u == (record_size = u * BLOCKSIZE) / BLOCKSIZE))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
+ _("Invalid blocking factor")));
+ }
+ break;
+
+ case OBSOLETE_READ_FULL_RECORDS:
+ WARN ((0, 0,
+ _("Obsolete option name replaced by --read-full-records")));
+ /* Fall through. */
+
+ case 'B':
+ /* Try to reblock input records. For reading 4.2BSD pipes. */
+
+ /* It would surely make sense to exchange -B and -R, but it seems
+ that -B has been used for a long while in Sun tar ans most
+ BSD-derived systems. This is a consequence of the block/record
+ terminology confusion. */
+
+ read_full_records_option = 1;
+ break;
+
+ case 'c':
+ set_subcommand_option (CREATE_SUBCOMMAND);
+ break;
+
+ case 'C':
+ name_add ("-C");
+ name_add (optarg);
+ break;
+
+ case 'd':
+ set_subcommand_option (DIFF_SUBCOMMAND);
+ break;
+
+ case 'f':
+ if (archive_names == allocated_archive_names)
+ {
+ allocated_archive_names *= 2;
+ archive_name_array =
+ xrealloc (archive_name_array,
+ sizeof (const char *) * allocated_archive_names);
+ }
+ archive_name_array[archive_names++] = optarg;
+ break;
+
+ case 'F':
+ /* Since -F is only useful with -M, make it implied. Run this
+ script at the end of each tape. */
+
+ info_script_option = optarg;
+ multi_volume_option = 1;
+ break;
+
+ case 'g':
+ listed_incremental_option = optarg;
+ after_date_option = 1;
+ /* Fall through. */
+
+ case 'G':
+ /* We are making an incremental dump (FIXME: are we?); save
+ directories at the beginning of the archive, and include in each
+ directory its contents. */
+
+ incremental_option = 1;
+ break;
+
+ case 'h':
+ /* Follow symbolic links. */
+
+ dereference_option = 1;
+ break;
+
+ case 'i':
+ /* Ignore zero blocks (eofs). This can't be the default,
+ because Unix tar writes two blocks of zeros, then pads out
+ the record with garbage. */
+
+ ignore_zeros_option = 1;
+ break;
+
+ case 'I':
+ USAGE_ERROR ((0, 0,
+ _("Warning: the -I option is not supported;"
+ " perhaps you meant -j or -T?")));
+ break;
+
+ case 'j':
+ set_use_compress_program_option ("bzip2");
+ break;
+
+ case 'k':
+ /* Don't replace existing files. */
+ old_files_option = KEEP_OLD_FILES;
+ break;
+
+ case 'K':
+ starting_file_option = 1;
+ addname (optarg, 0);
+ break;
+
+ case 'l':
+ /* When dumping directories, don't dump files/subdirectories
+ that are on other filesystems. */
+
+ one_file_system_option = 1;
+ break;
+
+ case 'L':
+ {
+ uintmax_t u;
+ if (xstrtoumax (optarg, 0, 10, &u, "") != LONGINT_OK)
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
+ _("Invalid tape length")));
+ tape_length_option = 1024 * (tarlong) u;
+ multi_volume_option = 1;
+ }
+ break;
+
+ case OBSOLETE_TOUCH:
+ WARN ((0, 0, _("Obsolete option name replaced by --touch")));
+ /* Fall through. */
+
+ case 'm':
+ touch_option = 1;
+ break;
+
+ case 'M':
+ /* Make multivolume archive: when we can't write any more into
+ the archive, re-open it, and continue writing. */
+
+ multi_volume_option = 1;
+ break;
+
+#if !MSDOS
+ case 'N':
+ after_date_option = 1;
+ /* Fall through. */
+
+ case NEWER_MTIME_OPTION:
+ if (newer_mtime_option != TYPE_MINIMUM (time_t))
+ USAGE_ERROR ((0, 0, _("More than one threshold date")));
+
+ if (FILESYSTEM_PREFIX_LEN (optarg) != 0
+ || ISSLASH (*optarg)
+ || *optarg == '.')
+ {
+ struct stat st;
+ if (deref_stat (dereference_option, optarg, &st) != 0)
+ {
+ stat_error (optarg);
+ USAGE_ERROR ((0, 0, _("Date file not found")));
+ }
+ newer_mtime_option = st.st_mtime;
+ }
+ else
+ {
+ newer_mtime_option = get_date (optarg, 0);
+ if (newer_mtime_option == (time_t) -1)
+ WARN ((0, 0, _("Substituting %s for unknown date format %s"),
+ tartime (newer_mtime_option), quote (optarg)));
+ }
+
+ break;
+#endif /* not MSDOS */
+
+ case 'o':
+ if (archive_format == DEFAULT_FORMAT)
+ archive_format = V7_FORMAT;
+ else if (archive_format != V7_FORMAT)
+ USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
+ break;
+
+ case 'O':
+ to_stdout_option = 1;
+ break;
+
+ case 'p':
+ same_permissions_option = 1;
+ break;
+
+ case OBSOLETE_ABSOLUTE_NAMES:
+ WARN ((0, 0, _("Obsolete option name replaced by --absolute-names")));
+ /* Fall through. */
+
+ case 'P':
+ absolute_names_option = 1;
+ break;
+
+ case 'r':
+ set_subcommand_option (APPEND_SUBCOMMAND);
+ break;
+
+ case OBSOLETE_BLOCK_NUMBER:
+ WARN ((0, 0, _("Obsolete option name replaced by --block-number")));
+ /* Fall through. */
+
+ case 'R':
+ /* Print block numbers for debugging bad tar archives. */
+
+ /* It would surely make sense to exchange -B and -R, but it seems
+ that -B has been used for a long while in Sun tar ans most
+ BSD-derived systems. This is a consequence of the block/record
+ terminology confusion. */
+
+ block_number_option = 1;
+ break;
+
+ case 's':
+ /* Names to extr are sorted. */
+
+ same_order_option = 1;
+ break;
+
+ case 'S':
+ sparse_option = 1;
+ break;
+
+ case 't':
+ set_subcommand_option (LIST_SUBCOMMAND);
+ verbose_option++;
+ break;
+
+ case 'T':
+ files_from_option = optarg;
+ break;
+
+ case 'u':
+ set_subcommand_option (UPDATE_SUBCOMMAND);
+ break;
+
+ case 'U':
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+ break;
+
+ case 'v':
+ verbose_option++;
+ break;
+
+ case 'V':
+ volume_label_option = optarg;
+ break;
+
+ case 'w':
+ interactive_option = 1;
+ break;
+
+ case 'W':
+ verify_option = 1;
+ break;
+
+ case 'x':
+ set_subcommand_option (EXTRACT_SUBCOMMAND);
+ break;
+
+ case 'X':
+ if (add_exclude_file (add_exclude, excluded, optarg,
+ exclude_options | recursion_option, '\n')
+ != 0)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, "%s", quotearg_colon (optarg)));
+ }
+ break;
+
+ case 'y':
+ USAGE_ERROR ((0, 0,
+ _("Warning: the -y option is not supported;"
+ " perhaps you meant -j?")));
+ break;
+
+ case 'z':
+ set_use_compress_program_option ("gzip");
+ break;
+
+ case 'Z':
+ set_use_compress_program_option ("compress");
+ break;
+
+ case OBSOLETE_VERSION_CONTROL:
+ WARN ((0, 0, _("Obsolete option name replaced by --backup")));
+ /* Fall through. */
+
+ case ANCHORED_OPTION:
+ exclude_options |= EXCLUDE_ANCHORED;
+ break;
+
+ case BACKUP_OPTION:
+ backup_option = 1;
+ if (optarg)
+ version_control_string = optarg;
+ break;
+
+ case DELETE_OPTION:
+ set_subcommand_option (DELETE_SUBCOMMAND);
+ break;
+
+ case EXCLUDE_OPTION:
+ add_exclude (excluded, optarg, exclude_options | recursion_option);
+ break;
+
+ case IGNORE_CASE_OPTION:
+ exclude_options |= FNM_CASEFOLD;
+ break;
+
+ case GROUP_OPTION:
+ if (! (strlen (optarg) < GNAME_FIELD_SIZE
+ && gname_to_gid (optarg, &group_option)))
+ {
+ uintmax_t g;
+ if (xstrtoumax (optarg, 0, 10, &g, "") == LONGINT_OK
+ && g == (gid_t) g)
+ group_option = g;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
+ _("%s: Invalid group")));
+ }
+ break;
+
+ case MODE_OPTION:
+ mode_option
+ = mode_compile (optarg,
+ MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS);
+ if (mode_option == MODE_INVALID)
+ FATAL_ERROR ((0, 0, _("Invalid mode given on option")));
+ if (mode_option == MODE_MEMORY_EXHAUSTED)
+ xalloc_die ();
+ break;
+
+ case NO_ANCHORED_OPTION:
+ exclude_options &= ~ EXCLUDE_ANCHORED;
+ break;
+
+ case NO_IGNORE_CASE_OPTION:
+ exclude_options &= ~ FNM_CASEFOLD;
+ break;
+
+ case NO_WILDCARDS_OPTION:
+ exclude_options &= ~ EXCLUDE_WILDCARDS;
+ break;
+
+ case NO_WILDCARDS_MATCH_SLASH_OPTION:
+ exclude_options |= FNM_FILE_NAME;
+ break;
+
+ case NULL_OPTION:
+ filename_terminator = '\0';
+ break;
+
+ case OVERWRITE_OPTION:
+ old_files_option = OVERWRITE_OLD_FILES;
+ break;
+
+ case OVERWRITE_DIR_OPTION:
+ old_files_option = OVERWRITE_OLD_DIRS;
+ break;
+
+ case OWNER_OPTION:
+ if (! (strlen (optarg) < UNAME_FIELD_SIZE
+ && uname_to_uid (optarg, &owner_option)))
+ {
+ uintmax_t u;
+ if (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
+ && u == (uid_t) u)
+ owner_option = u;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
+ _("Invalid owner")));
+ }
+ break;
+
+ case POSIX_OPTION:
+#if OLDGNU_COMPATIBILITY
+ if (archive_format == DEFAULT_FORMAT)
+ archive_format = GNU_FORMAT;
+ else if (archive_format != GNU_FORMAT)
+ USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
+#else
+ if (archive_format == DEFAULT_FORMAT)
+ archive_format = POSIX_FORMAT;
+ else if (archive_format != POSIX_FORMAT)
+ USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
+#endif
+ break;
+
+ case PRESERVE_OPTION:
+ same_permissions_option = 1;
+ same_order_option = 1;
+ break;
+
+ case RECORD_SIZE_OPTION:
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
+ && u == (size_t) u))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
+ _("Invalid record size")));
+ record_size = u;
+ if (record_size % BLOCKSIZE != 0)
+ USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
+ BLOCKSIZE));
+ blocking_factor = record_size / BLOCKSIZE;
+ }
+ break;
+
+ case RSH_COMMAND_OPTION:
+ rsh_command_option = optarg;
+ break;
+
+ case SUFFIX_OPTION:
+ backup_option = 1;
+ backup_suffix_string = optarg;
+ break;
+
+ case USE_COMPRESS_PROGRAM_OPTION:
+ set_use_compress_program_option (optarg);
+ break;
+
+ case VOLNO_FILE_OPTION:
+ volno_file_option = optarg;
+ break;
+
+ case WILDCARDS_OPTION:
+ exclude_options |= EXCLUDE_WILDCARDS;
+ break;
+
+ case WILDCARDS_MATCH_SLASH_OPTION:
+ exclude_options &= ~ FNM_FILE_NAME;
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+
+#ifdef DEVICE_PREFIX
+ {
+ int device = optchar - '0';
+ int density;
+ static char buf[sizeof DEVICE_PREFIX + 10];
+ char *cursor;
+
+ density = getopt_long (argc, argv, "lmh", 0, 0);
+ strcpy (buf, DEVICE_PREFIX);
+ cursor = buf + strlen (buf);
+
+#ifdef DENSITY_LETTER
+
+ sprintf (cursor, "%d%c", device, density);
+
+#else /* not DENSITY_LETTER */
+
+ switch (density)
+ {
+ case 'l':
+#ifdef LOW_NUM
+ device += LOW_NUM;
+#endif
+ break;
+
+ case 'm':
+#ifdef MID_NUM
+ device += MID_NUM;
+#else
+ device += 8;
+#endif
+ break;
+
+ case 'h':
+#ifdef HGH_NUM
+ device += HGH_NUM;
+#else
+ device += 16;
+#endif
+ break;
+
+ default:
+ usage (TAREXIT_FAILURE);
+ }
+ sprintf (cursor, "%d", device);
+
+#endif /* not DENSITY_LETTER */
+
+ if (archive_names == allocated_archive_names)
+ {
+ allocated_archive_names *= 2;
+ archive_name_array =
+ xrealloc (archive_name_array,
+ sizeof (const char *) * allocated_archive_names);
+ }
+ archive_name_array[archive_names++] = buf;
+
+ /* FIXME: How comes this works for many archives when buf is
+ not xstrdup'ed? */
+ }
+ break;
+
+#else /* not DEVICE_PREFIX */
+
+ USAGE_ERROR ((0, 0,
+ _("Options `-[0-7][lmh]' not supported by *this* tar")));
+
+#endif /* not DEVICE_PREFIX */
+ }
+
+ /* Handle operands after any "--" argument. */
+ for (; optind < argc; optind++)
+ {
+ name_add (argv[optind]);
+ input_files++;
+ }
+
+ /* Process trivial options. */
+
+ if (show_version)
+ {
+ printf ("tar (GNU %s) %s\n", PACKAGE, VERSION);
+ print_copyright ("2001 Free Software Foundation, Inc.");
+ puts (_("\
+This program comes with NO WARRANTY, to the extent permitted by law.\n\
+You may redistribute it under the terms of the GNU General Public License;\n\
+see the file named COPYING for details."));
+
+ puts (_("Written by John Gilmore and Jay Fenlason."));
+
+ exit (TAREXIT_SUCCESS);
+ }
+
+ if (show_help)
+ usage (TAREXIT_SUCCESS);
+
+ /* Derive option values and check option consistency. */
+
+ if (archive_format == DEFAULT_FORMAT)
+ {
+#if OLDGNU_COMPATIBILITY
+ archive_format = OLDGNU_FORMAT;
+#else
+ archive_format = GNU_FORMAT;
+#endif
+ }
+
+ if (archive_format == GNU_FORMAT && getenv ("POSIXLY_CORRECT"))
+ archive_format = POSIX_FORMAT;
+
+ if ((volume_label_option
+ || incremental_option || multi_volume_option || sparse_option)
+ && archive_format != OLDGNU_FORMAT && archive_format != GNU_FORMAT)
+ USAGE_ERROR ((0, 0,
+ _("GNU features wanted on incompatible archive format")));
+
+ if (archive_names == 0)
+ {
+ /* If no archive file name given, try TAPE from the environment, or
+ else, DEFAULT_ARCHIVE from the configuration process. */
+
+ archive_names = 1;
+ archive_name_array[0] = getenv ("TAPE");
+ if (! archive_name_array[0])
+ archive_name_array[0] = DEFAULT_ARCHIVE;
+ }
+
+ /* Allow multiple archives only with `-M'. */
+
+ if (archive_names > 1 && !multi_volume_option)
+ USAGE_ERROR ((0, 0,
+ _("Multiple archive files requires `-M' option")));
+
+ if (listed_incremental_option
+ && newer_mtime_option != TYPE_MINIMUM (time_t))
+ USAGE_ERROR ((0, 0,
+ _("Cannot combine --listed-incremental with --newer")));
+
+ if (volume_label_option)
+ {
+ size_t volume_label_max_len =
+ (sizeof current_header->header.name
+ - 1 /* for trailing '\0' */
+ - (multi_volume_option
+ ? (sizeof " Volume "
+ - 1 /* for null at end of " Volume " */
+ + INT_STRLEN_BOUND (int) /* for volume number */
+ - 1 /* for sign, as 0 <= volno */)
+ : 0));
+ if (volume_label_max_len < strlen (volume_label_option))
+ USAGE_ERROR ((0, 0,
+ _("%s: Volume label is too long (limit is %lu bytes)"),
+ quotearg_colon (volume_label_option),
+ (unsigned long) volume_label_max_len));
+ }
+
+ /* If ready to unlink hierarchies, so we are for simpler files. */
+ if (recursive_unlink_option)
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+
+ /* Forbid using -c with no input files whatsoever. Check that `-f -',
+ explicit or implied, is used correctly. */
+
+ switch (subcommand_option)
+ {
+ case CREATE_SUBCOMMAND:
+ if (input_files == 0 && !files_from_option)
+ USAGE_ERROR ((0, 0,
+ _("Cowardly refusing to create an empty archive")));
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ case LIST_SUBCOMMAND:
+ case DIFF_SUBCOMMAND:
+ for (archive_name_cursor = archive_name_array;
+ archive_name_cursor < archive_name_array + archive_names;
+ archive_name_cursor++)
+ if (!strcmp (*archive_name_cursor, "-"))
+ request_stdin ("-f");
+ break;
+
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ for (archive_name_cursor = archive_name_array;
+ archive_name_cursor < archive_name_array + archive_names;
+ archive_name_cursor++)
+ if (!strcmp (*archive_name_cursor, "-"))
+ USAGE_ERROR ((0, 0,
+ _("Options `-Aru' are incompatible with `-f -'")));
+
+ default:
+ break;
+ }
+
+ archive_name_cursor = archive_name_array;
+
+ /* Prepare for generating backup names. */
+
+ if (backup_suffix_string)
+ simple_backup_suffix = xstrdup (backup_suffix_string);
+
+ if (backup_option)
+ backup_type = xget_version ("--backup", version_control_string);
+}
+
+/* Tar proper. */
+
+/* Main routine for tar. */
+int
+main (int argc, char **argv)
+{
+#if HAVE_CLOCK_GETTIME
+ if (clock_gettime (CLOCK_REALTIME, &start_timespec) != 0)
+#endif
+ start_time = time (0);
+ program_name = argv[0];
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ exit_status = TAREXIT_SUCCESS;
+ filename_terminator = '\n';
+ set_quoting_style (0, escape_quoting_style);
+
+ /* Pre-allocate a few structures. */
+
+ allocated_archive_names = 10;
+ archive_name_array =
+ xmalloc (sizeof (const char *) * allocated_archive_names);
+ archive_names = 0;
+
+#ifdef SIGCHLD
+ /* System V fork+wait does not work if SIGCHLD is ignored. */
+ signal (SIGCHLD, SIG_DFL);
+#endif
+
+ init_names ();
+
+ /* Decode options. */
+
+ decode_options (argc, argv);
+ name_init (argc, argv);
+
+ /* Main command execution. */
+
+ if (volno_file_option)
+ init_volume_number ();
+
+ switch (subcommand_option)
+ {
+ case UNKNOWN_SUBCOMMAND:
+ USAGE_ERROR ((0, 0,
+ _("You must specify one of the `-Acdtrux' options")));
+
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ update_archive ();
+ break;
+
+ case DELETE_SUBCOMMAND:
+ delete_archive_members ();
+ break;
+
+ case CREATE_SUBCOMMAND:
+ create_archive ();
+ name_close ();
+
+ if (totals_option)
+ print_total_written ();
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ extr_init ();
+ read_and (extract_archive);
+ extract_finish ();
+ break;
+
+ case LIST_SUBCOMMAND:
+ read_and (list_archive);
+ break;
+
+ case DIFF_SUBCOMMAND:
+ diff_init ();
+ read_and (diff_archive);
+ break;
+ }
+
+ if (volno_file_option)
+ closeout_volume_number ();
+
+ /* Dispose of allocated memory, and return. */
+
+ free (archive_name_array);
+ name_term ();
+
+ if (stdlis == stdout && (ferror (stdout) || fclose (stdout) != 0))
+ FATAL_ERROR ((0, 0, _("Error in writing to standard output")));
+ if (exit_status == TAREXIT_FAILURE)
+ error (0, 0, _("Error exit delayed from previous errors"));
+ exit (exit_status);
+}
diff --git a/contrib/tar/src/tar.h b/contrib/tar/src/tar.h
new file mode 100644
index 0000000..ff2977b
--- /dev/null
+++ b/contrib/tar/src/tar.h
@@ -0,0 +1,235 @@
+/* GNU tar Archive Format description.
+
+ Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1996,
+ 1997, 2000, 2001 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* If OLDGNU_COMPATIBILITY is not zero, tar produces archives which, by
+ default, are readable by older versions of GNU tar. This can be
+ overriden by using --posix; in this case, POSIXLY_CORRECT in environment
+ may be set for enforcing stricter conformance. If OLDGNU_COMPATIBILITY
+ is zero or undefined, tar will eventually produces archives which, by
+ default, POSIX compatible; then either using --posix or defining
+ POSIXLY_CORRECT enforces stricter conformance.
+
+ This #define will disappear in a few years. FP, June 1995. */
+#define OLDGNU_COMPATIBILITY 1
+
+/* tar Header Block, from POSIX 1003.1-1990. */
+
+/* POSIX header. */
+
+struct posix_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[155]; /* 345 */
+ /* 500 */
+};
+
+#define TMAGIC "ustar" /* ustar and a null */
+#define TMAGLEN 6
+#define TVERSION "00" /* 00 and no null */
+#define TVERSLEN 2
+
+/* Values used in typeflag field. */
+#define REGTYPE '0' /* regular file */
+#define AREGTYPE '\0' /* regular file */
+#define LNKTYPE '1' /* link */
+#define SYMTYPE '2' /* reserved */
+#define CHRTYPE '3' /* character special */
+#define BLKTYPE '4' /* block special */
+#define DIRTYPE '5' /* directory */
+#define FIFOTYPE '6' /* FIFO special */
+#define CONTTYPE '7' /* reserved */
+
+/* Bits used in the mode field, values in octal. */
+#define TSUID 04000 /* set UID on execution */
+#define TSGID 02000 /* set GID on execution */
+#define TSVTX 01000 /* reserved */
+ /* file permissions */
+#define TUREAD 00400 /* read by owner */
+#define TUWRITE 00200 /* write by owner */
+#define TUEXEC 00100 /* execute/search by owner */
+#define TGREAD 00040 /* read by group */
+#define TGWRITE 00020 /* write by group */
+#define TGEXEC 00010 /* execute/search by group */
+#define TOREAD 00004 /* read by other */
+#define TOWRITE 00002 /* write by other */
+#define TOEXEC 00001 /* execute/search by other */
+
+/* tar Header Block, GNU extensions. */
+
+/* In GNU tar, SYMTYPE is for to symbolic links, and CONTTYPE is for
+ contiguous files, so maybe disobeying the `reserved' comment in POSIX
+ header description. I suspect these were meant to be used this way, and
+ should not have really been `reserved' in the published standards. */
+
+/* *BEWARE* *BEWARE* *BEWARE* that the following information is still
+ boiling, and may change. Even if the OLDGNU format description should be
+ accurate, the so-called GNU format is not yet fully decided. It is
+ surely meant to use only extensions allowed by POSIX, but the sketch
+ below repeats some ugliness from the OLDGNU format, which should rather
+ go away. Sparse files should be saved in such a way that they do *not*
+ require two passes at archive creation time. Huge files get some POSIX
+ fields to overflow, alternate solutions have to be sought for this. */
+
+/* Descriptor for a single file hole. */
+
+struct sparse
+{ /* byte offset */
+ char offset[12]; /* 0 */
+ char numbytes[12]; /* 12 */
+ /* 24 */
+};
+
+/* Sparse files are not supported in POSIX ustar format. For sparse files
+ with a POSIX header, a GNU extra header is provided which holds overall
+ sparse information and a few sparse descriptors. When an old GNU header
+ replaces both the POSIX header and the GNU extra header, it holds some
+ sparse descriptors too. Whether POSIX or not, if more sparse descriptors
+ are still needed, they are put into as many successive sparse headers as
+ necessary. The following constants tell how many sparse descriptors fit
+ in each kind of header able to hold them. */
+
+#define SPARSES_IN_EXTRA_HEADER 16
+#define SPARSES_IN_OLDGNU_HEADER 4
+#define SPARSES_IN_SPARSE_HEADER 21
+
+/* The GNU extra header contains some information GNU tar needs, but not
+ foreseen in POSIX header format. It is only used after a POSIX header
+ (and never with old GNU headers), and immediately follows this POSIX
+ header, when typeflag is a letter rather than a digit, so signaling a GNU
+ extension. */
+
+struct extra_header
+{ /* byte offset */
+ char atime[12]; /* 0 */
+ char ctime[12]; /* 12 */
+ char offset[12]; /* 24 */
+ char realsize[12]; /* 36 */
+ char longnames[4]; /* 48 */
+ char unused_pad1[68]; /* 52 */
+ struct sparse sp[SPARSES_IN_EXTRA_HEADER];
+ /* 120 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* Extension header for sparse files, used immediately after the GNU extra
+ header, and used only if all sparse information cannot fit into that
+ extra header. There might even be many such extension headers, one after
+ the other, until all sparse information has been recorded. */
+
+struct sparse_header
+{ /* byte offset */
+ struct sparse sp[SPARSES_IN_SPARSE_HEADER];
+ /* 0 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* The old GNU format header conflicts with POSIX format in such a way that
+ POSIX archives may fool old GNU tar's, and POSIX tar's might well be
+ fooled by old GNU tar archives. An old GNU format header uses the space
+ used by the prefix field in a POSIX header, and cumulates information
+ normally found in a GNU extra header. With an old GNU tar header, we
+ never see any POSIX header nor GNU extra header. Supplementary sparse
+ headers are allowed, however. */
+
+struct oldgnu_header
+{ /* byte offset */
+ char unused_pad1[345]; /* 0 */
+ char atime[12]; /* 345 */
+ char ctime[12]; /* 357 */
+ char offset[12]; /* 369 */
+ char longnames[4]; /* 381 */
+ char unused_pad2; /* 385 */
+ struct sparse sp[SPARSES_IN_OLDGNU_HEADER];
+ /* 386 */
+ char isextended; /* 482 */
+ char realsize[12]; /* 483 */
+ /* 495 */
+};
+
+/* OLDGNU_MAGIC uses both magic and version fields, which are contiguous.
+ Found in an archive, it indicates an old GNU header format, which will be
+ hopefully become obsolescent. With OLDGNU_MAGIC, uname and gname are
+ valid, though the header is not truly POSIX conforming. */
+#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
+
+/* The standards committee allows only capital A through capital Z for
+ user-defined expansion. */
+
+/* This is a dir entry that contains the names of files that were in the
+ dir at the time the dump was made. */
+#define GNUTYPE_DUMPDIR 'D'
+
+/* Identifies the *next* file on the tape as having a long linkname. */
+#define GNUTYPE_LONGLINK 'K'
+
+/* Identifies the *next* file on the tape as having a long name. */
+#define GNUTYPE_LONGNAME 'L'
+
+/* This is the continuation of a file that began on another volume. */
+#define GNUTYPE_MULTIVOL 'M'
+
+/* For storing filenames that do not fit into the main header. */
+#define GNUTYPE_NAMES 'N'
+
+/* This is for sparse files. */
+#define GNUTYPE_SPARSE 'S'
+
+/* This file is a tape/volume header. Ignore it on extraction. */
+#define GNUTYPE_VOLHDR 'V'
+
+/* tar Header Block, overall structure. */
+
+/* tar files are made in basic blocks of this size. */
+#define BLOCKSIZE 512
+
+enum archive_format
+{
+ DEFAULT_FORMAT, /* format to be decided later */
+ V7_FORMAT, /* old V7 tar format */
+ OLDGNU_FORMAT, /* GNU format as per before tar 1.12 */
+ POSIX_FORMAT, /* restricted, pure POSIX format */
+ GNU_FORMAT /* POSIX format with GNU extensions */
+};
+
+union block
+{
+ char buffer[BLOCKSIZE];
+ struct posix_header header;
+ struct extra_header extra_header;
+ struct oldgnu_header oldgnu_header;
+ struct sparse_header sparse_header;
+};
+
+/* End of Format description. */
diff --git a/contrib/tar/src/update.c b/contrib/tar/src/update.c
new file mode 100644
index 0000000..754d321
--- /dev/null
+++ b/contrib/tar/src/update.c
@@ -0,0 +1,195 @@
+/* Update a tar archive.
+
+ Copyright (C) 1988, 1992, 1994, 1996, 1997, 1999, 2000, 2001 Free
+ Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by 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 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* Implement the 'r', 'u' and 'A' options for tar. 'A' means that the
+ file names are tar files, and they should simply be appended to the end
+ of the archive. No attempt is made to record the reads from the args; if
+ they're on raw tape or something like that, it'll probably lose... */
+
+#include "system.h"
+#include <quotearg.h>
+#include "common.h"
+
+/* FIXME: This module should not directly handle the following variable,
+ instead, this should be done in buffer.c only. */
+extern union block *current_block;
+
+/* We've hit the end of the old stuff, and its time to start writing new
+ stuff to the tape. This involves seeking back one record and
+ re-writing the current record (which has been changed). */
+int time_to_start_writing;
+
+/* Pointer to where we started to write in the first record we write out.
+ This is used if we can't backspace the output and have to null out the
+ first part of the record. */
+char *output_start;
+
+/* Catenate file PATH to the archive without creating a header for it.
+ It had better be a tar file or the archive is screwed. */
+static void
+append_file (char *path)
+{
+ int handle = open (path, O_RDONLY | O_BINARY);
+ struct stat stat_data;
+
+ if (handle < 0)
+ {
+ open_error (path);
+ return;
+ }
+
+ if (fstat (handle, &stat_data) != 0)
+ stat_error (path);
+ else
+ {
+ off_t bytes_left = stat_data.st_size;
+
+ while (bytes_left > 0)
+ {
+ union block *start = find_next_block ();
+ size_t buffer_size = available_space_after (start);
+ ssize_t status;
+ char buf[UINTMAX_STRSIZE_BOUND];
+
+ if (bytes_left < buffer_size)
+ {
+ buffer_size = bytes_left;
+ status = buffer_size % BLOCKSIZE;
+ if (status)
+ memset (start->buffer + bytes_left, 0, BLOCKSIZE - status);
+ }
+
+ status = safe_read (handle, start->buffer, buffer_size);
+ if (status < 0)
+ read_fatal_details (path, stat_data.st_size - bytes_left,
+ buffer_size);
+ if (status == 0)
+ FATAL_ERROR ((0, 0, _("%s: File shrank by %s bytes"),
+ quotearg_colon (path),
+ STRINGIFY_BIGINT (bytes_left, buf)));
+
+ bytes_left -= status;
+
+ set_next_block_after (start + (status - 1) / BLOCKSIZE);
+ }
+ }
+
+ if (close (handle) != 0)
+ close_error (path);
+}
+
+/* Implement the 'r' (add files to end of archive), and 'u' (add files
+ to end of archive if they aren't there, or are more up to date than
+ the version in the archive) commands. */
+void
+update_archive (void)
+{
+ enum read_header previous_status = HEADER_STILL_UNREAD;
+ int found_end = 0;
+
+ name_gather ();
+ open_archive (ACCESS_UPDATE);
+
+ while (!found_end)
+ {
+ enum read_header status = read_header (0);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ abort ();
+
+ case HEADER_SUCCESS:
+ {
+ struct name *name;
+
+ if (subcommand_option == UPDATE_SUBCOMMAND
+ && (name = name_scan (current_file_name), name))
+ {
+ struct stat s;
+ enum archive_format unused;
+
+ decode_header (current_header, &current_stat, &unused, 0);
+ chdir_do (name->change_dir);
+ if (deref_stat (dereference_option, current_file_name, &s) == 0
+ && s.st_mtime <= current_stat.st_mtime)
+ add_avoided_name (current_file_name);
+ }
+ skip_member ();
+ break;
+ }
+
+ case HEADER_ZERO_BLOCK:
+ current_block = current_header;
+ found_end = 1;
+ break;
+
+ case HEADER_END_OF_FILE:
+ found_end = 1;
+ break;
+
+ case HEADER_FAILURE:
+ set_next_block_after (current_header);
+ switch (previous_status)
+ {
+ case HEADER_STILL_UNREAD:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_SUCCESS:
+ case HEADER_ZERO_BLOCK:
+ ERROR ((0, 0, _("Skipping to next header")));
+ /* Fall through. */
+
+ case HEADER_FAILURE:
+ break;
+
+ case HEADER_END_OF_FILE:
+ abort ();
+ }
+ break;
+ }
+
+ previous_status = status;
+ }
+
+ reset_eof ();
+ time_to_start_writing = 1;
+ output_start = current_block->buffer;
+
+ {
+ char *path;
+
+ while (path = name_from_list (), path)
+ {
+ if (excluded_name (path))
+ continue;
+ if (interactive_option && !confirm ("add", path))
+ continue;
+ if (subcommand_option == CAT_SUBCOMMAND)
+ append_file (path);
+ else
+ dump_file (path, 1, (dev_t) 0);
+ }
+ }
+
+ write_eot ();
+ close_archive ();
+ names_notfound ();
+}
OpenPOWER on IntegriCloud