/* A tar (tape archiver) program.
- Copyright (C) 1988, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
+ 2001, 2003, 2004, 2005 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
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
- 59 Place - Suite 330, Boston, MA 02111-1307, USA. */
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
-#include "system.h"
+#include <fnmatch.h>
+#include <argp.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 initialisation is usually not done. */
+ zero) values; explicit initialization is usually not done. */
#define GLOBAL
#include "common.h"
-#include "backupfile.h"
-enum backup_type get_version ();
-
-/* FIXME: We should use a conversion routine that does reasonable error
- checking -- atoi doesn't. For now, punt. */
-#define intconv atoi
-
-time_t get_date ();
+#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 DEFAULT_ARCHIVE
# define DEFAULT_ARCHIVE "tar.out"
#endif
# define DEFAULT_BLOCKING 20
#endif
-static void usage PARAMS ((int));
\f
/* Miscellaneous. */
-/*------------------------------------------------------------------------.
-| Check if STRING is the decimal representation of number, and return its |
-| value. If not a decimal number, return -1. |
-`------------------------------------------------------------------------*/
-
-static int
-check_decimal (const char *string)
-{
- int value = -1;
-
- while (*string)
- switch (*string)
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- value = value < 0 ? *string - '0' : 10 * value + *string - '0';
- string++;
- break;
-
- default:
- return -1;
- }
- return value;
-}
-
-/*----------------------------------------------.
-| Doesn't return if stdin already requested. |
-`----------------------------------------------*/
-
/* Name of option using stdin. */
-static const char *stdin_used_by = NULL;
+static const char *stdin_used_by;
+/* Doesn't return if stdin already requested. */
void
request_stdin (const char *option)
{
stdin_used_by = option;
}
-/*--------------------------------------------------------.
-| Returns true if and only if the user typed 'y' or 'Y'. |
-`--------------------------------------------------------*/
-
+/* 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 = NULL;
+ static FILE *confirm_file;
+ static int confirm_file_EOF;
if (!confirm_file)
{
if (archive == 0 || stdin_used_by)
- confirm_file = fopen (TTY_NAME, "r");
+ {
+ confirm_file = fopen (TTY_NAME, "r");
+ if (! confirm_file)
+ open_fatal (TTY_NAME);
+ }
else
{
request_stdin ("-w");
confirm_file = stdin;
}
-
- if (!confirm_file)
- FATAL_ERROR ((0, 0, _("Cannot read confirmation from user")));
}
- fprintf (stdlis, "%s %s?", message_action, message_name);
+ fprintf (stdlis, "%s %s?", message_action, quote (message_name));
fflush (stdlis);
{
- int reply = getc (confirm_file);
+ int reply = confirm_file_EOF ? EOF : getc (confirm_file);
int character;
for (character = reply;
- character != '\n' && character != EOF;
+ character != '\n';
character = getc (confirm_file))
- continue;
+ 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. */
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 at 2 and going upwards. */
-
-#define BACKUP_OPTION 2
-#define DELETE_OPTION 3
-#define EXCLUDE_OPTION 4
-#define GROUP_OPTION 5
-#define MODE_OPTION 6
-#define NEWER_MTIME_OPTION 7
-#define NO_RECURSE_OPTION 8
-#define NULL_OPTION 9
-#define OWNER_OPTION 10
-#define POSIX_OPTION 11
-#define PRESERVE_OPTION 12
-#define RECORD_SIZE_OPTION 13
-#define RSH_COMMAND_OPTION 14
-#define SUFFIX_OPTION 15
-#define USE_COMPRESS_PROGRAM_OPTION 16
-#define VOLNO_FILE_OPTION 17
-
-/* 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. We use
- nongraphic characters as pseudo short option characters, starting at 31
- and going downwards. */
-
-#define OBSOLETE_ABSOLUTE_NAMES 31
-#define OBSOLETE_BLOCK_COMPRESS 30
-#define OBSOLETE_BLOCKING_FACTOR 29
-#define OBSOLETE_BLOCK_NUMBER 28
-#define OBSOLETE_READ_FULL_RECORDS 27
-#define OBSOLETE_TOUCH 26
-#define OBSOLETE_VERSION_CONTROL 25
-
-/* If nonzero, display usage information and exit. */
-static int show_help = 0;
-
-/* If nonzero, print the version on standard output and exit. */
-static int show_version = 0;
-
-struct option long_options[] =
+ short option, we use non-characters as pseudo short options,
+ starting at CHAR_MAX + 1 and going upwards. */
+
+enum
{
- {"absolute-names", no_argument, NULL, 'P'},
- {"absolute-paths", no_argument, NULL, OBSOLETE_ABSOLUTE_NAMES},
- {"after-date", required_argument, NULL, 'N'},
- {"append", no_argument, NULL, 'r'},
- {"atime-preserve", no_argument, &atime_preserve_option, 1},
- {"backup", optional_argument, NULL, BACKUP_OPTION},
- {"block-compress", no_argument, NULL, OBSOLETE_BLOCK_COMPRESS},
- {"block-number", no_argument, NULL, 'R'},
- {"block-size", required_argument, NULL, OBSOLETE_BLOCKING_FACTOR},
- {"blocking-factor", required_argument, NULL, 'b'},
- {"catenate", no_argument, NULL, 'A'},
- {"checkpoint", no_argument, &checkpoint_option, 1},
- {"compare", no_argument, NULL, 'd'},
- {"compress", no_argument, NULL, 'Z'},
- {"concatenate", no_argument, NULL, 'A'},
- {"confirmation", no_argument, NULL, 'w'},
- /* FIXME: --selective as a synonym for --confirmation? */
- {"create", no_argument, NULL, 'c'},
- {"delete", no_argument, NULL, DELETE_OPTION},
- {"dereference", no_argument, NULL, 'h'},
- {"diff", no_argument, NULL, 'd'},
- {"directory", required_argument, NULL, 'C'},
- {"exclude", required_argument, NULL, EXCLUDE_OPTION},
- {"exclude-from", required_argument, NULL, 'X'},
- {"extract", no_argument, NULL, 'x'},
- {"file", required_argument, NULL, 'f'},
- {"files-from", required_argument, NULL, 'T'},
- {"force-local", no_argument, &force_local_option, 1},
- {"get", no_argument, NULL, 'x'},
- {"group", required_argument, NULL, GROUP_OPTION},
- {"gunzip", no_argument, NULL, 'z'},
- {"gzip", no_argument, NULL, 'z'},
- {"help", no_argument, &show_help, 1},
- {"ignore-failed-read", no_argument, &ignore_failed_read_option, 1},
- {"ignore-zeros", no_argument, NULL, 'i'},
- /* FIXME: --ignore-end as a new name for --ignore-zeros? */
- {"incremental", no_argument, NULL, 'G'},
- {"info-script", required_argument, NULL, 'F'},
- {"interactive", no_argument, NULL, 'w'},
- {"keep-old-files", no_argument, NULL, 'k'},
- {"label", required_argument, NULL, 'V'},
- {"list", no_argument, NULL, 't'},
- {"listed-incremental", required_argument, NULL, 'g'},
- {"mode", required_argument, NULL, MODE_OPTION},
- {"modification-time", no_argument, NULL, OBSOLETE_TOUCH},
- {"multi-volume", no_argument, NULL, 'M'},
- {"new-volume-script", required_argument, NULL, 'F'},
- {"newer", required_argument, NULL, 'N'},
- {"newer-mtime", required_argument, NULL, NEWER_MTIME_OPTION},
- {"null", no_argument, NULL, NULL_OPTION},
- {"no-recursion", no_argument, NULL, NO_RECURSE_OPTION},
- {"numeric-owner", no_argument, &numeric_owner_option, 1},
- {"old-archive", no_argument, NULL, 'o'},
- {"one-file-system", no_argument, NULL, 'l'},
- {"owner", required_argument, NULL, OWNER_OPTION},
- {"portability", no_argument, NULL, 'o'},
- {"posix", no_argument, NULL, POSIX_OPTION},
- {"preserve", no_argument, NULL, PRESERVE_OPTION},
- {"preserve-order", no_argument, NULL, 's'},
- {"preserve-permissions", no_argument, NULL, 'p'},
- {"recursive-unlink", no_argument, &recursive_unlink_option, 1},
- {"read-full-blocks", no_argument, NULL, OBSOLETE_READ_FULL_RECORDS},
- {"read-full-records", no_argument, NULL, 'B'},
- /* FIXME: --partial-blocks might be a synonym for --read-full-records? */
- {"record-number", no_argument, NULL, OBSOLETE_BLOCK_NUMBER},
- {"record-size", required_argument, NULL, RECORD_SIZE_OPTION},
- {"remove-files", no_argument, &remove_files_option, 1},
- {"rsh-command", required_argument, NULL, RSH_COMMAND_OPTION},
- {"same-order", no_argument, NULL, 's'},
- {"same-owner", no_argument, &same_owner_option, 1},
- {"same-permissions", no_argument, NULL, 'p'},
- {"show-omitted-dirs", no_argument, &show_omitted_dirs_option, 1},
- {"sparse", no_argument, NULL, 'S'},
- {"starting-file", required_argument, NULL, 'K'},
- {"suffix", required_argument, NULL, SUFFIX_OPTION},
- {"tape-length", required_argument, NULL, 'L'},
- {"to-stdout", no_argument, NULL, 'O'},
- {"totals", no_argument, &totals_option, 1},
- {"touch", no_argument, NULL, 'm'},
- {"uncompress", no_argument, NULL, 'Z'},
- {"ungzip", no_argument, NULL, 'z'},
- {"unlink-first", no_argument, NULL, 'U'},
- {"update", no_argument, NULL, 'u'},
- {"use-compress-program", required_argument, NULL, USE_COMPRESS_PROGRAM_OPTION},
- {"verbose", no_argument, NULL, 'v'},
- {"verify", no_argument, NULL, 'W'},
- {"version", no_argument, &show_version, 1},
- {"version-control", required_argument, NULL, OBSOLETE_VERSION_CONTROL},
- {"volno-file", required_argument, NULL, VOLNO_FILE_OPTION},
-
- {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
};
-/*---------------------------------------------.
-| 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"), 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 overwrite existing files when extracting\n\
- -U, --unlink-first remove each file prior to extracting over it\n\
- --recursive-unlink empty hierarchies prior to extracting directory\n\
- -S, --sparse handle sparse files efficiently\n\
- -O, --to-stdout extract files to standard output\n\
- -G, --incremental handle old GNU-format incremental backup\n\
- -g, --listed-incremental handle new GNU-format incremental backup\n\
- --ignore-failed-read do not exit with nonzero on unreadable files\n"),
- stdout);
- fputs (_("\
-\n\
-Handling of file attributes:\n\
- --owner=NAME force NAME as owner for added files\n\
- --group=NAME force NAME as group for added files\n\
- --mode=CHANGES force (symbolic) mode CHANGES for added files\n\
- --atime-preserve don't change access times on dumped files\n\
- -m, --modification-time don't extract file modified time\n\
- --same-owner try extracting files with the same ownership\n\
- --numeric-owner always use numbers for user/group names\n\
- -p, --same-permissions extract all protection information\n\
- --preserve-permissions same as -p\n\
- -s, --same-order sort names to extract to match archive\n\
- --preserve-order same as -s\n\
- --preserve same as both -p and -s\n"),
- stdout);
- fputs (_("\
-\n\
-Device selection and switching:\n\
- -f, --file=ARCHIVE use archive file or device ARCHIVE\n\
- --force-local archive file is local even if has a colon\n\
- --rsh-command=COMMAND use remote COMMAND instead of rsh\n\
- -[0-7][lmh] specify drive and density\n\
- -M, --multi-volume create/list/extract multi-volume archive\n\
- -L, --tape-length=NUM change tape after writing NUM x 1024 bytes\n\
- -F, --info-script=FILE run script at end of each tape (implies -M)\n\
- --new-volume-script=FILE same as -F FILE\n\
- --volno-file=FILE use/update the volume number in FILE\n"),
- stdout);
- fputs (_("\
-\n\
-Device blocking:\n\
- -b, --blocking-factor=BLOCKS BLOCKS x 512 bytes per record\n\
- --record-size=SIZE SIZE bytes per record, multiple of 512\n\
- -i, --ignore-zeros ignore zeroed blocks in archive (means EOF)\n\
- -B, --read-full-records reblock as we read (for 4.2BSD pipes)\n"),
- stdout);
- fputs (_("\
-\n\
-Archive format selection:\n\
- -V, --label=NAME create archive with volume name NAME\n\
- PATTERN at list/extract time, a globbing PATTERN\n\
- -o, --old-archive, --portability write a V7 format archive\n\
- --posix write a POSIX conformant archive\n\
- -z, --gzip, --ungzip filter the archive through gzip\n\
- -Z, --compress, --uncompress filter the archive through compress\n\
- --use-compress-program=PROG filter through PROG (must accept -d)\n"),
- stdout);
- fputs (_("\
-\n\
-Local file selection:\n\
- -C, --directory=DIR change to directory DIR\n\
- -T, --files-from=NAME get names to extract or create from file NAME\n\
- --null -T reads null-terminated names, disable -C\n\
- --exclude=PATTERN exclude files, given as a globbing PATTERN\n\
- -X, --exclude-from=FILE exclude globbing patterns listed in FILE\n\
- -P, --absolute-names don't strip leading `/'s from file names\n\
- -h, --dereference dump instead the files symlinks point to\n\
- --no-recursion avoid descending automatically in directories\n\
- -l, --one-file-system stay in local file system when creating archive\n\
- -K, --starting-file=NAME begin at file NAME in the archive\n"),
- stdout);
-#if !MSDOS
- fputs (_("\
- -N, --newer=DATE only store files newer than DATE\n\
- --newer-mtime compare date and time when data changed only\n\
- --after-date=DATE same as -N\n"),
- stdout);
-#endif
- fputs (_("\
- --backup[=CONTROL] backup before removal, choose version control\n\
- --suffix=SUFFIX backup before removel, override usual suffix\n"),
- stdout);
- fputs (_("\
-\n\
-Informative output:\n\
- --help print this help, then exit\n\
- --version print tar program version number, then exit\n\
- -v, --verbose verbosely list files processed\n\
- --checkpoint print directory names while reading the archive\n\
- --totals print total bytes written while creating archive\n\
- -R, --block-number show block number within archive with each message\n\
- -w, --interactive ask for confirmation for every action\n\
- --confirmation same as -w\n"),
- stdout);
- fputs (_("\
-\n\
-The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
-The version control may be set with --backup or VERSION_CONTROL, values are:\n\
+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"),
- 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; and FILE may be a file\n\
-or a device. *This* `tar' defaults to `-f%s -b%d'.\n"),
- DEFAULT_ARCHIVE, DEFAULT_BLOCKING);
- fputs (_("\
-\n\
-Report bugs to <tar-bugs@gnu.ai.mit.edu>.\n"),
- stdout);
- }
- exit (status);
-}
+ never, simple always make simple backups\n");
+
-/*----------------------------.
-| Parse the options for tar. |
-`----------------------------*/
+/* NOTE:
-/* Available option letters are DEHIJQY and aejnqy. Some are reserved:
+ 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 */
-#define OPTION_STRING \
- "-01234567ABC:F:GK:L:MN:OPRST:UV:WX:Zb:cdf:g:hiklmoprstuvwxz"
+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)
static void
set_use_compress_program_option (const char *string)
{
- if (use_compress_program_option && strcmp (use_compress_program_option, string) != 0)
+ 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
+license ()
+{
+ 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);
+}
+
+static volatile int _argp_hang;
+
+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 */
+ };
+
+/* 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;
+
+ 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;
+
static void
-decode_options (int argc, char *const *argv)
+add_file_id (const char *filename)
{
- int optchar; /* option letter */
- int input_files; /* number of input files */
- const char *backup_suffix_string;
- const char *version_control_string;
+ struct file_id_list *p;
+ struct stat st;
- /* Set some default option values. */
+ 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;
+}
- subcommand_option = UNKNOWN_SUBCOMMAND;
- archive_format = DEFAULT_FORMAT;
- blocking_factor = DEFAULT_BLOCKING;
- record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+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, "-"))
+ {
+ is_stdin = true;
+ request_stdin ("-T");
+ fp = stdin;
+ }
+ else
+ {
+ add_file_id (filename);
+ if ((fp = fopen (filename, "r")) == NULL)
+ open_fatal (filename);
+ }
- owner_option = -1;
- group_option = -1;
+ while ((read_state = read_name_from_file (fp, &argv_stk)) == file_list_success)
+ count++;
- backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
- version_control_string = getenv ("VERSION_CONTROL");
+ 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++;
+ }
- /* Convert old-style tar call by exploding option element and rearranging
- options accordingly. */
+ if (!is_stdin)
+ fclose (fp);
- if (argc > 1 && argv[1][0] != '-')
+ 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++)
{
- 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 */
+ if (filename_terminator == 0 && p[0] == '-')
+ state->argv[i++] = "--add-file";
+ state->argv[i] = p;
+ }
+}
- /* Initialize a constructed option. */
+\f
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ struct tar_args *args = state->input;
- buffer[0] = '-';
- buffer[2] = '\0';
+ switch (key)
+ {
+ case ARGP_KEY_ARG:
+ /* File name or non-parsed option, because of ARGP_IN_ORDER */
+ name_add (arg);
+ args->input_files++;
+ break;
- /* Allocate a new argument array, and copy program name in it. */
+ case 'A':
+ set_subcommand_option (CAT_SUBCOMMAND);
+ break;
- new_argc = argc - 1 + strlen (argv[1]);
- new_argv = (char **) xmalloc (new_argc * sizeof (char *));
- in = argv;
- out = new_argv;
- *out++ = *in++;
+ 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;
- /* Copy each old letter option as a separate option, and have the
- corresponding argument moved next to it. */
+ case 'B':
+ /* Try to reblock input records. For reading 4.2BSD pipes. */
- for (letter = *in++; *letter; letter++)
+ /* 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 '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)
{
- 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));
+ allocated_archive_names *= 2;
+ archive_name_array =
+ xrealloc (archive_name_array,
+ sizeof (const char *) * allocated_archive_names);
}
+ archive_name_array[archive_names++] = arg;
+ break;
- /* Copy all remaining options. */
+ case 'F':
+ /* Since -F is only useful with -M, make it implied. Run this
+ script at the end of each tape. */
- while (in < argv + argc)
- *out++ = *in++;
+ info_script_option = arg;
+ multi_volume_option = true;
+ break;
- /* Replace the old option list by the new one. */
+ case 'g':
+ listed_incremental_option = arg;
+ after_date_option = true;
+ /* Fall through. */
- argc = new_argc;
- argv = new_argv;
- }
+ 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. */
- /* Parse all options and non-options as they appear. */
+ 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. */
- input_files = 0;
+ ignore_zeros_option = true;
+ break;
- while (optchar = getopt_long (argc, argv, OPTION_STRING, long_options, NULL),
- optchar != EOF)
- switch (optchar)
+ 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':
{
- case '?':
- usage (TAREXIT_FAILURE);
+ 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 0:
- break;
+ case 'm':
+ touch_option = true;
+ break;
- case 1:
- /* File name or non-parsed option, because of RETURN_IN_ORDER
- ordering triggerred by the leading dash in OPTION_STRING. */
+ case 'M':
+ /* Make multivolume archive: when we can't write any more into
+ the archive, re-open it, and continue writing. */
- name_add (optarg);
- input_files++;
- break;
+ multi_volume_option = true;
+ break;
- case 'A':
- set_subcommand_option (CAT_SUBCOMMAND);
- break;
+ case 'n':
+ seekable_archive = true;
+ break;
- case OBSOLETE_BLOCK_COMPRESS:
- WARN ((0, 0, _("Obsolete option, now implied by --blocking-factor")));
- break;
+#if !MSDOS
+ case 'N':
+ after_date_option = true;
+ /* Fall through. */
- case OBSOLETE_BLOCKING_FACTOR:
- WARN ((0, 0, _("Obsolete option name replaced by --blocking-factor")));
- /* Fall through. */
+ case NEWER_MTIME_OPTION:
+ if (NEWER_OPTION_INITIALIZED (newer_mtime_option))
+ USAGE_ERROR ((0, 0, _("More than one threshold date")));
- case 'b':
- blocking_factor = intconv (optarg);
- record_size = blocking_factor * BLOCKSIZE;
- break;
+ if (FILE_SYSTEM_PREFIX_LEN (arg) != 0
+ || ISSLASH (*arg)
+ || *arg == '.')
+ {
+ struct stat st;
+ if (deref_stat (dereference_option, arg, &st) != 0)
+ {
+ stat_error (arg);
+ USAGE_ERROR ((0, 0, _("Date sample file not found")));
+ }
+ newer_mtime_option = get_stat_mtime (&st);
+ }
+ else
+ {
+ if (! get_date (&newer_mtime_option, arg, NULL))
+ {
+ 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;
+ }
- case OBSOLETE_READ_FULL_RECORDS:
- WARN ((0, 0,
- _("Obsolete option name replaced by --read-full-records")));
- /* Fall through. */
+ break;
+#endif /* not MSDOS */
- case 'B':
- /* Try to reblock input records. For reading 4.2BSD pipes. */
+ case 'o':
+ args->o_option = true;
+ break;
- /* 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. */
+ case 'O':
+ to_stdout_option = true;
+ break;
- read_full_records_option = 1;
- break;
+ case 'p':
+ same_permissions_option = true;
+ break;
- case 'c':
- set_subcommand_option (CREATE_SUBCOMMAND);
- break;
+ case 'P':
+ absolute_names_option = true;
+ break;
- case 'C':
- name_add ("-C");
- name_add (optarg);
- break;
+ case 'r':
+ set_subcommand_option (APPEND_SUBCOMMAND);
+ break;
- case 'd':
- set_subcommand_option (DIFF_SUBCOMMAND);
- break;
+ case 'R':
+ /* Print block numbers for debugging bad tar archives. */
- case 'f':
- if (archive_names == allocated_archive_names)
- {
- allocated_archive_names *= 2;
- archive_name_array = (const char **)
- xrealloc (archive_name_array,
- sizeof (const char *) * allocated_archive_names);
- }
- archive_name_array[archive_names++] = optarg;
- break;
+ /* 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. */
- case 'F':
- /* Since -F is only useful with -M, make it implied. Run this
- script at the end of each tape. */
+ block_number_option = true;
+ break;
- info_script_option = optarg;
- multi_volume_option = 1;
- break;
+ case 's':
+ /* Names to extr are sorted. */
+
+ same_order_option = true;
+ break;
- case 'g':
- listed_incremental_option = optarg;
- /* Fall through. */
+ case 'S':
+ sparse_option = true;
+ break;
- 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. */
+ case 't':
+ set_subcommand_option (LIST_SUBCOMMAND);
+ verbose_option++;
+ break;
- incremental_option = 1;
- 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 'h':
- /* Follow symbolic links. */
+ case 'u':
+ set_subcommand_option (UPDATE_SUBCOMMAND);
+ break;
- dereference_option = 1;
- 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)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+ }
+ break;
+
+ case 'y':
+ USAGE_ERROR ((0, 0,
+ _("Warning: the -y option is not supported;"
+ " perhaps you meant -j?")));
+ break;
+
+ case 'z':
+ set_use_compress_program_option ("gzip");
+ break;
+
+ case 'Z':
+ set_use_compress_program_option ("compress");
+ break;
+
+ case ANCHORED_OPTION:
+ args->exclude_options |= EXCLUDE_ANCHORED;
+ break;
+
+ case ATIME_PRESERVE_OPTION:
+ atime_preserve_option = true;
+ break;
+
+ case CHECKPOINT_OPTION:
+ checkpoint_option = true;
+ break;
+
+ case BACKUP_OPTION:
+ backup_option = true;
+ if (arg)
+ args->version_control_string = arg;
+ break;
+
+ case DELETE_OPTION:
+ set_subcommand_option (DELETE_SUBCOMMAND);
+ break;
+
+ case EXCLUDE_OPTION:
+ add_exclude (excluded, arg, args->exclude_options | recursion_option);
+ break;
+
+ case EXCLUDE_CACHES_OPTION:
+ exclude_caches_option = true;
+ break;
+
+ case FORCE_LOCAL_OPTION:
+ force_local_option = true;
+ break;
+
+ case 'H':
+ set_archive_format (arg);
+ break;
+
+ case INDEX_FILE_OPTION:
+ index_file_name = arg;
+ break;
+
+ case IGNORE_CASE_OPTION:
+ args->exclude_options |= FNM_CASEFOLD;
+ break;
+
+ case IGNORE_COMMAND_ERROR_OPTION:
+ ignore_command_error_option = true;
+ break;
+
+ case IGNORE_FAILED_READ_OPTION:
+ ignore_failed_read_option = true;
+ break;
+
+ case KEEP_NEWER_FILES_OPTION:
+ old_files_option = KEEP_NEWER_FILES;
+ 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 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 NO_ANCHORED_OPTION:
+ args->exclude_options &= ~ EXCLUDE_ANCHORED;
+ break;
+
+ case NO_IGNORE_CASE_OPTION:
+ args->exclude_options &= ~ FNM_CASEFOLD;
+ 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 '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. */
+ case PAX_OPTION:
+ args->pax_option++;
+ xheader_set_option (arg);
+ break;
- ignore_zeros_option = 1;
- break;
+ case POSIX_OPTION:
+ set_archive_format ("posix");
+ break;
- case 'k':
- /* Don't overwrite existing files. */
+ case PRESERVE_OPTION:
+ same_permissions_option = true;
+ same_order_option = true;
+ break;
- keep_old_files_option = 1;
- 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 'K':
- starting_file_option = 1;
- addname (optarg);
- break;
+ case RECURSIVE_UNLINK_OPTION:
+ recursive_unlink_option = true;
+ break;
- case 'l':
- /* When dumping directories, don't dump files/subdirectories
- that are on other filesystems. */
+ case REMOVE_FILES_OPTION:
+ remove_files_option = true;
+ break;
- one_file_system_option = 1;
- break;
+ case RMT_COMMAND_OPTION:
+ rmt_command = arg;
+ break;
- case 'L':
- clear_tarlong (tape_length_option);
- add_to_tarlong (tape_length_option, intconv (optarg));
- mult_tarlong (tape_length_option, 1024);
- multi_volume_option = 1;
- break;
+ case RSH_COMMAND_OPTION:
+ rsh_command_option = arg;
+ break;
- case OBSOLETE_TOUCH:
- WARN ((0, 0, _("Obsolete option name replaced by --touch")));
- /* Fall through. */
+ case SHOW_DEFAULTS_OPTION:
+ show_default_settings (stdout);
+ exit(0);
- case 'm':
- touch_option = 1;
- break;
+ 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 'M':
- /* Make multivolume archive: when we can't write any more into
- the archive, re-open it, and continue writing. */
+ case SHOW_OMITTED_DIRS_OPTION:
+ show_omitted_dirs_option = true;
+ break;
- multi_volume_option = 1;
- break;
+ case SUFFIX_OPTION:
+ backup_option = true;
+ args->backup_suffix_string = arg;
+ break;
-#if !MSDOS
- case 'N':
- after_date_option = 1;
- /* Fall through. */
+ case TO_COMMAND_OPTION:
+ if (to_command_option)
+ USAGE_ERROR ((0, 0, _("Only one --to-command option allowed")));
+ to_command_option = arg;
+ break;
- case NEWER_MTIME_OPTION:
- if (newer_mtime_option)
- USAGE_ERROR ((0, 0, _("More than one threshold date")));
+ case TOTALS_OPTION:
+ totals_option = true;
+ break;
- newer_mtime_option = get_date (optarg, (voidstar) 0);
- if (newer_mtime_option == (time_t) -1)
- USAGE_ERROR ((0, 0, _("Invalid date format `%s'"), optarg));
+ case USE_COMPRESS_PROGRAM_OPTION:
+ set_use_compress_program_option (arg);
+ break;
- break;
-#endif /* not MSDOS */
+ case VOLNO_FILE_OPTION:
+ volno_file_option = arg;
+ break;
- 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 WILDCARDS_OPTION:
+ args->exclude_options |= EXCLUDE_WILDCARDS;
+ break;
- case 'O':
- to_stdout_option = 1;
- break;
+ case WILDCARDS_MATCH_SLASH_OPTION:
+ args->exclude_options &= ~ FNM_FILE_NAME;
+ break;
- case 'p':
- same_permissions_option = 1;
- break;
+ case CHECK_LINKS_OPTION:
+ check_links_option = 1;
+ break;
- case OBSOLETE_ABSOLUTE_NAMES:
- WARN ((0, 0, _("Obsolete option name replaced by --absolute-names")));
- /* Fall through. */
+ case NO_RECURSION_OPTION:
+ recursion_option = 0;
+ break;
- case 'P':
- absolute_names_option = 1;
- break;
+ case NO_SAME_OWNER_OPTION:
+ same_owner_option = -1;
+ break;
- case 'r':
- set_subcommand_option (APPEND_SUBCOMMAND);
- break;
+ case NO_SAME_PERMISSIONS_OPTION:
+ same_permissions_option = -1;
+ break;
- case OBSOLETE_BLOCK_NUMBER:
- WARN ((0, 0, _("Obsolete option name replaced by --block-number")));
- /* Fall through. */
+ case RECURSION_OPTION:
+ recursion_option = FNM_LEADING_DIR;
+ break;
- case 'R':
- /* Print block numbers for debugging bad tar archives. */
+ case SAME_OWNER_OPTION:
+ same_owner_option = 1;
+ break;
- /* 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. */
+ case UNQUOTE_OPTION:
+ unquote_option = true;
+ break;
- block_number_option = 1;
- break;
+ case NO_UNQUOTE_OPTION:
+ unquote_option = false;
+ break;
- case 's':
- /* Names to extr are sorted. */
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
- same_order_option = 1;
- break;
+#ifdef DEVICE_PREFIX
+ {
+ int device = key - '0';
+ int density;
+ static char buf[sizeof DEVICE_PREFIX + 10];
+ char *cursor;
- case 'S':
- sparse_option = 1;
- break;
+ if (arg[1])
+ argp_error (state, _("Malformed density argument: %s"), quote (arg));
- case 't':
- set_subcommand_option (LIST_SUBCOMMAND);
- verbose_option++;
- break;
+ strcpy (buf, DEVICE_PREFIX);
+ cursor = buf + strlen (buf);
- case 'T':
- files_from_option = optarg;
- break;
+#ifdef DENSITY_LETTER
- case 'u':
- set_subcommand_option (UPDATE_SUBCOMMAND);
- break;
+ sprintf (cursor, "%d%c", device, arg[0]);
- case 'U':
- unlink_first_option = 1;
- break;
+#else /* not DENSITY_LETTER */
- case 'v':
- verbose_option++;
- break;
+ switch (arg[0])
+ {
+ case 'l':
+#ifdef LOW_NUM
+ device += LOW_NUM;
+#endif
+ break;
- case 'V':
- volume_label_option = optarg;
- break;
+ case 'm':
+#ifdef MID_NUM
+ device += MID_NUM;
+#else
+ device += 8;
+#endif
+ break;
- case 'w':
- interactive_option = 1;
- break;
+ case 'h':
+#ifdef HGH_NUM
+ device += HGH_NUM;
+#else
+ device += 16;
+#endif
+ break;
- case 'W':
- verify_option = 1;
- break;
+ default:
+ argp_error (state, _("Unknown density: `%c'"), arg[0]);
+ }
+ sprintf (cursor, "%d", device);
- case 'x':
- set_subcommand_option (EXTRACT_SUBCOMMAND);
- break;
+#endif /* not DENSITY_LETTER */
- case 'X':
- exclude_option = 1;
- add_exclude_file (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 'z':
- set_use_compress_program_option ("gzip");
- break;
+#else /* not DEVICE_PREFIX */
- case 'Z':
- set_use_compress_program_option ("compress");
- break;
+ argp_error (state,
+ _("Options `-[0-7][lmh]' not supported by *this* tar"));
- case OBSOLETE_VERSION_CONTROL:
- WARN ((0, 0, _("Obsolete option name replaced by --backup")));
- /* Fall through. */
+#endif /* not DEVICE_PREFIX */
- case BACKUP_OPTION:
- backup_option = 1;
- if (optarg)
- version_control_string = 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 USAGE_OPTION:
+ argp_state_help (state, state->out_stream,
+ ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
+ break;
- case DELETE_OPTION:
- set_subcommand_option (DELETE_SUBCOMMAND);
- break;
+ case VERSION_OPTION:
+ fprintf (state->out_stream, "%s\n", argp_program_version);
+ exit (0);
- case EXCLUDE_OPTION:
- exclude_option = 1;
- add_exclude (optarg);
- break;
+ case LICENSE_OPTION:
+ license ();
+ break;
- case GROUP_OPTION:
- if (!gname_to_gid (optarg, &group_option))
- if (!check_decimal (optarg) >= 0)
- ERROR ((TAREXIT_FAILURE, 0, _("Invalid group given on option")));
- else
- group_option = check_decimal (optarg);
- break;
+ case HANG_OPTION:
+ _argp_hang = atoi (arg ? arg : "3600");
+ while (_argp_hang-- > 0)
+ sleep (1);
+ break;
- case MODE_OPTION:
- mode_option
- = mode_compile (optarg,
- MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS);
- if (mode_option == MODE_INVALID)
- ERROR ((TAREXIT_FAILURE, 0, _("Invalid mode given on option")));
- if (mode_option == MODE_MEMORY_EXHAUSTED)
- ERROR ((TAREXIT_FAILURE, 0, _("Memory exhausted")));
- break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
- case NO_RECURSE_OPTION:
- no_recurse_option = 1;
- break;
+static struct argp argp = {
+ options,
+ parse_opt,
+ N_("[FILE]..."),
+ doc,
+ NULL,
+ NULL,
+ NULL
+};
- case NULL_OPTION:
- filename_terminator = '\0';
- break;
+void
+usage (int status)
+{
+ argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+ exit (status);
+}
- case OWNER_OPTION:
- if (!uname_to_uid (optarg, &owner_option))
- if (!check_decimal (optarg) >= 0)
- ERROR ((TAREXIT_FAILURE, 0, _("Invalid owner given on option")));
- else
- owner_option = check_decimal (optarg);
- break;
+/* Parse the options for tar. */
- 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;
+static struct argp_option *
+find_argp_option (struct argp_option *options, int letter)
+{
+ 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;
+}
- case PRESERVE_OPTION:
- same_permissions_option = 1;
- same_order_option = 1;
- break;
+static void
+decode_options (int argc, char **argv)
+{
+ int index;
+ struct tar_args args;
- case RECORD_SIZE_OPTION:
- record_size = intconv (optarg);
- if (record_size % BLOCKSIZE != 0)
- USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
- BLOCKSIZE));
- blocking_factor = record_size / BLOCKSIZE;
- break;
+ /* 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;
- case RSH_COMMAND_OPTION:
- rsh_command_option = optarg;
- break;
+ 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;
- case SUFFIX_OPTION:
- backup_option = 1;
- backup_suffix_string = optarg;
- break;
+ owner_option = -1;
+ group_option = -1;
- case VOLNO_FILE_OPTION:
- volno_file_option = optarg;
- break;
+ /* Convert old-style tar call by exploding option element and rearranging
+ options accordingly. */
- case USE_COMPRESS_PROGRAM_OPTION:
- set_use_compress_program_option (optarg);
- break;
+ 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 */
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
+ /* Initialize a constructed option. */
-#ifdef DEVICE_PREFIX
- {
- int device = optchar - '0';
- int density;
- static char buf[sizeof DEVICE_PREFIX + 10];
- char *cursor;
+ buffer[0] = '-';
+ buffer[2] = '\0';
- density = getopt_long (argc, argv, "lmh", NULL, NULL);
- strcpy (buf, DEVICE_PREFIX);
- cursor = buf + strlen (buf);
+ /* Allocate a new argument array, and copy program name in it. */
-#ifdef DENSITY_LETTER
+ new_argc = argc - 1 + strlen (argv[1]);
+ new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
+ in = argv;
+ out = new_argv;
+ *out++ = *in++;
- sprintf (cursor, "%d%c", device, density);
+ /* Copy each old letter option as a separate option, and have the
+ corresponding argument moved next to it. */
-#else /* not DENSITY_LETTER */
+ for (letter = *in++; *letter; letter++)
+ {
+ struct argp_option *opt;
- switch (density)
+ buffer[1] = *letter;
+ *out++ = xstrdup (buffer);
+ opt = find_argp_option (options, *letter);
+ if (opt && opt->arg)
{
- case 'l':
-#ifdef LOW_NUM
- device += LOW_NUM;
-#endif
- break;
+ if (in < argv + argc)
+ *out++ = *in++;
+ else
+ USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."),
+ *letter));
+ }
+ }
- case 'm':
-#ifdef MID_NUM
- device += MID_NUM;
-#else
- device += 8;
-#endif
- break;
+ /* Copy all remaining options. */
- case 'h':
-#ifdef HGH_NUM
- device += HGH_NUM;
-#else
- device += 16;
-#endif
- break;
+ while (in < argv + argc)
+ *out++ = *in++;
+ *out = 0;
- default:
- usage (TAREXIT_FAILURE);
- }
- sprintf (cursor, "%d", device);
+ /* Replace the old option list by the new one. */
-#endif /* not DENSITY_LETTER */
+ argc = new_argc;
+ argv = new_argv;
+ }
- if (archive_names == allocated_archive_names)
- {
- allocated_archive_names *= 2;
- archive_name_array = (const char **)
- xrealloc (archive_name_array,
- sizeof (const char *) * allocated_archive_names);
- }
- archive_name_array[archive_names++] = buf;
+ /* Parse all options and non-options as they appear. */
- /* FIXME: How comes this works for many archives when buf is
- not xstrdup'ed? */
- }
- break;
+ prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
-#else /* not DEVICE_PREFIX */
+ if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP,
+ &index, &args))
+ exit (1);
- USAGE_ERROR ((0, 0,
- _("Options `-[0-7][lmh]' not supported by *this* tar")));
-#endif /* not DEVICE_PREFIX */
- }
+ /* 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).
- /* Process trivial options. */
+ The old GNU tar semantics is retained when used with --create
+ option, otherwise UNIX98 semantics is assumed */
- if (show_version)
+ if (args.o_option)
{
- printf ("tar (GNU %s) %s\n", PACKAGE, VERSION);
- fputs (_("\
-\n\
-Copyright (C) 1988, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc.\n"),
- stdout);
- fputs (_("\
-This is free software; see the source for copying conditions. There is NO\n\
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"),
- stdout);
- fputs (_("\
-\n\
-Written by John Gilmore and Jay Fenlason.\n"),
- stdout);
- exit (TAREXIT_SUCCESS);
+ if (subcommand_option == CREATE_SUBCOMMAND)
+ {
+ /* GNU Tar <= 1.13 compatibility */
+ set_archive_format ("v7");
+ }
+ else
+ {
+ /* UNIX98 compatibility */
+ same_owner_option = -1;
+ }
}
- if (show_help)
- usage (TAREXIT_SUCCESS);
+ /* Handle operands after any "--" argument. */
+ for (; index < argc; index++)
+ {
+ name_add (argv[index]);
+ args.input_files++;
+ }
/* 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 (args.pax_option)
+ archive_format = POSIX_FORMAT;
+ else
+ archive_format = DEFAULT_ARCHIVE_FORMAT;
}
- if (archive_format == GNU_FORMAT && getenv ("POSIXLY_CORRECT"))
- archive_format = POSIX_FORMAT;
+ if (volume_label_option && subcommand_option == CREATE_SUBCOMMAND)
+ assert_format (FORMAT_MASK (OLDGNU_FORMAT)
+ | FORMAT_MASK (GNU_FORMAT));
- if ((volume_label_option != NULL
- || 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 (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 (!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")));
+ }
+
+ 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;
+ }
if (archive_names == 0)
{
archive_names = 1;
archive_name_array[0] = getenv ("TAPE");
- if (archive_name_array[0] == NULL)
+ if (! archive_name_array[0])
archive_name_array[0] = DEFAULT_ARCHIVE;
}
if (archive_names > 1 && !multi_volume_option)
USAGE_ERROR ((0, 0,
- _("Multiple archive files requires `-M' option")));
+ _("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")));
+
+ 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,
+ 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));
+ }
+
+ if (verify_option)
+ {
+ 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")));
+ }
+
+ 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")));
+ }
+
+ /* 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")));
/* If ready to unlink hierarchies, so we are for simpler files. */
if (recursive_unlink_option)
- unlink_first_option = 1;
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+
+ 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)
{
case CREATE_SUBCOMMAND:
- if (input_files == 0 && !files_from_option)
+ if (args.input_files == 0 && !files_from_option)
USAGE_ERROR ((0, 0,
_("Cowardly refusing to create an empty archive")));
break;
/* Prepare for generating backup names. */
- if (backup_suffix_string)
- simple_backup_suffix = xstrdup (backup_suffix_string);
+ if (args.backup_suffix_string)
+ simple_backup_suffix = xstrdup (args.backup_suffix_string);
if (backup_option)
- backup_type = get_version (version_control_string);
+ {
+ 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;
+ }
+
+ if (verbose_option && args.textual_date_option)
+ {
+ 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));
+ }
}
+
\f
/* Tar proper. */
-/*-----------------------.
-| Main routine for tar. |
-`-----------------------*/
-
+/* Main routine for tar. */
int
-main (int argc, char *const *argv)
+main (int argc, char **argv)
{
+ set_start_time ();
program_name = argv[0];
+
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
exit_status = TAREXIT_SUCCESS;
filename_terminator = '\n';
+ set_quoting_style (0, escape_quoting_style);
/* Pre-allocate a few structures. */
allocated_archive_names = 10;
- archive_name_array = (const char **)
+ archive_name_array =
xmalloc (sizeof (const char *) * allocated_archive_names);
archive_names = 0;
+ obstack_init (&argv_stk);
+
+#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);
+ name_init ();
/* Main command execution. */
break;
case CREATE_SUBCOMMAND:
- if (totals_option)
- init_total_written ();
-
create_archive ();
- name_close ();
-
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:
break;
}
+ if (check_links_option)
+ check_links ();
+
if (volno_file_option)
closeout_volume_number ();
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"));
- exit (exit_status);
+ if (ferror (stderr) || fclose (stderr) != 0)
+ exit_status = TAREXIT_FAILURE;
+ return exit_status;
+}
+
+void
+tar_stat_init (struct tar_stat_info *st)
+{
+ memset (st, 0, sizeof (*st));
+}
+
+void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+ 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));
}