diff options
Diffstat (limited to 'contrib/tar/src')
-rw-r--r-- | contrib/tar/src/arith.h | 27 | ||||
-rw-r--r-- | contrib/tar/src/buffer.c | 1619 | ||||
-rw-r--r-- | contrib/tar/src/common.h | 586 | ||||
-rw-r--r-- | contrib/tar/src/compare.c | 823 | ||||
-rw-r--r-- | contrib/tar/src/create.c | 1552 | ||||
-rw-r--r-- | contrib/tar/src/delete.c | 364 | ||||
-rw-r--r-- | contrib/tar/src/extract.c | 1333 | ||||
-rw-r--r-- | contrib/tar/src/incremen.c | 584 | ||||
-rw-r--r-- | contrib/tar/src/list.c | 1211 | ||||
-rw-r--r-- | contrib/tar/src/mangle.c | 121 | ||||
-rw-r--r-- | contrib/tar/src/misc.c | 854 | ||||
-rw-r--r-- | contrib/tar/src/names.c | 976 | ||||
-rw-r--r-- | contrib/tar/src/rmt.c | 576 | ||||
-rw-r--r-- | contrib/tar/src/rmt.h | 93 | ||||
-rw-r--r-- | contrib/tar/src/rtapelib.c | 723 | ||||
-rw-r--r-- | contrib/tar/src/system.h | 587 | ||||
-rw-r--r-- | contrib/tar/src/tar.c | 1366 | ||||
-rw-r--r-- | contrib/tar/src/tar.h | 235 | ||||
-rw-r--r-- | contrib/tar/src/update.c | 195 |
19 files changed, 13825 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..ae6eb5d --- /dev/null +++ b/contrib/tar/src/buffer.c @@ -0,0 +1,1619 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#include "system.h" + +#include <signal.h> + +#if __FreeBSD__ +# include <paths.h> +#else +# define _PATH_BSHELL "/bin/sh" +#endif + +#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 */ +static pid_t grandchild_pid; + +/* 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]; + 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); +} + +static void +sig_propagate(int sig) +{ + kill (grandchild_pid, sig); + exit (TAREXIT_FAILURE); +} + +/* Set ARCHIVE for uncompressing, then reading an archive. */ +static void +child_open_for_uncompress (void) +{ + int parent_pipe[2]; + int child_pipe[2]; + 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! */ + signal (SIGTERM, sig_propagate); + + /* 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 (¤t_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 (fast_read_option && namelist_freed && child_pid > 0) + kill(child_pid, SIGTERM); + + 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 (!fast_read_option || !namelist_freed) + 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 = _PATH_BSHELL; + child = xfork (); + if (child == 0) + { + execlp (shell, "-sh", "-i", (char *) 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..40e0ab6 --- /dev/null +++ b/contrib/tar/src/common.h @@ -0,0 +1,586 @@ +/* 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. */ + +/* $FreeBSD$ */ + +/* 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; + +/* Boolean value. */ +GLOBAL int fast_read_option; + +/* 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; + +/* Boolean value. */ +GLOBAL int namelist_freed; + +/* 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..da9dc33 --- /dev/null +++ b/contrib/tar/src/compare.c @@ -0,0 +1,823 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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) + { + report_difference (_("does not exist")); + } + 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, ¤t_stat, ¤t_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); + + 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..e1f2dda --- /dev/null +++ b/contrib/tar/src/create.c @@ -0,0 +1,1552 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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 (¤t_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, ¤t_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, ¤t_stat); + + but since they'd interpret DIRTYPE blocks as regular + files, we'd better put the / on the name. */ + + header = start_header (namebuf, ¤t_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 (¤t_link_name, link_name); + + current_stat.st_size = 0; + header = start_header (p, ¤t_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, ¤t_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, ¤t_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); + 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 (¤t_link_name, buffer); + + current_stat.st_size = 0; /* force 0 size on symlink */ + header = start_header (p, ¤t_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, ¤t_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..3032da0 --- /dev/null +++ b/contrib/tar/src/extract.c @@ -0,0 +1,1333 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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; +#ifndef __FreeBSD__ + same_permissions_option += we_are_root; +#endif + 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; +#ifdef __FreeBSD__ + same_permissions_option++; +#endif + } +} + +/* 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 & ~ current_umask) != 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, + ¤t_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, ¤t_stat, ¤t_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 = ¤t_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, ¤t_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, ¤t_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; + size_t skiplinkcrud; + + if (absolute_names_option) + skiplinkcrud = 0; + else { + skiplinkcrud = FILESYSTEM_PREFIX_LEN (current_link_name); + while (ISSLASH (current_link_name[skiplinkcrud])) + skiplinkcrud++; + } + + /* MSDOS does not implement links. However, djgpp's link() actually + copies the file. */ + status = link (current_link_name + skiplinkcrud, 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, ¤t_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, ¤t_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 (); + + { + struct stat st; + if (stat (CURRENT_FILE_NAME, &st) != 0 || !S_ISDIR (st.st_mode)) + 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, ¤t_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..9c68ae9 --- /dev/null +++ b/contrib/tar/src/list.c @@ -0,0 +1,1211 @@ +/* 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. */ + +/* $FreeBSD$ */ + +/* Define to non-zero for forcing old ctime format instead of ISO format. */ +#undef USE_OLD_CTIME + +#include "system.h" +#include <quotearg.h> +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif + +#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); + /* check if the namelist got emptied during the course of reading */ + /* the tape, if so stop by setting status to EOF */ + if (namelist_freed) + status = HEADER_END_OF_FILE; + 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, ¤t_stat, ¤t_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 = ¤t_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 (¤t_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 (¤t_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) +{ +#if !defined(__FreeBSD__) || !defined(HAVE_LANGINFO_CODESET) + 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; +#else /* __FreeBSD__ */ + static char buffer[80]; + static int d_first = -1; + + if (d_first < 0) + d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); + strftime(buffer, sizeof(buffer), d_first ? "%e %b %R %Y" : "%b %e %R %Y", + localtime(&t)); + return buffer; +#endif /* __FreeBSD__ */ +} + +/* 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..8ece9c6 --- /dev/null +++ b/contrib/tar/src/misc.c @@ -0,0 +1,854 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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)); + + 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; + ERROR ((0, e, _("%s: Cannot change mode to 0%o"), + quotearg_colon (name), mode)); +} + +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..2642515 --- /dev/null +++ b/contrib/tar/src/names.c @@ -0,0 +1,976 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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; + struct name *tmpnlp; + + 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); + if (fast_read_option) + { + /* remove the current entry, since we found a match */ + if (namelist->next == NULL) + { + /* the list contains one element */ + free(namelist); + namelist = 0; + nametail = &namelist; + /* set a boolean to decide wether we started with a */ + /* non-empty namelist, that was emptied */ + namelist_freed = 1; + } + else + { + if (cursor == namelist) + { + /* the first element is the one */ + tmpnlp = namelist->next; + free(namelist); + namelist = tmpnlp; + } + else + { + tmpnlp = namelist; + while (tmpnlp->next != cursor) + tmpnlp = tmpnlp->next; + tmpnlp->next = cursor->next; + free(cursor); + } + } + } + + /* 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..295974e --- /dev/null +++ b/contrib/tar/src/rtapelib.c @@ -0,0 +1,723 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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) + remote_shell = getenv("TAR_RSH"); + + 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) + execlp (remote_shell, remote_shell_basename, "-l", remote_user, + remote_host, "/etc/rmt", (char *) 0); + else + execlp (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..7d872fa --- /dev/null +++ b/contrib/tar/src/tar.c @@ -0,0 +1,1366 @@ +/* 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. */ + +/* $FreeBSD$ */ + +#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'}, + {"bzip", no_argument, 0, 'j'}, + {"bzip2", no_argument, 0, 'j'}, + {"bunzip2", 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'}, + {"fast-read", no_argument, &fast_read_option, 1}, + {"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}, + {"norecurse", no_argument, &recursion_option, 0}, + {"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", no_argument, 0, 'U'}, + {"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,\n\ + --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\ + --fast-read stop after desired names in archive have been found\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\ + --show-omitted-dirs show omitted directories while processing the\n\ + archive\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 it 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, -y, --bzip, --bzip2, --bunzip2 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, -I, --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 a globbing 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\ + -n, --norecurse\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 with creation time newer than\n\ + DATE\n\ + --newer-mtime=DATE only store files with modification time newer\n\ + than DATE\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 number of buffer reads/writes\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:GI:K:L:MnN: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; + namelist_freed = 0; + + 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 + 1) * 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++; + + /* And NULL terminate the argv[] array */ + *out++ = NULL; + + /* 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 'j': + case 'y': + 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; + + case 'n': + recursion_option = 0; + 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': + case 'I': + 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 '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]; + (void) 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, ¤t_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 (); +} |