-/* Tar -- a tape archiver.
- Copyright (C) 1988, 1992 Free Software Foundation
+/* A tar (tape archiver) program.
-This file is part of GNU Tar.
+ Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
+ 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
-GNU Tar is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
+ Written by John Gilmore, starting 1985-08-25.
-GNU Tar is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
+ 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.
-You should have received a copy of the GNU General Public License
-along with GNU Tar; see the file COPYING. If not, write to
-the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+ 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.
-/*
- * A tar (tape archiver) program.
- *
- * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85.
- */
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
-#include <stdio.h>
-#include <sys/types.h> /* Needed for typedefs in tar.h */
-#include "getopt.h"
-#include "regex.h"
-#include "fnmatch.h"
+#include <system.h>
-/*
- * The following causes "tar.h" to produce definitions of all the
- * global variables, rather than just "extern" declarations of them.
- */
-#define TAR_EXTERN /**/
-#include "tar.h"
-
-#include "port.h"
-
-#if defined(_POSIX_VERSION) || defined(DIRENT)
-#include <dirent.h>
-#ifdef direct
-#undef direct
-#endif /* direct */
-#define direct dirent
-#define DP_NAMELEN(x) strlen((x)->d_name)
-#endif /* _POSIX_VERSION or DIRENT */
-#if !defined(_POSIX_VERSION) && !defined(DIRENT) && defined(BSD42)
-#include <sys/dir.h>
-#define DP_NAMELEN(x) (x)->d_namlen
-#endif /* not _POSIX_VERSION and BSD42 */
-#ifdef __MSDOS__
-#include "msd_dir.h"
-#define DP_NAMELEN(x) (x)->d_namlen
-#define direct dirent
+#include <fnmatch.h>
+#include <argp.h>
+
+#include <signal.h>
+#if ! defined SIGCHLD && defined SIGCLD
+# define SIGCHLD SIGCLD
#endif
-#if defined(USG) && !defined(_POSIX_VERSION) && !defined(DIRENT)
-#include <ndir.h>
-#define DP_NAMELEN(x) strlen((x)->d_name)
-#endif /* USG and not _POSIX_VERSION and not DIRENT */
-
-/*
- * We should use a conversion routine that does reasonable error
- * checking -- atoi doesn't. For now, punt. FIXME.
- */
-#define intconv atoi
-PTR ck_malloc ();
-PTR ck_realloc ();
-extern int getoldopt ();
-extern void read_and ();
-extern void list_archive ();
-extern void extract_archive ();
-extern void diff_archive ();
-extern void create_archive ();
-extern void update_archive ();
-extern void junk_archive ();
-extern void init_volume_number ();
-extern void closeout_volume_number ();
-
-/* JF */
-extern time_t get_date ();
-
-time_t new_time;
-
-static FILE *namef; /* File to read names from */
-static char **n_argv; /* Argv used by name routines */
-static int n_argc; /* Argc used by name routines */
-static char **n_ind; /* Store an array of names */
-static int n_indalloc; /* How big is the array? */
-static int n_indused; /* How many entries does it have? */
-static int n_indscan; /* How many of the entries have we scanned? */
-
-
-extern FILE *msg_file;
-
-int check_exclude ();
-void add_exclude ();
-void add_exclude_file ();
-void addname ();
-void describe ();
-void diff_init ();
-void extr_init ();
-int is_regex ();
-void name_add ();
-void name_init ();
-void options ();
-char *un_quote_string ();
-
-#ifndef S_ISLNK
-#define lstat stat
+
+/* 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 <getdate.h>
+#include <localedir.h>
+#include <rmt.h>
+#include <prepargs.h>
+#include <quotearg.h>
+#include <xstrtol.h>
+
+/* Local declarations. */
+
+#ifndef DEFAULT_ARCHIVE_FORMAT
+# define DEFAULT_ARCHIVE_FORMAT GNU_FORMAT
#endif
-#ifndef DEFBLOCKING
-#define DEFBLOCKING 20
+#ifndef DEFAULT_ARCHIVE
+# define DEFAULT_ARCHIVE "tar.out"
#endif
-#ifndef DEF_AR_FILE
-#define DEF_AR_FILE "tar.out"
+#ifndef DEFAULT_BLOCKING
+# define DEFAULT_BLOCKING 20
+#endif
+
+\f
+/* 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';
+ }
+}
+
+static struct fmttab {
+ char const *name;
+ enum archive_format fmt;
+} const fmttab[] = {
+ { "v7", V7_FORMAT },
+ { "oldgnu", OLDGNU_FORMAT },
+ { "ustar", USTAR_FORMAT },
+ { "posix", POSIX_FORMAT },
+#if 0 /* not fully supported yet */
+ { "star", STAR_FORMAT },
#endif
+ { "gnu", GNU_FORMAT },
+ { "pax", POSIX_FORMAT }, /* An alias for posix */
+ { NULL, 0 }
+};
+
+static void
+set_archive_format (char const *name)
+{
+ struct fmttab const *p;
+
+ for (p = fmttab; strcmp (p->name, name) != 0; )
+ if (! (++p)->name)
+ USAGE_ERROR ((0, 0, _("%s: Invalid archive format"),
+ quotearg_colon (name)));
+
+ archive_format = p->fmt;
+}
+
+static const char *
+archive_format_string (enum archive_format fmt)
+{
+ struct fmttab const *p;
+
+ for (p = fmttab; p->name; p++)
+ if (p->fmt == fmt)
+ return p->name;
+ return "unknown?";
+}
+
+#define FORMAT_MASK(n) (1<<(n))
+
+static void
+assert_format(unsigned fmt_mask)
+{
+ if ((FORMAT_MASK(archive_format) & fmt_mask) == 0)
+ USAGE_ERROR ((0, 0,
+ _("GNU features wanted on incompatible archive format")));
+}
+
+
+\f
+/* 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 nongraphic characters as pseudo short option
- characters, starting (for no particular reason) with character 10. */
+ short option, we use non-characters as pseudo short options,
+ starting at CHAR_MAX + 1 and going upwards. */
-struct option long_options[] =
+enum
{
- {"create", 0, 0, 'c'},
- {"append", 0, 0, 'r'},
- {"extract", 0, 0, 'x'},
- {"get", 0, 0, 'x'},
- {"list", 0, 0, 't'},
- {"update", 0, 0, 'u'},
- {"catenate", 0, 0, 'A'},
- {"concatenate", 0, 0, 'A'},
- {"compare", 0, 0, 'd'},
- {"diff", 0, 0, 'd'},
- {"delete", 0, 0, 14},
- {"help", 0, 0, 12},
-
- {"null", 0, 0, 16},
- {"directory", 1, 0, 'C'},
- {"record-number", 0, &f_sayblock, 1},
- {"files-from", 1, 0, 'T'},
- {"label", 1, 0, 'V'},
- {"exclude-from", 1, 0, 'X'},
- {"exclude", 1, 0, 15},
- {"file", 1, 0, 'f'},
- {"block-size", 1, 0, 'b'},
- {"version", 0, 0, 11},
- {"verbose", 0, 0, 'v'},
- {"totals", 0, &f_totals, 1},
-
- {"read-full-blocks", 0, &f_reblock, 1},
- {"starting-file", 1, 0, 'K'},
- {"to-stdout", 0, &f_exstdout, 1},
- {"ignore-zeros", 0, &f_ignorez, 1},
- {"keep-old-files", 0, 0, 'k'},
- {"uncompress", 0, &f_compress, 1},
- {"same-permissions", 0, &f_use_protection, 1},
- {"preserve-permissions", 0, &f_use_protection, 1},
- {"modification-time", 0, &f_modified, 1},
- {"preserve", 0, 0, 10},
- {"same-order", 0, &f_sorted_names, 1},
- {"same-owner", 0, &f_do_chown, 1},
- {"preserve-order", 0, &f_sorted_names, 1},
-
- {"newer", 1, 0, 'N'},
- {"after-date", 1, 0, 'N'},
- {"newer-mtime", 1, 0, 13},
- {"incremental", 0, 0, 'G'},
- {"listed-incremental", 1, 0, 'g'},
- {"multi-volume", 0, &f_multivol, 1},
- {"info-script", 1, 0, 'F'},
- {"absolute-paths", 0, &f_absolute_paths, 1},
- {"interactive", 0, &f_confirm, 1},
- {"confirmation", 0, &f_confirm, 1},
-
- {"verify", 0, &f_verify, 1},
- {"dereference", 0, &f_follow_links, 1},
- {"one-file-system", 0, &f_local_filesys, 1},
- {"old-archive", 0, 0, 'o'},
- {"portability", 0, 0, 'o'},
- {"compress", 0, &f_compress, 1},
- {"compress-block", 0, &f_compress, 2},
- {"sparse", 0, &f_sparse_files, 1},
- {"tape-length", 1, 0, 'L'},
- {"remove-files", 0, &f_remove_files, 1},
- {"ignore-failed-read", 0, &f_ignore_failed_read, 1},
- {"checkpoint", 0, &f_checkpoint, 1},
- {"show-omitted-dirs", 0, &f_show_omitted_dirs, 1},
- {"volno-file", 1, 0, 17},
- {"force-local", 0, &f_force_local, 1},
- {"atime-preserve", 0, &f_atime_preserve, 1},
-
- {0, 0, 0, 0}
+ ANCHORED_OPTION = CHAR_MAX + 1,
+ ATIME_PRESERVE_OPTION,
+ BACKUP_OPTION,
+ CHECKPOINT_OPTION,
+ CHECK_LINKS_OPTION,
+ DELETE_OPTION,
+ EXCLUDE_OPTION,
+ EXCLUDE_CACHES_OPTION,
+ FORCE_LOCAL_OPTION,
+ GROUP_OPTION,
+ HANG_OPTION,
+ IGNORE_CASE_OPTION,
+ IGNORE_COMMAND_ERROR_OPTION,
+ IGNORE_FAILED_READ_OPTION,
+ INDEX_FILE_OPTION,
+ KEEP_NEWER_FILES_OPTION,
+ LICENSE_OPTION,
+ MODE_OPTION,
+ NEWER_MTIME_OPTION,
+ NO_ANCHORED_OPTION,
+ NO_IGNORE_CASE_OPTION,
+ NO_IGNORE_COMMAND_ERROR_OPTION,
+ NO_OVERWRITE_DIR_OPTION,
+ NO_RECURSION_OPTION,
+ NO_SAME_OWNER_OPTION,
+ NO_SAME_PERMISSIONS_OPTION,
+ NO_UNQUOTE_OPTION,
+ NO_WILDCARDS_OPTION,
+ NO_WILDCARDS_MATCH_SLASH_OPTION,
+ NULL_OPTION,
+ NUMERIC_OWNER_OPTION,
+ OCCURRENCE_OPTION,
+ OLD_ARCHIVE_OPTION,
+ ONE_FILE_SYSTEM_OPTION,
+ OVERWRITE_OPTION,
+ OWNER_OPTION,
+ PAX_OPTION,
+ POSIX_OPTION,
+ PRESERVE_OPTION,
+ RECORD_SIZE_OPTION,
+ RECURSION_OPTION,
+ RECURSIVE_UNLINK_OPTION,
+ REMOVE_FILES_OPTION,
+ RMT_COMMAND_OPTION,
+ RSH_COMMAND_OPTION,
+ SAME_OWNER_OPTION,
+ SHOW_DEFAULTS_OPTION,
+ SHOW_OMITTED_DIRS_OPTION,
+ STRIP_COMPONENTS_OPTION,
+ SUFFIX_OPTION,
+ TO_COMMAND_OPTION,
+ TOTALS_OPTION,
+ UNQUOTE_OPTION,
+ USAGE_OPTION,
+ USE_COMPRESS_PROGRAM_OPTION,
+ UTC_OPTION,
+ VERSION_OPTION,
+ VOLNO_FILE_OPTION,
+ WILDCARDS_OPTION,
+ WILDCARDS_MATCH_SLASH_OPTION
};
-/*
- * Main routine for tar.
- */
+const char *argp_program_version = "tar (" PACKAGE_NAME ") " VERSION;
+const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";
+static char doc[] = N_("GNU `tar' saves many files together into a single tape or disk archive, and can restore individual files from the archive.\n\
+\n\
+Examples:\n\
+ tar -cf archive.tar foo bar # Create archive.tar from files foo and bar.\n\
+ tar -tvf archive.tar # List all files in archive.tar verbosely.\n\
+ tar -xf archive.tar # Extract all files from archive.tar.\n\
+\vThe 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\
+ none, off never make backups\n\
+ t, numbered make numbered backups\n\
+ nil, existing numbered if numbered backups exist, simple otherwise\n\
+ never, simple always make simple backups\n");
+
+
+/* NOTE:
+
+ Available option letters are DEIJQY and aeqy. Consider the following
+ assignments:
+
+ [For Solaris tar compatibility]
+ e exit immediately with a nonzero exit status if unexpected errors occur
+ E use extended headers (--format=posix)
+ [q alias for --occurrence=1 =/= this would better be used for quiet?]
+ [I same as T =/= will harm star compatibility]
+
+ y per-file gzip compression
+ Y per-block gzip compression */
+
+static struct argp_option options[] = {
+ {NULL, 0, NULL, 0,
+ N_("Main operation mode:"), 0},
+
+ {"list", 't', 0, 0,
+ N_("list the contents of an archive"), 10 },
+ {"extract", 'x', 0, 0,
+ N_("extract files from an archive"), 10 },
+ {"get", 0, 0, OPTION_ALIAS, NULL, 0 },
+ {"create", 'c', 0, 0,
+ N_("create a new archive"), 10 },
+ {"diff", 'd', 0, 0,
+ N_("find differences between archive and file system"), 10 },
+ {"compare", 0, 0, OPTION_ALIAS, NULL, 10},
+ {"append", 'r', 0, 0,
+ N_("append files to the end of an archive"), 10 },
+ {"update", 'u', 0, 0,
+ N_("only append files newer than copy in archive"), 10 },
+ {"catenate", 'A', 0, 0,
+ N_("append tar files to an archive"), 10 },
+ {"concatenate", 0, 0, OPTION_ALIAS, NULL, 10},
+ {"delete", DELETE_OPTION, 0, 0,
+ N_("delete from the archive (not on mag tapes!)"), 10 },
+
+ {NULL, 0, NULL, 0,
+ N_("Operation modifiers:"), 20},
+
+ {"sparse", 'S', 0, 0,
+ N_("handle sparse files efficiently"), 21 },
+ {"incremental", 'G', 0, 0,
+ N_("handle old GNU-format incremental backup"), 21 },
+ {"listed-incremental", 'g', N_("FILE"), 0,
+ N_("handle new GNU-format incremental backup"), 21 },
+ {"ignore-failed-read", IGNORE_FAILED_READ_OPTION, 0, 0,
+ N_("do not exit with nonzero on unreadable files"), 21 },
+ {"occurrence", OCCURRENCE_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL,
+ N_("process only the NUMBERth occurrence of each file in the archive. This option is valid only in conjunction with one of the subcommands --delete, --diff, --extract or --list and when a list of files is given either on the command line or via -T option. NUMBER defaults to 1."), 21 },
+ {"seek", 'n', NULL, 0,
+ N_("archive is seekable"), 21 },
+
+ {NULL, 0, NULL, 0,
+ N_("Overwrite control:"), 30},
+
+ {"verify", 'W', 0, 0,
+ N_("attempt to verify the archive after writing it"), 31 },
+ {"remove-files", REMOVE_FILES_OPTION, 0, 0,
+ N_("remove files after adding them to the archive"), 31 },
+ {"keep-old-files", 'k', 0, 0,
+ N_("don't replace existing files when extracting"), 31 },
+ {"keep-newer-files", KEEP_NEWER_FILES_OPTION, 0, 0,
+ N_("don't replace existing files that are newer than their archive copies"), 31 },
+ {"overwrite", OVERWRITE_OPTION, 0, 0,
+ N_("overwrite existing files when extracting"), 31 },
+ {"unlink-first", 'U', 0, 0,
+ N_("remove each file prior to extracting over it"), 31 },
+ {"recursive-unlink", RECURSIVE_UNLINK_OPTION, 0, 0,
+ N_("empty hierarchies prior to extracting directory"), 31 },
+ {"no-overwrite-dir", NO_OVERWRITE_DIR_OPTION, 0, 0,
+ N_("preserve metadata of existing directories"), 31 },
+
+ {NULL, 0, NULL, 0,
+ N_("Select output stream:"), 40},
+
+ {"to-stdout", 'O', 0, 0,
+ N_("extract files to standard output"), 41 },
+ {"to-command", TO_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("pipe extracted files to another program"), 41 },
+ {"ignore-command-error", IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+ N_("ignore exit codes of children"), 41 },
+ {"no-ignore-command-error", NO_IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+ N_("treat non-zero exit codes of children as error"), 41 },
+
+ {NULL, 0, NULL, 0,
+ N_("Handling of file attributes:"), 50 },
+
+ {"owner", OWNER_OPTION, N_("NAME"), 0,
+ N_("force NAME as owner for added files"), 51 },
+ {"group", GROUP_OPTION, N_("NAME"), 0,
+ N_("force NAME as group for added files"), 51 },
+ {"mode", MODE_OPTION, N_("CHANGES"), 0,
+ N_("force (symbolic) mode CHANGES for added files"), 51 },
+ {"atime-preserve", ATIME_PRESERVE_OPTION, 0, 0,
+ N_("don't change access times on dumped files"), 51 },
+ {"touch", 'm', 0, 0,
+ N_("don't extract file modified time"), 51 },
+ {"same-owner", SAME_OWNER_OPTION, 0, 0,
+ N_("try extracting files with the same ownership"), 51 },
+ {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0,
+ N_("extract files as yourself"), 51 },
+ {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0,
+ N_("always use numbers for user/group names"), 51 },
+ {"preserve-permissions", 'p', 0, 0,
+ N_("extract information about file permissions (default for superuser)"),
+ 51 },
+ {"same-permissions", 0, 0, OPTION_ALIAS, NULL, 51 },
+ {"no-same-permissions", NO_SAME_PERMISSIONS_OPTION, 0, 0,
+ N_("apply the user's umask when extracting permissions from the archive (default for ordinary users)"), 51 },
+ {"preserve-order", 's', 0, 0,
+ N_("sort names to extract to match archive"), 51 },
+ {"same-order", 0, 0, OPTION_ALIAS, NULL, 51 },
+ {"preserve", PRESERVE_OPTION, 0, 0,
+ N_("same as both -p and -s"), 51 },
+
+ {NULL, 0, NULL, 0,
+ N_("Device selection and switching:"), 60 },
+
+ {"file", 'f', N_("ARCHIVE"), 0,
+ N_("use archive file or device ARCHIVE"), 61 },
+ {"force-local", FORCE_LOCAL_OPTION, 0, 0,
+ N_("archive file is local even if it has a colon"), 61 },
+ {"rmt-command", RMT_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("use given rmt COMMAND instead of rmt"), 61 },
+ {"rsh-command", RSH_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("use remote COMMAND instead of rsh"), 61 },
+#ifdef DEVICE_PREFIX
+ {"-[0-7][lmh]", 0, NULL, OPTION_DOC, /* It is OK, since `name' will never be
+ translated */
+ N_("specify drive and density"), 61 },
+#endif
+ {NULL, '0', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '1', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '2', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '3', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '4', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '5', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '6', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '7', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '8', NULL, OPTION_HIDDEN, NULL, 61 },
+ {NULL, '9', NULL, OPTION_HIDDEN, NULL, 61 },
+
+ {"multi-volume", 'M', 0, 0,
+ N_("create/list/extract multi-volume archive"), 61 },
+ {"tape-length", 'L', N_("NUMBER"), 0,
+ N_("change tape after writing NUMBER x 1024 bytes"), 61 },
+ {"info-script", 'F', N_("NAME"), 0,
+ N_("run script at end of each tape (implies -M)"), 61 },
+ {"new-volume-script", 0, 0, OPTION_ALIAS, NULL, 61 },
+ {"volno-file", VOLNO_FILE_OPTION, N_("FILE"), 0,
+ N_("use/update the volume number in FILE"), 61 },
+
+ {NULL, 0, NULL, 0,
+ N_("Device blocking:"), 70 },
+
+ {"blocking-factor", 'b', N_("BLOCKS"), 0,
+ N_("BLOCKS x 512 bytes per record"), 71 },
+ {"record-size", RECORD_SIZE_OPTION, N_("NUMBER"), 0,
+ N_("NUMBER of bytes per record, multiple of 512"), 71 },
+ {"ignore-zeros", 'i', 0, 0,
+ N_("ignore zeroed blocks in archive (means EOF)"), 71 },
+ {"read-full-records", 'B', 0, 0,
+ N_("reblock as we read (for 4.2BSD pipes)"), 71 },
+
+ {NULL, 0, NULL, 0,
+ N_("Archive format selection:"), 80 },
+
+ {"format", 'H', N_("FORMAT"), 0,
+ N_("create archive of the given format."), 81 },
+
+ {NULL, 0, NULL, 0, N_("FORMAT is one of the following:"), 82 },
+ {" v7", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("old V7 tar format"), 83},
+ {" oldgnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("GNU format as per tar <= 1.12"), 83},
+ {" gnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("GNU tar 1.13.x format"), 83},
+ {" ustar", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("POSIX 1003.1-1988 (ustar) format"), 83 },
+ {" pax", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("POSIX 1003.1-2001 (pax) format"), 83 },
+ {" posix", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("same as pax"), 83 },
+
+ {"old-archive", OLD_ARCHIVE_OPTION, 0, 0, /* FIXME */
+ N_("same as --format=v7"), 88 },
+ {"portability", 0, 0, OPTION_ALIAS, NULL, 88 },
+ {"posix", POSIX_OPTION, 0, 0,
+ N_("same as --format=posix"), 88 },
+ {"pax-option", PAX_OPTION, N_("keyword[[:]=value][,keyword[[:]=value], ...]"), 0,
+ N_("control pax keywords"), 88 },
+ {"label", 'V', N_("TEXT"), 0,
+ N_("create archive with volume name TEXT. At list/extract time, use TEXT as a globbing pattern for volume name"), 88 },
+ {"bzip2", 'j', 0, 0,
+ N_("filter the archive through bzip2"), 88 },
+ {"gzip", 'z', 0, 0,
+ N_("filter the archive through gzip"), 88 },
+ {"gunzip", 0, 0, OPTION_ALIAS, NULL, 88 },
+ {"ungzip", 0, 0, OPTION_ALIAS, NULL, 88 },
+ {"compress", 'Z', 0, 0,
+ N_("filter the archive through compress"), 88 },
+ {"uncompress", 0, 0, OPTION_ALIAS, NULL, 88 },
+ {"use-compress-program", USE_COMPRESS_PROGRAM_OPTION, N_("PROG"), 0,
+ N_("filter through PROG (must accept -d)"), 88 },
+
+ {NULL, 0, NULL, 0,
+ N_("Local file selection:"), 90 },
+
+ {"add-file", ARGP_KEY_ARG, N_("FILE"), 0,
+ N_("add given FILE to the archive (useful if its name starts with a dash)"), 91},
+ {"directory", 'C', N_("DIR"), 0,
+ N_("change to directory DIR"), 91 },
+ {"files-from", 'T', N_("FILE"), 0,
+ N_("get names to extract or create from FILE"), 91 },
+ {"null", NULL_OPTION, 0, 0,
+ N_("-T reads null-terminated names, disable -C"), 91 },
+ {"unquote", UNQUOTE_OPTION, 0, 0,
+ N_("unquote filenames read with -T (default)"), 91 },
+ {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
+ N_("do not unquote filenames read with -T"), 91 },
+ {"exclude", EXCLUDE_OPTION, N_("PATTERN"), 0,
+ N_("exclude files, given as a PATTERN"), 91 },
+ {"exclude-from", 'X', N_("FILE"), 0,
+ N_("exclude patterns listed in FILE"), 91 },
+ {"exclude-caches", EXCLUDE_CACHES_OPTION, 0, 0,
+ N_("exclude directories containing a cache tag"), 91 },
+ {"ignore-case", IGNORE_CASE_OPTION, 0, 0,
+ N_("exclusion ignores case"), 91 },
+ {"anchored", ANCHORED_OPTION, 0, 0,
+ N_("exclude patterns match file name start"), 91 },
+ {"no-anchored", NO_ANCHORED_OPTION, 0, 0,
+ N_("exclude patterns match after any `/' (default)"), 91 },
+ {"no-ignore-case", NO_IGNORE_CASE_OPTION, 0, 0,
+ N_("exclusion is case sensitive (default)"), 91 },
+ {"no-wildcards", NO_WILDCARDS_OPTION, 0, 0,
+ N_("exclude patterns are plain strings"), 91 },
+ {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+ N_("exclude pattern wildcards do not match `/'"), 91 },
+ {"no-recursion", NO_RECURSION_OPTION, 0, 0,
+ N_("avoid descending automatically in directories"), 91 },
+ {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0,
+ N_("stay in local file system when creating archive"), 91 },
+ {NULL, 'l', 0, OPTION_HIDDEN, "", 91},
+ {"recursion", RECURSION_OPTION, 0, 0,
+ N_("recurse into directories (default)"), 91 },
+ {"absolute-names", 'P', 0, 0,
+ N_("don't strip leading `/'s from file names"), 91 },
+ {"dereference", 'h', 0, 0,
+ N_("follow symlinks; archive and dump the files they point to"), 91 },
+ {"starting-file", 'K', N_("MEMBER-NAME"), 0,
+ N_("begin at member MEMBER-NAME in the archive"), 91 },
+ {"strip-components", STRIP_COMPONENTS_OPTION, N_("NUMBER"), 0,
+ N_("strip NUMBER leading components from file names"), 91 },
+ {"newer", 'N', N_("DATE-OR-FILE"), 0,
+ N_("only store files newer than DATE-OR-FILE"), 91 },
+ {"newer-mtime", NEWER_MTIME_OPTION, N_("DATE"), 0,
+ N_("compare date and time when data changed only"), 91 },
+ {"after-date", 'N', N_("DATE"), 0,
+ N_("same as -N"), 91 },
+ {"backup", BACKUP_OPTION, N_("CONTROL"), OPTION_ARG_OPTIONAL,
+ N_("backup before removal, choose version CONTROL"), 91 },
+ {"suffix", SUFFIX_OPTION, N_("STRING"), 0,
+ N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), 91 },
+ {"wildcards", WILDCARDS_OPTION, 0, 0,
+ N_("exclude patterns use wildcards (default)"), 91 },
+ {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+ N_("exclude pattern wildcards match `/' (default)"), 91 },
+
+ {NULL, 0, NULL, 0,
+ N_("Informative output:"), 100 },
+
+ {"verbose", 'v', 0, 0,
+ N_("verbosely list files processed"), 101 },
+ {"checkpoint", CHECKPOINT_OPTION, 0, 0,
+ N_("display progress messages every 10th record"), 101 },
+ {"check-links", CHECK_LINKS_OPTION, 0, 0,
+ N_("print a message if not all links are dumped"), 102 },
+ {"totals", TOTALS_OPTION, 0, 0,
+ N_("print total bytes written while creating archive"), 102 },
+ {"utc", UTC_OPTION, 0, 0,
+ N_("print file modification dates in UTC"), 102 },
+ {"index-file", INDEX_FILE_OPTION, N_("FILE"), 0,
+ N_("send verbose output to FILE"), 102 },
+ {"block-number", 'R', 0, 0,
+ N_("show block number within archive with each message"), 102 },
+ {"interactive", 'w', 0, 0,
+ N_("ask for confirmation for every action"), 102 },
+ {"confirmation", 0, 0, OPTION_ALIAS, NULL, 102 },
+ {"show-defaults", SHOW_DEFAULTS_OPTION, 0, 0,
+ N_("Show tar defaults"), 102 },
+ {"show-omitted-dirs", SHOW_OMITTED_DIRS_OPTION, 0, 0,
+ N_("When listing or extracting, list each directory that does not match search criteria"), 102 },
+
+ {NULL, 0, NULL, 0,
+ N_("Compatibility options:"), 110 },
+
+ {NULL, 'o', 0, 0,
+ N_("when creating, same as --old-archive. When extracting, same as --no-same-owner"), 111 },
+
+ {NULL, 0, NULL, 0,
+ N_("Other options:"), 120 },
+
+ {"help", '?', 0, 0, N_("Give this help list"), -1},
+ {"usage", USAGE_OPTION, 0, 0, N_("Give a short usage message"), -1},
+ {"license", LICENSE_OPTION, 0, 0, N_("Print license and exit"), -1},
+ {"version", VERSION_OPTION, 0, 0, N_("Print program version"), -1},
+ /* FIXME -V (--label) conflicts with the default short option for
+ --version */
+ {"HANG", HANG_OPTION, "SECS", OPTION_ARG_OPTIONAL | OPTION_HIDDEN,
+ N_("Hang for SECS seconds (default 3600)"), 0},
+ {0, 0, 0, 0, 0, 0}
+};
+
+struct tar_args {
+ char const *textual_date_option;
+ int exclude_options;
+ bool o_option;
+ int pax_option;
+ char const *backup_suffix_string;
+ char const *version_control_string;
+ int input_files;
+};
+
+static void
+show_default_settings (FILE *stream)
+{
+ fprintf (stream,
+ "--format=%s -f%s -b%d --rmt-command=%s",
+ archive_format_string (DEFAULT_ARCHIVE_FORMAT),
+ DEFAULT_ARCHIVE, DEFAULT_BLOCKING,
+ DEFAULT_RMT_COMMAND);
+#ifdef REMOTE_SHELL
+ fprintf (stream, " --rsh-command=%s", REMOTE_SHELL);
+#endif
+ fprintf (stream, "\n");
+}
+
+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;
+}
+
void
-main (argc, argv)
- int argc;
- char **argv;
+license ()
{
- extern char version_string[];
+ printf ("tar (%s) %s\n%s\n", PACKAGE_NAME, PACKAGE_VERSION,
+ "Copyright (C) 2004 Free Software Foundation, Inc.\n");
+ puts (_("Based on the work of John Gilmore and Jay Fenlason. See AUTHORS\n\
+for complete list of authors.\n"));
+ printf (_(" GNU tar is free software; you can redistribute it and/or modify\n"
+ " it under the terms of the GNU General Public License as published by\n"
+ " the Free Software Foundation; either version 2 of the License, or\n"
+ " (at your option) any later version.\n"
+ "\n"
+ " GNU tar is distributed in the hope that it will be useful,\n"
+ " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ " GNU General Public License for more details.\n"
+ "\n"
+ " You should have received a copy of the GNU General Public License\n"
+ " along with GNU tar; if not, write to the Free Software Foundation,\n"
+ " Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA\n\n"));
+ exit (0);
+}
- tar = argv[0]; /* JF: was "tar" Set program name */
- filename_terminator = '\n';
- errors = 0;
+static volatile int _argp_hang;
- options (argc, argv);
+enum read_file_list_state /* Result of reading file name from the list file */
+ {
+ file_list_success, /* OK, name read successfully */
+ file_list_end, /* End of list file */
+ file_list_zero /* Zero separator encountered where it should not */
+ };
- if (!n_argv)
- name_init (argc, argv);
+/* Read from FP a sequence of characters up to FILENAME_TERMINATOR and put them
+ into STK.
+ */
+static enum read_file_list_state
+read_name_from_file (FILE *fp, struct obstack *stk)
+{
+ int c;
+ size_t counter = 0;
- if (f_volno_file)
- init_volume_number ();
+ for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp))
+ {
+ if (c == 0)
+ {
+ /* We have read a zero separator. The file possibly is zero-separated */
+ /* FATAL_ERROR((0, 0, N_("file name contains null character"))); */
+ return file_list_zero;
+ }
+ obstack_1grow (stk, c);
+ counter++;
+ }
+
+ obstack_1grow (stk, 0);
+
+ return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
+}
+
+\f
+static bool files_from_option; /* When set, tar will not refuse to create
+ empty archives */
+static struct obstack argv_stk; /* Storage for additional command line options
+ read using -T option */
+
+/* Prevent recursive inclusion of the same file */
+struct file_id_list
+{
+ struct file_id_list *next;
+ ino_t ino;
+ dev_t dev;
+};
+
+static struct file_id_list *file_id_list;
- switch (cmd_mode)
+static void
+add_file_id (const char *filename)
+{
+ struct file_id_list *p;
+ struct stat st;
+
+ if (stat (filename, &st))
+ stat_fatal (filename);
+ for (p = file_id_list; p; p = p->next)
+ if (p->ino == st.st_ino && p->dev == st.st_dev)
+ {
+ FATAL_ERROR ((0, 0, _("%s: file list already read"),
+ quotearg_colon (filename)));
+ }
+ p = xmalloc (sizeof *p);
+ p->next = file_id_list;
+ p->ino = st.st_ino;
+ p->dev = st.st_dev;
+ file_id_list = p;
+}
+
+static void
+update_argv (const char *filename, struct argp_state *state)
+{
+ FILE *fp;
+ size_t count = 0, i;
+ char *start, *p;
+ char **new_argv;
+ size_t new_argc;
+ bool is_stdin = false;
+ enum read_file_list_state read_state;
+
+ if (!strcmp (filename, "-"))
{
- case CMD_CAT:
- case CMD_UPDATE:
- case CMD_APPEND:
- update_archive ();
+ is_stdin = true;
+ request_stdin ("-T");
+ fp = stdin;
+ }
+ else
+ {
+ add_file_id (filename);
+ if ((fp = fopen (filename, "r")) == NULL)
+ open_fatal (filename);
+ }
+
+ while ((read_state = read_name_from_file (fp, &argv_stk)) == file_list_success)
+ count++;
+
+ if (read_state == file_list_zero)
+ {
+ size_t size;
+
+ WARN ((0, 0, N_("%s: file name read contains nul character"),
+ quotearg_colon (filename)));
+
+ /* Prepare new stack contents */
+ size = obstack_object_size (&argv_stk);
+ p = obstack_finish (&argv_stk);
+ for (; size > 0; size--, p++)
+ if (*p)
+ obstack_1grow (&argv_stk, *p);
+ else
+ obstack_1grow (&argv_stk, '\n');
+ obstack_1grow (&argv_stk, 0);
+ count = 1;
+
+ /* Read rest of files using new filename terminator */
+ filename_terminator = 0;
+ while (read_name_from_file (fp, &argv_stk) == file_list_success)
+ count++;
+ }
+
+ if (!is_stdin)
+ fclose (fp);
+
+ if (count == 0)
+ return;
+
+ start = obstack_finish (&argv_stk);
+
+ if (filename_terminator == 0)
+ for (p = start; *p; p += strlen (p) + 1)
+ if (p[0] == '-')
+ count++;
+
+ new_argc = state->argc + count;
+ new_argv = xmalloc (sizeof (state->argv[0]) * (new_argc + 1));
+ memcpy (new_argv, state->argv, sizeof (state->argv[0]) * (state->argc + 1));
+ state->argv = new_argv;
+ memmove (&state->argv[state->next + count], &state->argv[state->next],
+ (state->argc - state->next + 1) * sizeof (state->argv[0]));
+
+ state->argc = new_argc;
+
+ for (i = state->next, p = start; *p; p += strlen (p) + 1, i++)
+ {
+ if (filename_terminator == 0 && p[0] == '-')
+ state->argv[i++] = "--add-file";
+ state->argv[i] = p;
+ }
+}
+
+\f
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ struct tar_args *args = state->input;
+
+ switch (key)
+ {
+ case ARGP_KEY_ARG:
+ /* File name or non-parsed option, because of ARGP_IN_ORDER */
+ name_add (arg);
+ args->input_files++;
+ break;
+
+ case 'A':
+ set_subcommand_option (CAT_SUBCOMMAND);
break;
- case CMD_DELETE:
- junk_archive ();
+
+ case 'b':
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (arg, 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 (arg),
+ _("Invalid blocking factor")));
+ }
break;
- case CMD_CREATE:
- create_archive ();
- if (f_totals)
- fprintf (stderr, "Total bytes written: %d\n", tot_written);
+
+ 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 and most
+ BSD-derived systems. This is a consequence of the block/record
+ terminology confusion. */
+
+ read_full_records_option = true;
break;
- case CMD_EXTRACT:
- if (f_volhdr)
+
+ case 'c':
+ set_subcommand_option (CREATE_SUBCOMMAND);
+ break;
+
+ case 'C':
+ name_add ("-C");
+ name_add (arg);
+ break;
+
+ case 'd':
+ set_subcommand_option (DIFF_SUBCOMMAND);
+ break;
+
+ case 'f':
+ if (archive_names == allocated_archive_names)
{
- const char *err;
- label_pattern = (struct re_pattern_buffer *)
- ck_malloc (sizeof *label_pattern);
- err = re_compile_pattern (f_volhdr, strlen (f_volhdr),
- label_pattern);
- if (err)
+ allocated_archive_names *= 2;
+ archive_name_array =
+ xrealloc (archive_name_array,
+ sizeof (const char *) * allocated_archive_names);
+ }
+ archive_name_array[archive_names++] = arg;
+ 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 = arg;
+ multi_volume_option = true;
+ break;
+
+ case 'g':
+ listed_incremental_option = arg;
+ after_date_option = true;
+ /* 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 = true;
+ break;
+
+ case 'h':
+ /* Follow symbolic links. */
+ dereference_option = true;
+ 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 = true;
+ break;
+
+ case 'I':
+ USAGE_ERROR ((0, 0,
+ _("Warning: the -I option is not supported;"
+ " perhaps you meant -j or -T?")));
+ break;
+
+ case 'j':
+ set_use_compress_program_option ("bzip2");
+ break;
+
+ case 'k':
+ /* Don't replace existing files. */
+ old_files_option = KEEP_OLD_FILES;
+ break;
+
+ case 'K':
+ starting_file_option = true;
+ addname (arg, 0);
+ break;
+
+ case 'l':
+ /* Historically equivalent to --one-file-system. This usage is
+ incompatible with UNIX98 and POSIX specs and therefore is
+ deprecated. The semantics of -l option will be changed in
+ future versions. See TODO.
+ */
+ WARN ((0, 0,
+ _("Semantics of -l option will change in the future releases.")));
+ WARN ((0, 0,
+ _("Please use --one-file-system option instead.")));
+ /* FALL THROUGH */
+ case ONE_FILE_SYSTEM_OPTION:
+ /* When dumping directories, don't dump files/subdirectories
+ that are on other filesystems. */
+ one_file_system_option = true;
+ break;
+
+ case 'L':
+ {
+ uintmax_t u;
+ if (xstrtoumax (arg, 0, 10, &u, "") != LONGINT_OK)
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid tape length")));
+ tape_length_option = 1024 * (tarlong) u;
+ multi_volume_option = true;
+ }
+ break;
+
+ case 'm':
+ touch_option = true;
+ 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 = true;
+ break;
+
+ case 'n':
+ seekable_archive = true;
+ break;
+
+#if !MSDOS
+ case 'N':
+ after_date_option = true;
+ /* Fall through. */
+
+ case NEWER_MTIME_OPTION:
+ if (NEWER_OPTION_INITIALIZED (newer_mtime_option))
+ USAGE_ERROR ((0, 0, _("More than one threshold date")));
+
+ if (FILE_SYSTEM_PREFIX_LEN (arg) != 0
+ || ISSLASH (*arg)
+ || *arg == '.')
+ {
+ struct stat st;
+ if (deref_stat (dereference_option, arg, &st) != 0)
{
- fprintf (stderr, "Bad regular expression: %s\n",
- err);
- errors++;
- break;
+ stat_error (arg);
+ USAGE_ERROR ((0, 0, _("Date sample file not found")));
}
-
+ newer_mtime_option = get_stat_mtime (&st);
}
- extr_init ();
- read_and (extract_archive);
- break;
- case CMD_LIST:
- if (f_volhdr)
+ else
{
- const char *err;
- label_pattern = (struct re_pattern_buffer *)
- ck_malloc (sizeof *label_pattern);
- err = re_compile_pattern (f_volhdr, strlen (f_volhdr),
- label_pattern);
- if (err)
+ if (! get_date (&newer_mtime_option, arg, NULL))
{
- fprintf (stderr, "Bad regular expression: %s\n",
- err);
- errors++;
- break;
+ WARN ((0, 0, _("Substituting %s for unknown date format %s"),
+ tartime (newer_mtime_option, false), quote (arg)));
+ newer_mtime_option.tv_nsec = 0;
}
+ else
+ args->textual_date_option = arg;
}
- read_and (list_archive);
-#if 0
- if (!errors)
- errors = different;
-#endif
+
break;
- case CMD_DIFF:
- diff_init ();
- read_and (diff_archive);
+#endif /* not MSDOS */
+
+ case 'o':
+ args->o_option = true;
break;
- case CMD_VERSION:
- fprintf (stderr, "%s\n", version_string);
+
+ case 'O':
+ to_stdout_option = true;
break;
- case CMD_NONE:
- msg ("you must specify exactly one of the r, c, t, x, or d options\n");
- fprintf (stderr, "For more information, type ``%s --help''.\n", tar);
- exit (EX_ARGSBAD);
- }
- if (f_volno_file)
- closeout_volume_number ();
- exit (errors);
- /* NOTREACHED */
-}
+ case 'p':
+ same_permissions_option = true;
+ break;
-/*
- * Parse the options for tar.
- */
-void
-options (argc, argv)
- int argc;
- char **argv;
-{
- register int c; /* Option letter */
- int ind = -1;
-
- /* Set default option values */
- blocking = DEFBLOCKING; /* From Makefile */
- ar_files = (char **) malloc (sizeof (char *) * 10);
- ar_files_len = 10;
- n_ar_files = 0;
- cur_ar_file = 0;
-
- /* Parse options */
- while ((c = getoldopt (argc, argv,
- "-01234567Ab:BcC:df:F:g:GhikK:lL:mMN:oOpPrRsStT:uvV:wWxX:zZ",
- long_options, &ind)) != EOF)
- {
- switch (c)
+ case 'P':
+ absolute_names_option = true;
+ break;
+
+ case 'r':
+ set_subcommand_option (APPEND_SUBCOMMAND);
+ break;
+
+ 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 = true;
+ break;
+
+ case 's':
+ /* Names to extr are sorted. */
+
+ same_order_option = true;
+ break;
+
+ case 'S':
+ sparse_option = true;
+ break;
+
+ case 't':
+ set_subcommand_option (LIST_SUBCOMMAND);
+ verbose_option++;
+ break;
+
+ case 'T':
+ update_argv (arg, state);
+ /* Indicate we've been given -T option. This is for backward
+ compatibility only, so that `tar cfT archive /dev/null will
+ succeed */
+ files_from_option = true;
+ break;
+
+ case 'u':
+ set_subcommand_option (UPDATE_SUBCOMMAND);
+ break;
+
+ case 'U':
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+ break;
+
+ case UTC_OPTION:
+ utc_option = true;
+ break;
+
+ case 'v':
+ verbose_option++;
+ break;
+
+ case 'V':
+ volume_label_option = arg;
+ break;
+
+ case 'w':
+ interactive_option = true;
+ break;
+
+ case 'W':
+ verify_option = true;
+ break;
+
+ case 'x':
+ set_subcommand_option (EXTRACT_SUBCOMMAND);
+ break;
+
+ case 'X':
+ if (add_exclude_file (add_exclude, excluded, arg,
+ args->exclude_options | recursion_option, '\n')
+ != 0)
{
- case 0: /* long options that set a single flag */
- break;
- case 1:
- /* File name or non-parsed option */
- name_add (optarg);
- break;
- case 'C':
- name_add ("-C");
- name_add (optarg);
- break;
- case 10: /* preserve */
- f_use_protection = f_sorted_names = 1;
- break;
- case 11:
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_VERSION;
- break;
- case 12: /* help */
- printf ("This is GNU tar, the tape archiving program.\n");
- describe ();
- exit (1);
- case 13:
- f_new_files++;
- goto get_newer;
-
- case 14: /* Delete in the archive */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_DELETE;
- break;
+ int e = errno;
+ FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+ }
+ break;
- case 15:
- f_exclude++;
- add_exclude (optarg);
- break;
+ case 'y':
+ USAGE_ERROR ((0, 0,
+ _("Warning: the -y option is not supported;"
+ " perhaps you meant -j?")));
+ break;
- case 16: /* -T reads null terminated filenames. */
- filename_terminator = '\0';
- break;
+ case 'z':
+ set_use_compress_program_option ("gzip");
+ break;
- case 17:
- f_volno_file = optarg;
- break;
+ case 'Z':
+ set_use_compress_program_option ("compress");
+ break;
- case 'g': /* We are making a GNU dump; save
- directories at the beginning of
- the archive, and include in each
- directory its contents */
- if (f_oldarch)
- goto badopt;
- f_gnudump++;
- gnu_dumpfile = optarg;
- break;
+ case ANCHORED_OPTION:
+ args->exclude_options |= EXCLUDE_ANCHORED;
+ break;
+ case ATIME_PRESERVE_OPTION:
+ atime_preserve_option = true;
+ break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- {
- /* JF this'll have to be modified for other
- systems, of course! */
- int d, add;
- static char buf[50];
-
- d = getoldopt (argc, argv, "lmh");
-#ifdef MAYBEDEF
- sprintf (buf, "/dev/rmt/%d%c", c, d);
-#else
-#ifndef LOW_NUM
-#define LOW_NUM 0
-#define MID_NUM 8
-#define HGH_NUM 16
-#endif
- if (d == 'l')
- add = LOW_NUM;
- else if (d == 'm')
- add = MID_NUM;
- else if (d == 'h')
- add = HGH_NUM;
- else
- goto badopt;
-
- sprintf (buf, "/dev/rmt%d", add + c - '0');
-#endif
- if (n_ar_files == ar_files_len)
- ar_files
- = (char **)
- ck_malloc (sizeof (char *)
- * (ar_files_len *= 2));
- ar_files[n_ar_files++] = buf;
- }
- break;
+ case CHECKPOINT_OPTION:
+ checkpoint_option = true;
+ break;
- case 'A': /* Arguments are tar files,
- just cat them onto the end
- of the archive. */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_CAT;
- break;
+ case BACKUP_OPTION:
+ backup_option = true;
+ if (arg)
+ args->version_control_string = arg;
+ break;
- case 'b': /* Set blocking factor */
- blocking = intconv (optarg);
- break;
+ case DELETE_OPTION:
+ set_subcommand_option (DELETE_SUBCOMMAND);
+ break;
- case 'B': /* Try to reblock input */
- f_reblock++; /* For reading 4.2BSD pipes */
- break;
+ case EXCLUDE_OPTION:
+ add_exclude (excluded, arg, args->exclude_options | recursion_option);
+ break;
- case 'c': /* Create an archive */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_CREATE;
- break;
+ case EXCLUDE_CACHES_OPTION:
+ exclude_caches_option = true;
+ break;
-#if 0
- case 'C':
- if (chdir (optarg) < 0)
- msg_perror ("Can't change directory to %d", optarg);
- break;
-#endif
+ case FORCE_LOCAL_OPTION:
+ force_local_option = true;
+ break;
- case 'd': /* Find difference tape/disk */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_DIFF;
- break;
+ case 'H':
+ set_archive_format (arg);
+ break;
- case 'f': /* Use ar_file for the archive */
- if (n_ar_files == ar_files_len)
- ar_files
- = (char **) ck_malloc (sizeof (char *)
- * (ar_files_len *= 2));
+ case INDEX_FILE_OPTION:
+ index_file_name = arg;
+ break;
- ar_files[n_ar_files++] = optarg;
- break;
+ case IGNORE_CASE_OPTION:
+ args->exclude_options |= FNM_CASEFOLD;
+ break;
- case 'F':
- /* Since -F is only useful with -M , make it implied */
- f_run_script_at_end++;/* run this script at the end */
- info_script = optarg; /* of each tape */
- f_multivol++;
- break;
+ case IGNORE_COMMAND_ERROR_OPTION:
+ ignore_command_error_option = true;
+ break;
- case 'G': /* We are making a GNU dump; save
- directories at the beginning of
- the archive, and include in each
- directory its contents */
- if (f_oldarch)
- goto badopt;
- f_gnudump++;
- gnu_dumpfile = 0;
- break;
+ case IGNORE_FAILED_READ_OPTION:
+ ignore_failed_read_option = true;
+ break;
- case 'h':
- f_follow_links++; /* follow symbolic links */
- break;
+ case KEEP_NEWER_FILES_OPTION:
+ old_files_option = KEEP_NEWER_FILES;
+ break;
- case 'i':
- f_ignorez++; /* Ignore zero records (eofs) */
- /*
- * This can't be the default, because Unix tar
- * writes two records of zeros, then pads out the
- * block with garbage.
- */
- break;
+ case GROUP_OPTION:
+ if (! (strlen (arg) < GNAME_FIELD_SIZE
+ && gname_to_gid (arg, &group_option)))
+ {
+ uintmax_t g;
+ if (xstrtoumax (arg, 0, 10, &g, "") == LONGINT_OK
+ && g == (gid_t) g)
+ group_option = g;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("%s: Invalid group")));
+ }
+ break;
- case 'k': /* Don't overwrite files */
-#ifdef NO_OPEN3
- msg ("can't keep old files on this system");
- exit (EX_ARGSBAD);
-#else
- f_keep++;
-#endif
- break;
+ case MODE_OPTION:
+ mode_option = mode_compile (arg);
+ if (!mode_option)
+ FATAL_ERROR ((0, 0, _("Invalid mode given on option")));
+ initial_umask = umask (0);
+ umask (initial_umask);
+ break;
- case 'K':
- f_startfile++;
- addname (optarg);
- break;
+ case NO_ANCHORED_OPTION:
+ args->exclude_options &= ~ EXCLUDE_ANCHORED;
+ break;
- case 'l': /* When dumping directories, don't
- dump files/subdirectories that are
- on other filesystems. */
- f_local_filesys++;
- break;
+ case NO_IGNORE_CASE_OPTION:
+ args->exclude_options &= ~ FNM_CASEFOLD;
+ break;
- case 'L':
- tape_length = intconv (optarg);
- f_multivol++;
- break;
- case 'm':
- f_modified++;
- break;
+ case NO_IGNORE_COMMAND_ERROR_OPTION:
+ ignore_command_error_option = false;
+ break;
+
+ case NO_OVERWRITE_DIR_OPTION:
+ old_files_option = NO_OVERWRITE_DIR_OLD_FILES;
+ break;
+
+ case NO_WILDCARDS_OPTION:
+ args->exclude_options &= ~ EXCLUDE_WILDCARDS;
+ break;
+
+ case NO_WILDCARDS_MATCH_SLASH_OPTION:
+ args->exclude_options |= FNM_FILE_NAME;
+ break;
+
+ case NULL_OPTION:
+ filename_terminator = '\0';
+ break;
+
+ case NUMERIC_OWNER_OPTION:
+ numeric_owner_option = true;
+ break;
+
+ case OCCURRENCE_OPTION:
+ if (!arg)
+ occurrence_option = 1;
+ else
+ {
+ uintmax_t u;
+ if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK)
+ occurrence_option = u;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid number")));
+ }
+ break;
+
+ case OVERWRITE_OPTION:
+ old_files_option = OVERWRITE_OLD_FILES;
+ break;
+
+ case OWNER_OPTION:
+ if (! (strlen (arg) < UNAME_FIELD_SIZE
+ && uname_to_uid (arg, &owner_option)))
+ {
+ uintmax_t u;
+ if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+ && u == (uid_t) u)
+ owner_option = u;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid owner")));
+ }
+ break;
+
+ case PAX_OPTION:
+ args->pax_option++;
+ xheader_set_option (arg);
+ break;
+
+ case POSIX_OPTION:
+ set_archive_format ("posix");
+ break;
+
+ case PRESERVE_OPTION:
+ same_permissions_option = true;
+ same_order_option = true;
+ break;
+
+ case RECORD_SIZE_OPTION:
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+ && u == (size_t) u))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("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 RECURSIVE_UNLINK_OPTION:
+ recursive_unlink_option = true;
+ break;
+
+ case REMOVE_FILES_OPTION:
+ remove_files_option = true;
+ break;
+
+ case RMT_COMMAND_OPTION:
+ rmt_command = arg;
+ break;
+
+ case RSH_COMMAND_OPTION:
+ rsh_command_option = arg;
+ break;
+
+ case SHOW_DEFAULTS_OPTION:
+ show_default_settings (stdout);
+ exit(0);
+
+ case STRIP_COMPONENTS_OPTION:
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+ && u == (size_t) u))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid number of elements")));
+ strip_name_components = u;
+ }
+ break;
+
+ case SHOW_OMITTED_DIRS_OPTION:
+ show_omitted_dirs_option = true;
+ break;
+
+ case SUFFIX_OPTION:
+ backup_option = true;
+ args->backup_suffix_string = arg;
+ break;
+
+ case TO_COMMAND_OPTION:
+ if (to_command_option)
+ USAGE_ERROR ((0, 0, _("Only one --to-command option allowed")));
+ to_command_option = arg;
+ break;
+
+ case TOTALS_OPTION:
+ totals_option = true;
+ break;
+
+ case USE_COMPRESS_PROGRAM_OPTION:
+ set_use_compress_program_option (arg);
+ break;
+
+ case VOLNO_FILE_OPTION:
+ volno_file_option = arg;
+ break;
+
+ case WILDCARDS_OPTION:
+ args->exclude_options |= EXCLUDE_WILDCARDS;
+ break;
+
+ case WILDCARDS_MATCH_SLASH_OPTION:
+ args->exclude_options &= ~ FNM_FILE_NAME;
+ break;
+
+ case CHECK_LINKS_OPTION:
+ check_links_option = 1;
+ break;
+
+ case NO_RECURSION_OPTION:
+ recursion_option = 0;
+ break;
+
+ case NO_SAME_OWNER_OPTION:
+ same_owner_option = -1;
+ break;
+
+ case NO_SAME_PERMISSIONS_OPTION:
+ same_permissions_option = -1;
+ break;
+
+ case RECURSION_OPTION:
+ recursion_option = FNM_LEADING_DIR;
+ break;
+
+ case SAME_OWNER_OPTION:
+ same_owner_option = 1;
+ break;
+
+ case UNQUOTE_OPTION:
+ unquote_option = true;
+ break;
- case 'M': /* Make Multivolume archive:
- When we can't write any more
- into the archive, re-open it,
- and continue writing */
- f_multivol++;
- break;
+ case NO_UNQUOTE_OPTION:
+ unquote_option = false;
+ break;
- case 'N': /* Only write files newer than X */
- get_newer:
- f_new_files++;
- new_time = get_date (optarg, (PTR) 0);
- if (new_time == (time_t) - 1)
- {
- msg ("invalid date format `%s'", optarg);
- exit (EX_ARGSBAD);
- }
- break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
- case 'o': /* Generate old archive */
- if (f_gnudump /* || f_dironly */ )
- goto badopt;
- f_oldarch++;
- break;
+#ifdef DEVICE_PREFIX
+ {
+ int device = key - '0';
+ int density;
+ static char buf[sizeof DEVICE_PREFIX + 10];
+ char *cursor;
- case 'O':
- f_exstdout++;
- break;
+ if (arg[1])
+ argp_error (state, _("Malformed density argument: %s"), quote (arg));
- case 'p':
- f_use_protection++;
- break;
+ strcpy (buf, DEVICE_PREFIX);
+ cursor = buf + strlen (buf);
- case 'P':
- f_absolute_paths++;
- break;
+#ifdef DENSITY_LETTER
- case 'r': /* Append files to the archive */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_APPEND;
- break;
+ sprintf (cursor, "%d%c", device, arg[0]);
- case 'R':
- f_sayblock++; /* Print block #s for debug */
- break; /* of bad tar archives */
+#else /* not DENSITY_LETTER */
- case 's':
- f_sorted_names++; /* Names to extr are sorted */
- break;
+ switch (arg[0])
+ {
+ case 'l':
+#ifdef LOW_NUM
+ device += LOW_NUM;
+#endif
+ break;
- case 'S': /* deal with sparse files */
- f_sparse_files++;
- break;
- case 't':
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_LIST;
- f_verbose++; /* "t" output == "cv" or "xv" */
- break;
+ case 'm':
+#ifdef MID_NUM
+ device += MID_NUM;
+#else
+ device += 8;
+#endif
+ break;
- case 'T':
- name_file = optarg;
- f_namefile++;
- break;
+ case 'h':
+#ifdef HGH_NUM
+ device += HGH_NUM;
+#else
+ device += 16;
+#endif
+ break;
- case 'u': /* Append files to the archive that
- aren't there, or are newer than the
- copy in the archive */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_UPDATE;
- break;
+ default:
+ argp_error (state, _("Unknown density: `%c'"), arg[0]);
+ }
+ sprintf (cursor, "%d", device);
- case 'v':
- f_verbose++;
- break;
+#endif /* not DENSITY_LETTER */
- case 'V':
- f_volhdr = optarg;
- break;
+ 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++] = xstrdup (buf);
+ }
+ break;
- case 'w':
- f_confirm++;
- break;
+#else /* not DEVICE_PREFIX */
- case 'W':
- f_verify++;
- break;
+ argp_error (state,
+ _("Options `-[0-7][lmh]' not supported by *this* tar"));
- case 'x': /* Extract files from the archive */
- if (cmd_mode != CMD_NONE)
- goto badopt;
- cmd_mode = CMD_EXTRACT;
- break;
+#endif /* not DEVICE_PREFIX */
- case 'X':
- f_exclude++;
- add_exclude_file (optarg);
- break;
+ case '?':
+ state->flags |= ARGP_NO_EXIT;
+ argp_state_help (state, state->out_stream,
+ ARGP_HELP_STD_HELP & ~ARGP_HELP_BUG_ADDR);
+ fprintf (state->out_stream, _("\n*This* tar defaults to:\n"));
+ show_default_settings (state->out_stream);
+ fprintf (state->out_stream, "\n");
+ fprintf (state->out_stream, _("Report bugs to %s.\n"),
+ argp_program_bug_address);
+ exit (0);
- case 'z': /* Easy to type */
- case 'Z': /* Like the filename extension .Z */
- f_compress++;
- break;
+ case USAGE_OPTION:
+ argp_state_help (state, state->out_stream,
+ ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
+ break;
- case '?':
- badopt:
- msg ("Unknown option. Use '%s --help' for a complete list of options.", tar);
- exit (EX_ARGSBAD);
+ case VERSION_OPTION:
+ fprintf (state->out_stream, "%s\n", argp_program_version);
+ exit (0);
- }
- }
+ case LICENSE_OPTION:
+ license ();
+ break;
- blocksize = blocking * RECORDSIZE;
- if (n_ar_files == 0)
- {
- n_ar_files = 1;
- ar_files[0] = getenv ("TAPE"); /* From environment, or */
- if (ar_files[0] == 0)
- ar_files[0] = DEF_AR_FILE; /* From Makefile */
- }
- if (n_ar_files > 1 && !f_multivol)
- {
- msg ("Multiple archive files requires --multi-volume\n");
- exit (EX_ARGSBAD);
+ case HANG_OPTION:
+ _argp_hang = atoi (arg ? arg : "3600");
+ while (_argp_hang-- > 0)
+ sleep (1);
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
}
+ return 0;
}
+static struct argp argp = {
+ options,
+ parse_opt,
+ N_("[FILE]..."),
+ doc,
+ NULL,
+ NULL,
+ NULL
+};
-/*
- * Print as much help as the user's gonna get.
- *
- * We have to sprinkle in the KLUDGE lines because too many compilers
- * cannot handle character strings longer than about 512 bytes. Yuk!
- * In particular, MS-DOS and Xenix MSC and PDP-11 V7 Unix have this
- * problem.
- */
void
-describe ()
+usage (int status)
{
- puts ("choose one of the following:");
- fputs ("\
--A, --catenate,\n\
- --concatenate append tar files to an archive\n\
--c, --create create a new archive\n\
--d, --diff,\n\
- --compare find differences between archive and file system\n\
---delete delete from the archive (not for use on mag tapes!)\n\
--r, --append append files to the end of an archive\n\
--t, --list list the contents of an archive\n\
--u, --update only append files that are newer than copy in archive\n\
--x, --extract,\n\
- --get extract files from an archive\n", stdout);
-
- fprintf (stdout, "\
-Other options:\n\
---atime-preserve don't change access times on dumped files\n\
--b, --block-size N block size of Nx512 bytes (default N=%d)\n", DEFBLOCKING);
- fputs ("\
--B, --read-full-blocks reblock as we read (for reading 4.2BSD pipes)\n\
--C, --directory DIR change to directory DIR\n\
---checkpoint print directory names while reading the archive\n\
-", stdout); /* KLUDGE */
- fprintf (stdout, "\
--f, --file [HOSTNAME:]F use archive file or device F (default %s)\n",
- DEF_AR_FILE);
- fputs ("\
---force-local archive file is local even if has a colon\n\
--F, --info-script F run script at end of each tape (implies -M)\n\
--G, --incremental create/list/extract old GNU-format incremental backup\n\
--g, --listed-incremental F create/list/extract new GNU-format incremental backup\n\
--h, --dereference don't dump symlinks; dump the files they point to\n\
--i, --ignore-zeros ignore blocks of zeros in archive (normally mean EOF)\n\
---ignore-failed-read don't exit with non-zero status on unreadable files\n\
--k, --keep-old-files keep existing files; don't overwrite them from archive\n\
--K, --starting-file F begin at file F in the archive\n\
--l, --one-file-system stay in local file system when creating an archive\n\
--L, --tape-length LENGTH change tapes after writing LENGTH\n\
-", stdout); /* KLUDGE */
- fputs ("\
--m, --modification-time don't extract file modified time\n\
--M, --multi-volume create/list/extract multi-volume archive\n\
--N, --after-date DATE,\n\
- --newer DATE only store files newer than DATE\n\
--o, --old-archive,\n\
- --portability write a V7 format archive, rather than ANSI format\n\
--O, --to-stdout extract files to standard output\n\
--p, --same-permissions,\n\
- --preserve-permissions extract all protection information\n\
--P, --absolute-paths don't strip leading `/'s from file names\n\
---preserve like -p -s\n\
-", stdout); /* KLUDGE */
- fputs ("\
--R, --record-number show record number within archive with each message\n\
---remove-files remove files after adding them to the archive\n\
--s, --same-order,\n\
- --preserve-order list of names to extract is sorted to match archive\n\
---same-owner create extracted files with the same ownership \n\
--S, --sparse handle sparse files efficiently\n\
--T, --files-from F get names to extract or create from file F\n\
---null -T reads null-terminated names, disable -C\n\
---totals print total bytes written with --create\n\
--v, --verbose verbosely list files processed\n\
--V, --label NAME create archive with volume name NAME\n\
---version print tar program version number\n\
--w, --interactive,\n\
- --confirmation ask for confirmation for every action\n\
-", stdout); /* KLUDGE */
- fputs ("\
--W, --verify attempt to verify the archive after writing it\n\
---exclude FILE exclude file FILE\n\
--X, --exclude-from FILE exclude files listed in FILE\n\
--z, -Z, --compress,\n\
- --uncompress filter the archive through compress\n\
--[0-7][lmh] specify drive and density\n\
-", stdout);
+ argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+ exit (status);
}
-void
-name_add (name)
- char *name;
+/* Parse the options for tar. */
+
+static struct argp_option *
+find_argp_option (struct argp_option *options, int letter)
{
- if (n_indalloc == n_indused)
- {
- n_indalloc += 10;
- n_ind = (char **) (n_indused ? ck_realloc (n_ind, n_indalloc * sizeof (char *)): ck_malloc (n_indalloc * sizeof (char *)));
- }
- n_ind[n_indused++] = name;
+ for (;
+ !(options->name == NULL
+ && options->key == 0
+ && options->arg == 0
+ && options->flags == 0
+ && options->doc == NULL); options++)
+ if (options->key == letter)
+ return options;
+ return NULL;
}
-/*
- * Set up to gather file names for tar.
- *
- * They can either come from stdin or from argv.
- */
-void
-name_init (argc, argv)
- int argc;
- char **argv;
+static void
+decode_options (int argc, char **argv)
{
-
- if (f_namefile)
- {
- if (optind < argc)
- {
- msg ("too many args with -T option");
- exit (EX_ARGSBAD);
- }
- if (!strcmp (name_file, "-"))
- {
- namef = stdin;
- }
- else
- {
- namef = fopen (name_file, "r");
- if (namef == NULL)
- {
- msg_perror ("can't open file %s", name_file);
- exit (EX_BADFILE);
- }
- }
- }
- else
+ int index;
+ struct tar_args args;
+
+ /* Set some default option values. */
+ args.textual_date_option = NULL;
+ args.exclude_options = EXCLUDE_WILDCARDS;
+ args.o_option = 0;
+ args.pax_option = 0;
+ args.backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
+ args.version_control_string = 0;
+ args.input_files = 0;
+
+ subcommand_option = UNKNOWN_SUBCOMMAND;
+ archive_format = DEFAULT_FORMAT;
+ blocking_factor = DEFAULT_BLOCKING;
+ record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+ excluded = new_exclude ();
+ newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+ newer_mtime_option.tv_nsec = -1;
+ recursion_option = FNM_LEADING_DIR;
+ unquote_option = true;
+
+ owner_option = -1;
+ group_option = -1;
+
+ /* Convert old-style tar call by exploding option element and rearranging
+ options accordingly. */
+
+ if (argc > 1 && argv[1][0] != '-')
{
- /* Get file names from argv, after options. */
- n_argc = argc;
- n_argv = argv;
- }
-}
+ 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 */
-/* Read the next filename read from STREAM and null-terminate it.
- Put it into BUFFER, reallocating and adjusting *PBUFFER_SIZE if necessary.
- Return the new value for BUFFER, or NULL at end of file. */
+ /* Initialize a constructed option. */
-char *
-read_name_from_file (buffer, pbuffer_size, stream)
- char *buffer;
- size_t *pbuffer_size;
- FILE *stream;
-{
- register int c;
- register int indx = 0;
- register size_t buffer_size = *pbuffer_size;
+ buffer[0] = '-';
+ buffer[2] = '\0';
- while ((c = getc (stream)) != EOF && c != filename_terminator)
- {
- if (indx == buffer_size)
- {
- buffer_size += NAMSIZ;
- buffer = ck_realloc (buffer, buffer_size + 2);
- }
- buffer[indx++] = c;
- }
- if (indx == 0 && c == EOF)
- return NULL;
- if (indx == buffer_size)
- {
- buffer_size += NAMSIZ;
- buffer = ck_realloc (buffer, buffer_size + 2);
- }
- buffer[indx] = '\0';
- *pbuffer_size = buffer_size;
- return buffer;
-}
+ /* Allocate a new argument array, and copy program name in it. */
-/*
- * Get the next name from argv or the name file.
- *
- * Result is in static storage and can't be relied upon across two calls.
- *
- * If CHANGE_DIRS is non-zero, 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 '\0', CHANGE_DIRS is effectively always 0.
- */
+ new_argc = argc - 1 + strlen (argv[1]);
+ new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
+ in = argv;
+ out = new_argv;
+ *out++ = *in++;
-char *
-name_next (change_dirs)
- int change_dirs;
-{
- static char *buffer; /* Holding pattern */
- static int buffer_siz;
- register char *p;
- register char *q = 0;
- register int next_name_is_dir = 0;
- extern char *un_quote_string ();
-
- if (buffer_siz == 0)
- {
- buffer = ck_malloc (NAMSIZ + 2);
- buffer_siz = NAMSIZ;
- }
- if (filename_terminator == '\0')
- change_dirs = 0;
-tryagain:
- if (namef == NULL)
- {
- if (n_indscan < n_indused)
- p = n_ind[n_indscan++];
- else if (optind < n_argc)
- /* Names come from argv, after options */
- p = n_argv[optind++];
- else
- {
- if (q)
- msg ("Missing filename after -C");
- return NULL;
- }
+ /* Copy each old letter option as a separate option, and have the
+ corresponding argument moved next to it. */
- /* JF trivial support for -C option. I don't know if
- chdir'ing at this point is dangerous or not.
- It seems to work, which is all I ask. */
- if (change_dirs && !q && p[0] == '-' && p[1] == 'C' && p[2] == '\0')
- {
- q = p;
- goto tryagain;
- }
- if (q)
+ for (letter = *in++; *letter; letter++)
{
- if (chdir (p) < 0)
- msg_perror ("Can't chdir to %s", p);
- q = 0;
- goto tryagain;
- }
- /* End of JF quick -C hack */
+ struct argp_option *opt;
- if (f_exclude && check_exclude (p))
- goto tryagain;
- return un_quote_string (p);
- }
- while (p = read_name_from_file (buffer, &buffer_siz, namef))
- {
- buffer = p;
- if (*p == '\0')
- continue; /* Ignore empty lines. */
- q = p + strlen (p) - 1;
- while (q > p && *q == '/')/* Zap trailing "/"s. */
- *q-- = '\0';
- if (change_dirs && next_name_is_dir == 0
- && p[0] == '-' && p[1] == 'C' && p[2] == '\0')
- {
- next_name_is_dir = 1;
- goto tryagain;
- }
- if (next_name_is_dir)
- {
- if (chdir (p) < 0)
- msg_perror ("Can't change to directory %s", p);
- next_name_is_dir = 0;
- goto tryagain;
+ buffer[1] = *letter;
+ *out++ = xstrdup (buffer);
+ opt = find_argp_option (options, *letter);
+ if (opt && opt->arg)
+ {
+ if (in < argv + argc)
+ *out++ = *in++;
+ else
+ USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."),
+ *letter));
+ }
}
- if (f_exclude && check_exclude (p))
- goto tryagain;
- return un_quote_string (p);
- }
- return NULL;
-}
+ /* Copy all remaining options. */
-/*
- * Close the name file, if any.
- */
-void
-name_close ()
-{
+ while (in < argv + argc)
+ *out++ = *in++;
+ *out = 0;
- if (namef != NULL && namef != stdin)
- fclose (namef);
-}
+ /* Replace the old option list by the new one. */
+ argc = new_argc;
+ argv = new_argv;
+ }
-/*
- * 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 ()
-{
- register char *p;
- static struct name *namebuf; /* One-name buffer */
- static namelen;
- static char *chdir_name;
+ /* Parse all options and non-options as they appear. */
- if (f_sorted_names)
- {
- if (!namelen)
- {
- namelen = NAMSIZ;
- namebuf = (struct name *) ck_malloc (sizeof (struct name) + NAMSIZ);
- }
- p = name_next (0);
- if (p)
- {
- if (*p == '-' && p[1] == 'C' && p[2] == '\0')
- {
- chdir_name = name_next (0);
- p = name_next (0);
- if (!p)
- {
- msg ("Missing file name after -C");
- exit (EX_ARGSBAD);
- }
- namebuf->change_dir = chdir_name;
- }
- namebuf->length = strlen (p);
- if (namebuf->length >= namelen)
- {
- namebuf = (struct name *) ck_realloc (namebuf, sizeof (struct name) + namebuf->length);
- namelen = namebuf->length;
- }
- strncpy (namebuf->name, p, namebuf->length);
- namebuf->name[namebuf->length] = 0;
- namebuf->next = (struct name *) NULL;
- namebuf->found = 0;
- namelist = namebuf;
- namelast = namelist;
- }
- return;
- }
+ prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
- /* Non sorted names -- read them all in */
- while (p = name_next (0))
- addname (p);
-}
+ if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP,
+ &index, &args))
+ exit (1);
-/*
- * Add a name to the namelist.
- */
-void
-addname (name)
- char *name; /* pointer to name */
-{
- register int i; /* Length of string */
- register struct name *p; /* Current struct pointer */
- static char *chdir_name;
- char *new_name ();
- if (name[0] == '-' && name[1] == 'C' && name[2] == '\0')
+ /* Special handling for 'o' option:
+
+ GNU tar used to say "output old format".
+ UNIX98 tar says don't chown files after extracting (we use
+ "--no-same-owner" for this).
+
+ The old GNU tar semantics is retained when used with --create
+ option, otherwise UNIX98 semantics is assumed */
+
+ if (args.o_option)
{
- chdir_name = name_next (0);
- name = name_next (0);
- if (!chdir_name)
+ if (subcommand_option == CREATE_SUBCOMMAND)
{
- msg ("Missing file name after -C");
- exit (EX_ARGSBAD);
+ /* GNU Tar <= 1.13 compatibility */
+ set_archive_format ("v7");
}
- if (chdir_name[0] != '/')
+ else
{
- char *path = ck_malloc (PATH_MAX);
-#if defined(__MSDOS__) || defined(USG) || defined(_POSIX_VERSION)
- if (!getcwd (path, PATH_MAX))
- {
- msg ("Couldn't get current directory.");
- exit (EX_SYSTEM);
- }
-#else
- char *getwd ();
-
- if (!getwd (path))
- {
- msg ("Couldn't get current directory: %s", path);
- exit (EX_SYSTEM);
- }
-#endif
- chdir_name = new_name (path, chdir_name);
- free (path);
+ /* UNIX98 compatibility */
+ same_owner_option = -1;
}
}
- if (name)
+ /* Handle operands after any "--" argument. */
+ for (; index < argc; index++)
{
- i = strlen (name);
- /*NOSTRICT*/
- p = (struct name *) malloc ((unsigned) (sizeof (struct name) + i));
+ name_add (argv[index]);
+ args.input_files++;
}
- else
- p = (struct name *) malloc ((unsigned) (sizeof (struct name)));
- if (!p)
+
+ /* Derive option values and check option consistency. */
+
+ if (archive_format == DEFAULT_FORMAT)
{
- if (name)
- msg ("cannot allocate mem for name '%s'.", name);
+ if (args.pax_option)
+ archive_format = POSIX_FORMAT;
else
- msg ("cannot allocate mem for chdir record.");
- exit (EX_SYSTEM);
- }
- p->next = (struct name *) NULL;
- if (name)
- {
- p->fake = 0;
- p->length = i;
- strncpy (p->name, name, i);
- p->name[i] = '\0'; /* Null term */
- }
- else
- p->fake = 1;
- p->found = 0;
- p->regexp = 0; /* Assume not a regular expression */
- p->firstch = 1; /* Assume first char is literal */
- p->change_dir = chdir_name;
- p->dir_contents = 0; /* JF */
- if (name)
- {
- if (index (name, '*') || index (name, '[') || index (name, '?'))
- {
- p->regexp = 1; /* No, it's a regexp */
- if (name[0] == '*' || name[0] == '[' || name[0] == '?')
- p->firstch = 0; /* Not even 1st char literal */
- }
+ archive_format = DEFAULT_ARCHIVE_FORMAT;
}
- if (namelast)
- namelast->next = p;
- namelast = p;
- if (!namelist)
- namelist = p;
-}
+ if (volume_label_option && subcommand_option == CREATE_SUBCOMMAND)
+ assert_format (FORMAT_MASK (OLDGNU_FORMAT)
+ | FORMAT_MASK (GNU_FORMAT));
-/*
- * Return nonzero if name P (from an archive) matches any name from
- * the namelist, zero if not.
- */
-int
-name_match (p)
- register char *p;
-{
- register struct name *nlp;
- register int len;
-again:
- if (0 == (nlp = namelist)) /* Empty namelist is easy */
- return 1;
- if (nlp->fake)
+ if (incremental_option || multi_volume_option)
+ assert_format (FORMAT_MASK (OLDGNU_FORMAT) | FORMAT_MASK (GNU_FORMAT));
+
+ if (sparse_option)
+ assert_format (FORMAT_MASK (OLDGNU_FORMAT)
+ | FORMAT_MASK (GNU_FORMAT)
+ | FORMAT_MASK (POSIX_FORMAT));
+
+ if (occurrence_option)
{
- if (nlp->change_dir && chdir (nlp->change_dir))
- msg_perror ("Can't change to directory %d", nlp->change_dir);
- namelist = 0;
- return 1;
+ if (!args.input_files)
+ USAGE_ERROR ((0, 0,
+ _("--occurrence is meaningless without a file list")));
+ if (subcommand_option != DELETE_SUBCOMMAND
+ && subcommand_option != DIFF_SUBCOMMAND
+ && subcommand_option != EXTRACT_SUBCOMMAND
+ && subcommand_option != LIST_SUBCOMMAND)
+ USAGE_ERROR ((0, 0,
+ _("--occurrence cannot be used in the requested operation mode")));
}
- len = strlen (p);
- for (; nlp != 0; nlp = nlp->next)
- {
- /* If first chars don't match, quick skip */
- if (nlp->firstch && nlp->name[0] != p[0])
- continue;
-
- /* Regular expressions (shell globbing, actually). */
- if (nlp->regexp)
- {
- if (fnmatch (nlp->name, p, FNM_TARPATH) == 0)
- {
- nlp->found = 1; /* Remember it matched */
- if (f_startfile)
- {
- free ((void *) namelist);
- namelist = 0;
- }
- if (nlp->change_dir && chdir (nlp->change_dir))
- msg_perror ("Can't change to directory %s", nlp->change_dir);
- return 1; /* We got a match */
- }
- continue;
- }
- /* Plain Old Strings */
- if (nlp->length <= len /* Archive len >= specified */
- && (p[nlp->length] == '\0' || p[nlp->length] == '/')
- /* Full match on file/dirname */
- && strncmp (p, nlp->name, nlp->length) == 0) /* Name compare */
- {
- nlp->found = 1; /* Remember it matched */
- if (f_startfile)
- {
- free ((void *) namelist);
- namelist = 0;
- }
- if (nlp->change_dir && chdir (nlp->change_dir))
- msg_perror ("Can't change to directory %s", nlp->change_dir);
- return 1; /* We got a match */
- }
+ if (seekable_archive && subcommand_option == DELETE_SUBCOMMAND)
+ {
+ /* The current code in delete.c is based on the assumption that
+ skip_member() reads all data from the archive. So, we should
+ make sure it won't use seeks. On the other hand, the same code
+ depends on the ability to backspace a record in the archive,
+ so setting seekable_archive to false is technically incorrect.
+ However, it is tested only in skip_member(), so it's not a
+ problem. */
+ seekable_archive = false;
}
- /*
- * 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 (f_sorted_names && namelist->found)
+ if (archive_names == 0)
{
- name_gather (); /* Read one more */
- if (!namelist->found)
- goto again;
+ /* 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;
}
- return 0;
-}
+ /* Allow multiple archives only with `-M'. */
-/*
- * Print the names of things in the namelist that were not matched.
- */
-void
-names_notfound ()
-{
- register struct name *nlp, *next;
- register char *p;
+ if (archive_names > 1 && !multi_volume_option)
+ USAGE_ERROR ((0, 0,
+ _("Multiple archive files require `-M' option")));
+
+ if (listed_incremental_option
+ && NEWER_OPTION_INITIALIZED (newer_mtime_option))
+ USAGE_ERROR ((0, 0,
+ _("Cannot combine --listed-incremental with --newer")));
- for (nlp = namelist; nlp != 0; nlp = next)
+ if (volume_label_option)
{
- next = nlp->next;
- if (!nlp->found)
- msg ("%s not found in archive", nlp->name);
-
- /*
- * We could free() the list, but the process is about
- * to die anyway, so save some CPU time. Amigas and
- * other similarly broken software will need to waste
- * the time, though.
- */
-#ifdef amiga
- if (!f_sorted_names)
- free (nlp);
-#endif
+ 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,
+ ngettext ("%s: Volume label is too long (limit is %lu byte)",
+ "%s: Volume label is too long (limit is %lu bytes)",
+ volume_label_max_len),
+ quotearg_colon (volume_label_option),
+ (unsigned long) volume_label_max_len));
}
- namelist = (struct name *) NULL;
- namelast = (struct name *) NULL;
- if (f_sorted_names)
+ if (verify_option)
{
- while (0 != (p = name_next (1)))
- msg ("%s not found in archive", p);
+ if (multi_volume_option)
+ USAGE_ERROR ((0, 0, _("Cannot verify multi-volume archives")));
+ if (use_compress_program_option)
+ USAGE_ERROR ((0, 0, _("Cannot verify compressed archives")));
}
-}
-/* These next routines were created by JF */
+ if (use_compress_program_option)
+ {
+ if (multi_volume_option)
+ USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
+ if (subcommand_option == UPDATE_SUBCOMMAND)
+ USAGE_ERROR ((0, 0, _("Cannot update compressed archives")));
+ }
-void
-name_expand ()
-{
- ;
-}
+ /* It is no harm to use --pax-option on non-pax archives in archive
+ reading mode. It may even be useful, since it allows to override
+ file attributes from tar headers. Therefore I allow such usage.
+ --gray */
+ if (args.pax_option
+ && archive_format != POSIX_FORMAT
+ && (subcommand_option != EXTRACT_SUBCOMMAND
+ || subcommand_option != DIFF_SUBCOMMAND
+ || subcommand_option != LIST_SUBCOMMAND))
+ USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives")));
-/* This is like name_match(), except that it returns a pointer to the name
- it matched, and doesn't set ->found The caller will have to do that
- if it wants to. Oh, and if the namelist is empty, it returns 0, unlike
- name_match(), which returns TRUE */
+ /* If ready to unlink hierarchies, so we are for simpler files. */
+ if (recursive_unlink_option)
+ old_files_option = UNLINK_FIRST_OLD_FILES;
-struct name *
-name_scan (p)
- register char *p;
-{
- register struct name *nlp;
- register int len;
-
-again:
- if (0 == (nlp = namelist)) /* Empty namelist is easy */
- return 0;
- len = strlen (p);
- for (; nlp != 0; nlp = nlp->next)
+ if (utc_option)
+ verbose_option = 2;
+
+ /* Forbid using -c with no input files whatsoever. Check that `-f -',
+ explicit or implied, is used correctly. */
+
+ switch (subcommand_option)
{
- /* If first chars don't match, quick skip */
- if (nlp->firstch && nlp->name[0] != p[0])
- continue;
+ case CREATE_SUBCOMMAND:
+ if (args.input_files == 0 && !files_from_option)
+ USAGE_ERROR ((0, 0,
+ _("Cowardly refusing to create an empty archive")));
+ break;
- /* Regular expressions */
- if (nlp->regexp)
- {
- if (fnmatch (nlp->name, p, FNM_TARPATH) == 0)
- return nlp; /* We got a match */
- continue;
- }
+ 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;
- /* Plain Old Strings */
- if (nlp->length <= len /* Archive len >= specified */
- && (p[nlp->length] == '\0' || p[nlp->length] == '/')
- /* Full match on file/dirname */
- && strncmp (p, nlp->name, nlp->length) == 0) /* Name compare */
- return nlp; /* We got a match */
+ 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;
}
- /*
- * 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 (f_sorted_names && namelist->found)
- {
- name_gather (); /* Read one more */
- if (!namelist->found)
- goto again;
- }
- return (struct name *) 0;
-}
+ archive_name_cursor = archive_name_array;
-/* 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 */
+ /* Prepare for generating backup names. */
-struct name *gnu_list_name;
+ if (args.backup_suffix_string)
+ simple_backup_suffix = xstrdup (args.backup_suffix_string);
-char *
-name_from_list ()
-{
- if (!gnu_list_name)
- gnu_list_name = namelist;
- while (gnu_list_name && gnu_list_name->found)
- gnu_list_name = gnu_list_name->next;
- if (gnu_list_name)
+ if (backup_option)
{
- gnu_list_name->found++;
- if (gnu_list_name->change_dir)
- if (chdir (gnu_list_name->change_dir) < 0)
- msg_perror ("can't chdir to %s", gnu_list_name->change_dir);
- return gnu_list_name->name;
+ backup_type = xget_version ("--backup", args.version_control_string);
+ /* No backup is needed either if explicitely disabled or if
+ the extracted files are not being written to disk. */
+ if (backup_type == no_backups || EXTRACT_OVER_PIPE)
+ backup_option = false;
}
- return (char *) 0;
-}
-
-void
-blank_name_list ()
-{
- struct name *n;
-
- gnu_list_name = 0;
- for (n = namelist; n; n = n->next)
- n->found = 0;
-}
-
-char *
-new_name (path, name)
- char *path, *name;
-{
- char *path_buf;
- path_buf = (char *) malloc (strlen (path) + strlen (name) + 2);
- if (path_buf == 0)
+ if (verbose_option && args.textual_date_option)
{
- msg ("Can't allocate memory for name '%s/%s", path, name);
- exit (EX_SYSTEM);
+ char const *treated_as = tartime (newer_mtime_option, true);
+ if (strcmp (args.textual_date_option, treated_as) != 0)
+ WARN ((0, 0, _("Treating date `%s' as %s"),
+ args.textual_date_option, treated_as));
}
- (void) sprintf (path_buf, "%s/%s", path, name);
- return path_buf;
}
-/* returns non-zero if the luser typed 'y' or 'Y', zero otherwise. */
+\f
+/* Tar proper. */
+/* Main routine for tar. */
int
-confirm (action, file)
- char *action, *file;
+main (int argc, char **argv)
{
- int c, nl;
- static FILE *confirm_file = 0;
- extern FILE *msg_file;
- extern char TTY_NAME[];
+ set_start_time ();
+ program_name = argv[0];
- fprintf (msg_file, "%s %s?", action, file);
- fflush (msg_file);
- if (!confirm_file)
- {
- confirm_file = (archive == 0) ? fopen (TTY_NAME, "r") : stdin;
- if (!confirm_file)
- {
- msg ("Can't read confirmation from user");
- exit (EX_SYSTEM);
- }
- }
- c = getc (confirm_file);
- for (nl = c; nl != '\n' && nl != EOF; nl = getc (confirm_file))
- ;
- return (c == 'y' || c == 'Y');
-}
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
-char *x_buffer = 0;
-int size_x_buffer;
-int free_x_buffer;
+ exit_status = TAREXIT_SUCCESS;
+ filename_terminator = '\n';
+ set_quoting_style (0, escape_quoting_style);
-char **exclude = 0;
-int size_exclude = 0;
-int free_exclude = 0;
+ /* Pre-allocate a few structures. */
-char **re_exclude = 0;
-int size_re_exclude = 0;
-int free_re_exclude = 0;
+ allocated_archive_names = 10;
+ archive_name_array =
+ xmalloc (sizeof (const char *) * allocated_archive_names);
+ archive_names = 0;
-void
-add_exclude (name)
- char *name;
-{
- /* char *rname;*/
- /* char **tmp_ptr;*/
- int size_buf;
+ obstack_init (&argv_stk);
- un_quote_string (name);
- size_buf = strlen (name);
+#ifdef SIGCHLD
+ /* System V fork+wait does not work if SIGCHLD is ignored. */
+ signal (SIGCHLD, SIG_DFL);
+#endif
- if (x_buffer == 0)
- {
- x_buffer = (char *) ck_malloc (size_buf + 1024);
- free_x_buffer = 1024;
- }
- else if (free_x_buffer <= size_buf)
- {
- char *old_x_buffer;
- char **tmp_ptr;
-
- old_x_buffer = x_buffer;
- x_buffer = (char *) ck_realloc (x_buffer, size_x_buffer + 1024);
- free_x_buffer = 1024;
- for (tmp_ptr = exclude; tmp_ptr < exclude + size_exclude; tmp_ptr++)
- *tmp_ptr = x_buffer + ((*tmp_ptr) - old_x_buffer);
- for (tmp_ptr = re_exclude; tmp_ptr < re_exclude + size_re_exclude; tmp_ptr++)
- *tmp_ptr = x_buffer + ((*tmp_ptr) - old_x_buffer);
- }
+ init_names ();
- if (is_regex (name))
- {
- if (free_re_exclude == 0)
- {
- re_exclude = (char **) (re_exclude ? ck_realloc (re_exclude, (size_re_exclude + 32) * sizeof (char *)): ck_malloc (sizeof (char *) * 32));
- free_re_exclude += 32;
- }
- re_exclude[size_re_exclude] = x_buffer + size_x_buffer;
- size_re_exclude++;
- free_re_exclude--;
- }
- else
- {
- if (free_exclude == 0)
- {
- exclude = (char **) (exclude ? ck_realloc (exclude, (size_exclude + 32) * sizeof (char *)): ck_malloc (sizeof (char *) * 32));
- free_exclude += 32;
- }
- exclude[size_exclude] = x_buffer + size_x_buffer;
- size_exclude++;
- free_exclude--;
- }
- strcpy (x_buffer + size_x_buffer, name);
- size_x_buffer += size_buf + 1;
- free_x_buffer -= size_buf + 1;
-}
+ /* Decode options. */
-void
-add_exclude_file (file)
- char *file;
-{
- FILE *fp;
- char buf[1024];
+ decode_options (argc, argv);
+ name_init ();
- if (strcmp (file, "-"))
- fp = fopen (file, "r");
- else
- /* Let's hope the person knows what they're doing. */
- /* Using -X - -T - -f - will get you *REALLY* strange
- results. . . */
- fp = stdin;
+ /* Main command execution. */
- if (!fp)
- {
- msg_perror ("can't open %s", file);
- exit (2);
- }
- while (fgets (buf, 1024, fp))
+ if (volno_file_option)
+ init_volume_number ();
+
+ switch (subcommand_option)
{
- /* int size_buf;*/
- char *end_str;
+ 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;
- end_str = rindex (buf, '\n');
- if (end_str)
- *end_str = '\0';
- add_exclude (buf);
+ case DELETE_SUBCOMMAND:
+ delete_archive_members ();
+ break;
+
+ case CREATE_SUBCOMMAND:
+ create_archive ();
+ if (totals_option)
+ print_total_written ();
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ extr_init ();
+ read_and (extract_archive);
+
+ /* FIXME: should extract_finish () even if an ordinary signal is
+ received. */
+ extract_finish ();
+ break;
+
+ case LIST_SUBCOMMAND:
+ read_and (list_archive);
+ break;
+
+ case DIFF_SUBCOMMAND:
+ diff_init ();
+ read_and (diff_archive);
+ break;
}
- fclose (fp);
+
+ if (check_links_option)
+ check_links ();
+
+ if (volno_file_option)
+ closeout_volume_number ();
+
+ /* Dispose of allocated memory, and return. */
+
+ free (archive_name_array);
+ name_term ();
+
+ if (stdlis != stderr && (ferror (stdlis) || fclose (stdlis) != 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"));
+ if (ferror (stderr) || fclose (stderr) != 0)
+ exit_status = TAREXIT_FAILURE;
+ return exit_status;
}
-int
-is_regex (str)
- char *str;
+void
+tar_stat_init (struct tar_stat_info *st)
{
- return index (str, '*') || index (str, '[') || index (str, '?');
+ memset (st, 0, sizeof (*st));
}
-/* Returns non-zero if the file 'name' should not be added/extracted */
-int
-check_exclude (name)
- char *name;
+void
+tar_stat_destroy (struct tar_stat_info *st)
{
- int n;
- char *str;
- extern char *strstr ();
-
- for (n = 0; n < size_re_exclude; n++)
- {
- if (fnmatch (re_exclude[n], name, FNM_TARPATH) == 0)
- return 1;
- }
- for (n = 0; n < size_exclude; n++)
- {
- /* Accept the output from strstr only if it is the last
- part of the string. There is certainly a faster way to
- do this. . . */
- if ((str = strstr (name, exclude[n]))
- && (str == name || str[-1] == '/')
- && str[strlen (exclude[n])] == '\0')
- return 1;
- }
- return 0;
+ free (st->orig_file_name);
+ free (st->file_name);
+ free (st->link_name);
+ free (st->uname);
+ free (st->gname);
+ free (st->sparse_map);
+ memset (st, 0, sizeof (*st));
}