X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Ftar.c;h=6fd117c636702f564511be0d3c7c4876b849efdb;hb=8da503cad6e883b30c05749149084d24319063b4;hp=0b17359e986725108aef4dc726c375db51055c92;hpb=d989864712d07ebea6fea68d3c8ea57413b21493;p=chaz%2Ftar diff --git a/src/tar.c b/src/tar.c index 0b17359..6fd117c 100644 --- a/src/tar.c +++ b/src/tar.c @@ -1,13 +1,13 @@ /* A tar (tape archiver) program. Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000, - 2001, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. + 2001, 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc. Written by John Gilmore, starting 1985-08-25. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any later + Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but @@ -22,10 +22,10 @@ #include #include -#include #include #include #include +#include #include #if ! defined SIGCHLD && defined SIGCLD @@ -51,6 +51,7 @@ #include #include #include +#include /* Local declarations. */ @@ -73,7 +74,7 @@ static const char *stdin_used_by; /* Doesn't return if stdin already requested. */ -void +static void request_stdin (const char *option) { if (stdin_used_by) @@ -213,21 +214,28 @@ subcommand_string (enum subcommand c) case UPDATE_SUBCOMMAND: return "-u"; - default: - abort (); + case TEST_LABEL_SUBCOMMAND: + return "--test-label"; } + abort (); } -void -tar_list_quoting_styles (argp_fmtstream_t fs, char *prefix) +static void +tar_list_quoting_styles (struct obstack *stk, char const *prefix) { int i; + size_t prefixlen = strlen (prefix); for (i = 0; quoting_style_args[i]; i++) - argp_fmtstream_printf (fs, "%s%s\n", prefix, quoting_style_args[i]); + { + obstack_grow (stk, prefix, prefixlen); + obstack_grow (stk, quoting_style_args[i], + strlen (quoting_style_args[i])); + obstack_1grow (stk, '\n'); + } } -void +static void tar_set_quoting_style (char *arg) { int i; @@ -250,9 +258,13 @@ enum ANCHORED_OPTION = CHAR_MAX + 1, ATIME_PRESERVE_OPTION, BACKUP_OPTION, + CHECK_DEVICE_OPTION, CHECKPOINT_OPTION, + CHECKPOINT_ACTION_OPTION, DELAY_DIRECTORY_RESTORE_OPTION, + HARD_DEREFERENCE_OPTION, DELETE_OPTION, + EXCLUDE_BACKUPS_OPTION, EXCLUDE_CACHES_OPTION, EXCLUDE_CACHES_UNDER_OPTION, EXCLUDE_CACHES_ALL_OPTION, @@ -260,26 +272,35 @@ enum EXCLUDE_TAG_OPTION, EXCLUDE_TAG_UNDER_OPTION, EXCLUDE_TAG_ALL_OPTION, + EXCLUDE_VCS_OPTION, FORCE_LOCAL_OPTION, + FULL_TIME_OPTION, GROUP_OPTION, - HANG_OPTION, IGNORE_CASE_OPTION, IGNORE_COMMAND_ERROR_OPTION, IGNORE_FAILED_READ_OPTION, INDEX_FILE_OPTION, KEEP_NEWER_FILES_OPTION, + LEVEL_OPTION, + LZIP_OPTION, + LZMA_OPTION, + LZOP_OPTION, MODE_OPTION, MTIME_OPTION, NEWER_MTIME_OPTION, NO_ANCHORED_OPTION, + NO_AUTO_COMPRESS_OPTION, + NO_CHECK_DEVICE_OPTION, NO_DELAY_DIRECTORY_RESTORE_OPTION, NO_IGNORE_CASE_OPTION, NO_IGNORE_COMMAND_ERROR_OPTION, + NO_NULL_OPTION, NO_OVERWRITE_DIR_OPTION, NO_QUOTE_CHARS_OPTION, NO_RECURSION_OPTION, NO_SAME_OWNER_OPTION, NO_SAME_PERMISSIONS_OPTION, + NO_SEEK_OPTION, NO_UNQUOTE_OPTION, NO_WILDCARDS_MATCH_SLASH_OPTION, NO_WILDCARDS_OPTION, @@ -315,11 +336,9 @@ enum TO_COMMAND_OPTION, TRANSFORM_OPTION, UNQUOTE_OPTION, - USAGE_OPTION, - USE_COMPRESS_PROGRAM_OPTION, UTC_OPTION, - VERSION_OPTION, VOLNO_FILE_OPTION, + WARNING_OPTION, WILDCARDS_MATCH_SLASH_OPTION, WILDCARDS_OPTION }; @@ -345,7 +364,7 @@ The version control may be set with --backup or VERSION_CONTROL, values are:\n\n /* NOTE: - Available option letters are DEIJQY and aeqy. Consider the following + Available option letters are DEQY and eqy. Consider the following assignments: [For Solaris tar compatibility =/= Is it important at all?] @@ -353,10 +372,14 @@ The version control may be set with --backup or VERSION_CONTROL, values are:\n\n 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 */ + Y per-block gzip compression. + + Additionally, the 'n' letter is assigned for option --seek, which + is probably not needed and should be marked as deprecated, so that + -n may become available in the future. +*/ static struct argp_option options[] = { #define GRID 10 @@ -398,6 +421,8 @@ static struct argp_option options[] = { N_("handle old GNU-format incremental backup"), GRID+1 }, {"listed-incremental", 'g', N_("FILE"), 0, N_("handle new GNU-format incremental backup"), GRID+1 }, + {"level", LEVEL_OPTION, N_("NUMBER"), 0, + N_("dump level for created listed-incremental archive"), GRID+1 }, {"ignore-failed-read", IGNORE_FAILED_READ_OPTION, 0, 0, N_("do not exit with nonzero on unreadable files"), GRID+1 }, {"occurrence", OCCURRENCE_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL, @@ -408,6 +433,14 @@ static struct argp_option options[] = { " NUMBER defaults to 1"), GRID+1 }, {"seek", 'n', NULL, 0, N_("archive is seekable"), GRID+1 }, + {"no-seek", NO_SEEK_OPTION, NULL, 0, + N_("archive is not seekable"), GRID+1 }, + {"no-check-device", NO_CHECK_DEVICE_OPTION, NULL, 0, + N_("do not check device numbers when creating incremental archives"), + GRID+1 }, + {"check-device", CHECK_DEVICE_OPTION, NULL, 0, + N_("check device numbers when creating incremental archives (default)"), + GRID+1 }, #undef GRID #define GRID 30 @@ -469,9 +502,9 @@ static struct argp_option options[] = { {"touch", 'm', 0, 0, N_("don't extract file modified time"), GRID+1 }, {"same-owner", SAME_OWNER_OPTION, 0, 0, - N_("try extracting files with the same ownership"), GRID+1 }, + N_("try extracting files with the same ownership as exists in the archive (default for superuser)"), GRID+1 }, {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0, - N_("extract files as yourself"), GRID+1 }, + N_("extract files as yourself (default for ordinary users)"), GRID+1 }, {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0, N_("always use numbers for user/group names"), GRID+1 }, {"preserve-permissions", 'p', 0, 0, @@ -574,20 +607,32 @@ static struct argp_option options[] = { N_("control pax keywords"), GRID+8 }, {"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"), GRID+8 }, - {"bzip2", 'j', 0, 0, - N_("filter the archive through bzip2"), GRID+8 }, - {"gzip", 'z', 0, 0, - N_("filter the archive through gzip"), GRID+8 }, - {"gunzip", 0, 0, OPTION_ALIAS, NULL, GRID+8 }, - {"ungzip", 0, 0, OPTION_ALIAS, NULL, GRID+8 }, - {"compress", 'Z', 0, 0, - N_("filter the archive through compress"), GRID+8 }, - {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+8 }, - {"use-compress-program", USE_COMPRESS_PROGRAM_OPTION, N_("PROG"), 0, - N_("filter through PROG (must accept -d)"), GRID+8 }, #undef GRID #define GRID 90 + {NULL, 0, NULL, 0, + N_("Compression options:"), GRID }, + {"auto-compress", 'a', 0, 0, + N_("use archive suffix to determine the compression program"), GRID+1 }, + {"no-auto-compress", NO_AUTO_COMPRESS_OPTION, 0, 0, + N_("do not use archive suffix to determine the compression program"), + GRID+1 }, + {"use-compress-program", 'I', N_("PROG"), 0, + N_("filter through PROG (must accept -d)"), GRID+1 }, + /* Note: docstrings for the options below are generated by tar_help_filter */ + {"bzip2", 'j', 0, 0, NULL, GRID+1 }, + {"gzip", 'z', 0, 0, NULL, GRID+1 }, + {"gunzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, + {"ungzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, + {"compress", 'Z', 0, 0, NULL, GRID+1 }, + {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, + {"lzip", LZIP_OPTION, 0, 0, NULL, GRID+1 }, + {"lzma", LZMA_OPTION, 0, 0, NULL, GRID+1 }, + {"lzop", LZOP_OPTION, 0, 0, NULL, GRID+1 }, + {"xz", 'J', 0, 0, NULL, GRID+1 }, +#undef GRID + +#define GRID 100 {NULL, 0, NULL, 0, N_("Local file selection:"), GRID }, @@ -599,6 +644,8 @@ static struct argp_option options[] = { N_("get names to extract or create from FILE"), GRID+1 }, {"null", NULL_OPTION, 0, 0, N_("-T reads null-terminated names, disable -C"), GRID+1 }, + {"no-null", NO_NULL_OPTION, 0, 0, + N_("disable the effect of the previous --null option"), GRID+1 }, {"unquote", UNQUOTE_OPTION, 0, 0, N_("unquote filenames read with -T (default)"), GRID+1 }, {"no-unquote", NO_UNQUOTE_OPTION, 0, 0, @@ -617,11 +664,15 @@ static struct argp_option options[] = { N_("exclude directories containing CACHEDIR.TAG"), GRID+1 }, {"exclude-tag", EXCLUDE_TAG_OPTION, N_("FILE"), 0, N_("exclude contents of directories containing FILE, except" - " for FILE itself"), GRID+1 }, + " for FILE itself"), GRID+1 }, {"exclude-tag-under", EXCLUDE_TAG_UNDER_OPTION, N_("FILE"), 0, N_("exclude everything under directories containing FILE"), GRID+1 }, {"exclude-tag-all", EXCLUDE_TAG_ALL_OPTION, N_("FILE"), 0, N_("exclude directories containing FILE"), GRID+1 }, + {"exclude-vcs", EXCLUDE_VCS_OPTION, NULL, 0, + N_("exclude version control system directories"), GRID+1 }, + {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0, + N_("exclude backup and lock files"), GRID+1 }, {"no-recursion", NO_RECURSION_OPTION, 0, 0, N_("avoid descending automatically in directories"), GRID+1 }, {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0, @@ -632,6 +683,8 @@ static struct argp_option options[] = { N_("don't strip leading `/'s from file names"), GRID+1 }, {"dereference", 'h', 0, 0, N_("follow symlinks; archive and dump the files they point to"), GRID+1 }, + {"hard-dereference", HARD_DEREFERENCE_OPTION, 0, 0, + N_("follow hard links; archive and dump the files they refer to"), GRID+1 }, {"starting-file", 'K', N_("MEMBER-NAME"), 0, N_("begin at member MEMBER-NAME in the archive"), GRID+1 }, {"newer", 'N', N_("DATE-OR-FILE"), 0, @@ -645,7 +698,7 @@ static struct argp_option options[] = { N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), GRID+1 }, #undef GRID -#define GRID 92 +#define GRID 110 {NULL, 0, NULL, 0, N_("File name transformations:"), GRID }, {"strip-components", STRIP_COMPONENTS_OPTION, N_("NUMBER"), 0, @@ -653,9 +706,10 @@ static struct argp_option options[] = { GRID+1 }, {"transform", TRANSFORM_OPTION, N_("EXPRESSION"), 0, N_("use sed replace EXPRESSION to transform file names"), GRID+1 }, + {"xform", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, #undef GRID -#define GRID 95 +#define GRID 120 {NULL, 0, NULL, 0, N_("File name matching options (affect both exclude and include patterns):"), GRID }, @@ -677,15 +731,20 @@ static struct argp_option options[] = { N_("wildcards match `/' (default for exclusion)"), GRID+1 }, #undef GRID -#define GRID 100 +#define GRID 130 {NULL, 0, NULL, 0, N_("Informative output:"), GRID }, {"verbose", 'v', 0, 0, N_("verbosely list files processed"), GRID+1 }, - {"checkpoint", CHECKPOINT_OPTION, N_("[.]NUMBER"), OPTION_ARG_OPTIONAL, + {"warning", WARNING_OPTION, N_("KEYWORD"), 0, + N_("warning control"), GRID+1 }, + {"checkpoint", CHECKPOINT_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL, N_("display progress messages every NUMBERth record (default 10)"), GRID+1 }, + {"checkpoint-action", CHECKPOINT_ACTION_OPTION, N_("ACTION"), 0, + N_("execute ACTION on each checkpoint"), + GRID+1 }, {"check-links", 'l', 0, 0, N_("print a message if not all links are dumped"), GRID+1 }, {"totals", TOTALS_OPTION, N_("SIGNAL"), OPTION_ARG_OPTIONAL, @@ -694,7 +753,9 @@ static struct argp_option options[] = { "Allowed signals are: SIGHUP, SIGQUIT, SIGINT, SIGUSR1 and SIGUSR2; " "the names without SIG prefix are also accepted"), GRID+1 }, {"utc", UTC_OPTION, 0, 0, - N_("print file modification dates in UTC"), GRID+1 }, + N_("print file modification times in UTC"), GRID+1 }, + {"full-time", FULL_TIME_OPTION, 0, 0, + N_("print file time to its full resolution"), GRID+1 }, {"index-file", INDEX_FILE_OPTION, N_("FILE"), 0, N_("send verbose output to FILE"), GRID+1 }, {"block-number", 'R', 0, 0, @@ -718,7 +779,7 @@ static struct argp_option options[] = { N_("disable quoting for characters from STRING"), GRID+1 }, #undef GRID -#define GRID 110 +#define GRID 140 {NULL, 0, NULL, 0, N_("Compatibility options:"), GRID }, @@ -726,20 +787,12 @@ static struct argp_option options[] = { N_("when creating, same as --old-archive; when extracting, same as --no-same-owner"), GRID+1 }, #undef GRID -#define GRID 120 +#define GRID 150 {NULL, 0, NULL, 0, N_("Other options:"), GRID }, {"restrict", RESTRICT_OPTION, 0, 0, N_("disable use of some potentially harmful options"), -1 }, - - {"help", '?', 0, 0, N_("give this help list"), -1}, - {"usage", USAGE_OPTION, 0, 0, N_("give a short usage message"), -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}, #undef GRID {0, 0, 0, 0, 0, 0} @@ -783,8 +836,11 @@ struct tar_args /* Variables used during option parsing */ char const *backup_suffix_string; /* --suffix option argument */ char const *version_control_string; /* --backup option argument */ bool input_files; /* True if some input files where given */ + int compress_autodetect; /* True if compression autodetection should + be attempted when creating archives */ }; + #define MAKE_EXCL_OPTIONS(args) \ ((((args)->wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0) \ | (args)->matching_flags \ @@ -796,46 +852,82 @@ struct tar_args /* Variables used during option parsing */ | (args)->matching_flags \ | recursion_option) -#ifdef REMOTE_SHELL -# define DECL_SHOW_DEFAULT_SETTINGS(stream, printer) \ -{ \ - printer (stream, \ - "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s", \ - archive_format_string (DEFAULT_ARCHIVE_FORMAT), \ - DEFAULT_ARCHIVE, DEFAULT_BLOCKING, \ - quoting_style_args[DEFAULT_QUOTING_STYLE], \ - DEFAULT_RMT_COMMAND); \ - printer (stream, " --rsh-command=%s", REMOTE_SHELL); \ - printer (stream, "\n"); \ -} -#else -# define DECL_SHOW_DEFAULT_SETTINGS(stream, printer) \ -{ \ - printer (stream, \ - "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s", \ - archive_format_string (DEFAULT_ARCHIVE_FORMAT), \ - DEFAULT_ARCHIVE, DEFAULT_BLOCKING, \ - quoting_style_args[DEFAULT_QUOTING_STYLE], \ - DEFAULT_RMT_COMMAND); \ - printer (stream, "\n"); \ -} -#endif +static char const * const vcs_file_table[] = { + /* CVS: */ + "CVS", + ".cvsignore", + /* RCS: */ + "RCS", + /* SCCS: */ + "SCCS", + /* SVN: */ + ".svn", + /* git: */ + ".git", + ".gitignore", + /* Arch: */ + ".arch-ids", + "{arch}", + "=RELEASE-ID", + "=meta-update", + "=update", + /* Bazaar */ + ".bzr", + ".bzrignore", + ".bzrtags", + /* Mercurial */ + ".hg", + ".hgignore", + ".hgtags", + /* darcs */ + "_darcs", + NULL +}; -static void -show_default_settings (FILE *fp) - DECL_SHOW_DEFAULT_SETTINGS(fp, fprintf) +static char const * const backup_file_table[] = { + ".#*", + "*~", + "#*#", + NULL +}; static void -show_default_settings_fs (argp_fmtstream_t fs) - DECL_SHOW_DEFAULT_SETTINGS(fs, argp_fmtstream_printf) +add_exclude_array (char const * const * fv) +{ + int i; + + for (i = 0; fv[i]; i++) + add_exclude (excluded, fv[i], 0); +} + + +static char * +format_default_settings (void) +{ + return xasprintf ( + "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s" +#ifdef REMOTE_SHELL + " --rsh-command=%s" +#endif + , + archive_format_string (DEFAULT_ARCHIVE_FORMAT), + DEFAULT_ARCHIVE, DEFAULT_BLOCKING, + quoting_style_args[DEFAULT_QUOTING_STYLE], + DEFAULT_RMT_COMMAND +#ifdef REMOTE_SHELL + , REMOTE_SHELL +#endif + ); +} + 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"))); + _("You may not specify more than one `-Acdtrux' or `--test-label' option"))); subcommand_option = subcommand; } @@ -874,14 +966,14 @@ stat_on_signal (int signo) #endif } -void +static void set_stat_signal (const char *name) { static struct sigtab { - char *name; + char const *name; int signo; - } sigtab[] = { + } const sigtab[] = { { "SIGUSR1", SIGUSR1 }, { "USR1", SIGUSR1 }, { "SIGUSR2", SIGUSR2 }, @@ -893,7 +985,7 @@ set_stat_signal (const char *name) { "SIGQUIT", SIGQUIT }, { "QUIT", SIGQUIT } }; - struct sigtab *p; + struct sigtab const *p; for (p = sigtab; p < sigtab + sizeof (sigtab) / sizeof (sigtab[0]); p++) if (strcmp (p->name, name) == 0) @@ -908,12 +1000,12 @@ set_stat_signal (const char *name) struct textual_date { struct textual_date *next; - struct timespec *ts; + struct timespec ts; const char *option; - const char *date; + char *date; }; -static void +static int get_date_or_file (struct tar_args *args, const char *option, const char *str, struct timespec *ts) { @@ -936,17 +1028,19 @@ get_date_or_file (struct tar_args *args, const char *option, WARN ((0, 0, _("Substituting %s for unknown date format %s"), tartime (*ts, false), quote (str))); ts->tv_nsec = 0; + return 1; } else { struct textual_date *p = xmalloc (sizeof (*p)); - p->ts = ts; + p->ts = *ts; p->option = option; - p->date = str; + p->date = xstrdup (str); p->next = args->textual_date; args->textual_date = p; } } + return 0; } static void @@ -956,17 +1050,23 @@ report_textual_dates (struct tar_args *args) for (p = args->textual_date; p; ) { struct textual_date *next = p->next; - char const *treated_as = tartime (*p->ts, true); - if (strcmp (p->date, treated_as) != 0) - WARN ((0, 0, _("Option %s: Treating date `%s' as %s"), - p->option, p->date, treated_as)); + if (verbose_option) + { + char const *treated_as = tartime (p->ts, true); + if (strcmp (p->date, treated_as) != 0) + WARN ((0, 0, _("Option %s: Treating date `%s' as %s"), + p->option, p->date, treated_as)); + } + free (p->date); free (p); p = next; } } -static volatile int _argp_hang; + +/* Either NL or NUL, as decided by the --null option. */ +static char filename_terminator; enum read_file_list_state /* Result of reading file name from the list file */ { @@ -976,16 +1076,16 @@ enum read_file_list_state /* Result of reading file name from the list file */ file_list_skip /* Empty (zero-length) entry encountered, skip it */ }; -/* Read from FP a sequence of characters up to FILENAME_TERMINATOR and put them +/* Read from FP a sequence of characters up to TERM and put them into STK. */ static enum read_file_list_state -read_name_from_file (FILE *fp, struct obstack *stk) +read_name_from_file (FILE *fp, struct obstack *stk, int term) { int c; size_t counter = 0; - for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp)) + for (c = getc (fp); c != EOF && c != term; c = getc (fp)) { if (c == 0) { @@ -1066,6 +1166,7 @@ update_argv (const char *filename, struct argp_state *state) size_t new_argc; bool is_stdin = false; enum read_file_list_state read_state; + int term = filename_terminator; if (!strcmp (filename, "-")) { @@ -1080,7 +1181,8 @@ update_argv (const char *filename, struct argp_state *state) open_fatal (filename); } - while ((read_state = read_name_from_file (fp, &argv_stk)) != file_list_end) + while ((read_state = read_name_from_file (fp, &argv_stk, term)) + != file_list_end) { switch (read_state) { @@ -1095,8 +1197,9 @@ update_argv (const char *filename, struct argp_state *state) { size_t size; - WARN ((0, 0, N_("%s: file name read contains nul character"), - quotearg_colon (filename))); + WARNOPT (WARN_FILENAME_WITH_NULS, + (0, 0, N_("%s: file name read contains nul character"), + quotearg_colon (filename))); /* Prepare new stack contents */ size = obstack_object_size (&argv_stk); @@ -1109,7 +1212,7 @@ update_argv (const char *filename, struct argp_state *state) obstack_1grow (&argv_stk, 0); count = 1; /* Read rest of files using new filename terminator */ - filename_terminator = 0; + term = 0; break; } @@ -1126,7 +1229,7 @@ update_argv (const char *filename, struct argp_state *state) start = obstack_finish (&argv_stk); - if (filename_terminator == 0) + if (term == 0) for (p = start; *p; p += strlen (p) + 1) if (p[0] == '-') count++; @@ -1142,35 +1245,127 @@ update_argv (const char *filename, struct argp_state *state) for (i = state->next, p = start; *p; p += strlen (p) + 1, i++) { - if (filename_terminator == 0 && p[0] == '-') + if (term == 0 && p[0] == '-') state->argv[i++] = "--add-file"; state->argv[i] = p; } } -static void -tar_help (struct argp_state *state) +static char * +tar_help_filter (int key, const char *text, void *input) { - argp_fmtstream_t fs; - state->flags |= ARGP_NO_EXIT; - argp_state_help (state, state->out_stream, - ARGP_HELP_STD_HELP & ~ARGP_HELP_BUG_ADDR); - /* FIXME: use struct uparams.rmargin (from argp-help.c) instead of 79 */ - fs = argp_make_fmtstream (state->out_stream, 0, 79, 0); - - argp_fmtstream_printf (fs, "\n%s\n\n", - _("Valid arguments for --quoting-style options are:")); - tar_list_quoting_styles (fs, " "); - - argp_fmtstream_puts (fs, _("\n*This* tar defaults to:\n")); - show_default_settings_fs (fs); - argp_fmtstream_putc (fs, '\n'); - argp_fmtstream_printf (fs, _("Report bugs to %s.\n"), - argp_program_bug_address); - argp_fmtstream_free (fs); + struct obstack stk; + char *s; + + switch (key) + { + default: + s = (char*) text; + break; + + case 'j': + s = xasprintf (_("filter the archive through %s"), BZIP2_PROGRAM); + break; + + case 'z': + s = xasprintf (_("filter the archive through %s"), GZIP_PROGRAM); + break; + + case 'Z': + s = xasprintf (_("filter the archive through %s"), COMPRESS_PROGRAM); + break; + + case LZIP_OPTION: + s = xasprintf (_("filter the archive through %s"), LZIP_PROGRAM); + break; + + case LZMA_OPTION: + s = xasprintf (_("filter the archive through %s"), LZMA_PROGRAM); + break; + + case 'J': + s = xasprintf (_("filter the archive through %s"), XZ_PROGRAM); + break; + + case ARGP_KEY_HELP_EXTRA: + { + const char *tstr; + + obstack_init (&stk); + tstr = _("Valid arguments for the --quoting-style option are:"); + obstack_grow (&stk, tstr, strlen (tstr)); + obstack_grow (&stk, "\n\n", 2); + tar_list_quoting_styles (&stk, " "); + tstr = _("\n*This* tar defaults to:\n"); + obstack_grow (&stk, tstr, strlen (tstr)); + s = format_default_settings (); + obstack_grow (&stk, s, strlen (s)); + obstack_1grow (&stk, '\n'); + obstack_1grow (&stk, 0); + s = xstrdup (obstack_finish (&stk)); + obstack_free (&stk, NULL); + } + } + return s; } +static char * +expand_pax_option (struct tar_args *targs, const char *arg) +{ + struct obstack stk; + char *res; + + obstack_init (&stk); + while (*arg) + { + size_t seglen = strcspn (arg, ","); + char *p = memchr (arg, '=', seglen); + if (p) + { + size_t len = p - arg + 1; + obstack_grow (&stk, arg, len); + len = seglen - len; + for (++p; *p && isspace ((unsigned char) *p); p++) + len--; + if (*p == '{' && p[len-1] == '}') + { + struct timespec ts; + char *tmp = xmalloc (len); + memcpy (tmp, p + 1, len-2); + tmp[len-2] = 0; + if (get_date_or_file (targs, "--pax-option", tmp, &ts) == 0) + { + char buf[UINTMAX_STRSIZE_BOUND], *s; + s = umaxtostr (ts.tv_sec, buf); + obstack_grow (&stk, s, strlen (s)); + } + else + obstack_grow (&stk, p, len); + free (tmp); + } + else + obstack_grow (&stk, p, len); + } + else + obstack_grow (&stk, arg, seglen); + + arg += seglen; + if (*arg) + { + obstack_1grow (&stk, *arg); + arg++; + } + } + obstack_1grow (&stk, 0); + res = xstrdup (obstack_finish (&stk)); + obstack_free (&stk, NULL); + return res; +} + + +#define TAR_SIZE_SUFFIXES "bBcGgkKMmPTtw" + static error_t parse_opt (int key, char *arg, struct argp_state *state) { @@ -1188,6 +1383,14 @@ parse_opt (int key, char *arg, struct argp_state *state) set_subcommand_option (CAT_SUBCOMMAND); break; + case 'a': + args->compress_autodetect = true; + break; + + case NO_AUTO_COMPRESS_OPTION: + args->compress_autodetect = false; + break; + case 'b': { uintmax_t u; @@ -1240,6 +1443,10 @@ parse_opt (int key, char *arg, struct argp_state *state) multi_volume_option = true; break; + case FULL_TIME_OPTION: + full_time_option = true; + break; + case 'g': listed_incremental_option = arg; after_date_option = true; @@ -1258,6 +1465,10 @@ parse_opt (int key, char *arg, struct argp_state *state) dereference_option = true; break; + case HARD_DEREFERENCE_OPTION: + hard_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 @@ -1266,14 +1477,12 @@ parse_opt (int key, char *arg, struct argp_state *state) ignore_zeros_option = true; break; - case 'I': - USAGE_ERROR ((0, 0, - _("Warning: the -I option is not supported;" - " perhaps you meant -j or -T?"))); + case 'j': + set_use_compress_program_option (BZIP2_PROGRAM); break; - case 'j': - set_use_compress_program_option ("bzip2"); + case 'J': + set_use_compress_program_option (XZ_PROGRAM); break; case 'k': @@ -1283,7 +1492,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case 'K': starting_file_option = true; - addname (arg, 0); + addname (arg, 0, true, NULL); break; case ONE_FILE_SYSTEM_OPTION: @@ -1299,14 +1508,40 @@ parse_opt (int key, char *arg, struct argp_state *state) case 'L': { uintmax_t u; - if (xstrtoumax (arg, 0, 10, &u, "") != LONGINT_OK) + char *p; + + if (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES) != LONGINT_OK) USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg), _("Invalid tape length"))); - tape_length_option = 1024 * (tarlong) u; + if (p > arg && !strchr (TAR_SIZE_SUFFIXES, p[-1])) + tape_length_option = 1024 * (tarlong) u; + else + tape_length_option = (tarlong) u; multi_volume_option = true; } break; + case LEVEL_OPTION: + { + char *p; + incremental_level = strtoul (arg, &p, 10); + if (*p) + USAGE_ERROR ((0, 0, _("Invalid incremental level value"))); + } + break; + + case LZIP_OPTION: + set_use_compress_program_option (LZIP_PROGRAM); + break; + + case LZMA_OPTION: + set_use_compress_program_option (LZMA_PROGRAM); + break; + + case LZOP_OPTION: + set_use_compress_program_option (LZOP_PROGRAM); + break; + case 'm': touch_option = true; break; @@ -1324,7 +1559,11 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case 'n': - seekable_archive = true; + seek_option = 1; + break; + + case NO_SEEK_OPTION: + seek_option = 0; break; case 'N': @@ -1336,7 +1575,7 @@ parse_opt (int key, char *arg, struct argp_state *state) USAGE_ERROR ((0, 0, _("More than one threshold date"))); get_date_or_file (args, key == NEWER_MTIME_OPTION ? "--newer-mtime" - : "--after-date", arg, &newer_mtime_option); + : "--after-date", arg, &newer_mtime_option); break; case 'o': @@ -1402,8 +1641,7 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case TEST_LABEL_OPTION: - set_subcommand_option (LIST_SUBCOMMAND); - test_label_option = true; + set_subcommand_option (TEST_LABEL_SUBCOMMAND); break; case 'T': @@ -1428,6 +1666,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case 'v': verbose_option++; + warning_option |= WARN_VERBOSE_WARNINGS; break; case 'V': @@ -1457,11 +1696,11 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case 'z': - set_use_compress_program_option ("gzip"); + set_use_compress_program_option (GZIP_PROGRAM); break; case 'Z': - set_use_compress_program_option ("compress"); + set_use_compress_program_option (COMPRESS_PROGRAM); break; case ANCHORED_OPTION: @@ -1480,6 +1719,14 @@ parse_opt (int key, char *arg, struct argp_state *state) " on this platform"))); break; + case CHECK_DEVICE_OPTION: + check_device_option = true; + break; + + case NO_CHECK_DEVICE_OPTION: + check_device_option = false; + break; + case CHECKPOINT_OPTION: if (arg) { @@ -1487,7 +1734,7 @@ parse_opt (int key, char *arg, struct argp_state *state) if (*arg == '.') { - checkpoint_style = checkpoint_dot; + checkpoint_compile_action ("."); arg++; } checkpoint_option = strtoul (arg, &p, 0); @@ -1496,7 +1743,11 @@ parse_opt (int key, char *arg, struct argp_state *state) _("--checkpoint value is not an integer"))); } else - checkpoint_option = 10; + checkpoint_option = DEFAULT_CHECKPOINT; + break; + + case CHECKPOINT_ACTION_OPTION: + checkpoint_compile_action (arg); break; case BACKUP_OPTION: @@ -1517,6 +1768,10 @@ parse_opt (int key, char *arg, struct argp_state *state) set_subcommand_option (DELETE_SUBCOMMAND); break; + case EXCLUDE_BACKUPS_OPTION: + add_exclude_array (backup_file_table); + break; + case EXCLUDE_OPTION: add_exclude (excluded, arg, MAKE_EXCL_OPTIONS (args)); break; @@ -1539,15 +1794,19 @@ parse_opt (int key, char *arg, struct argp_state *state) case EXCLUDE_TAG_OPTION: add_exclusion_tag (arg, exclusion_tag_contents, NULL); break; - + case EXCLUDE_TAG_UNDER_OPTION: add_exclusion_tag (arg, exclusion_tag_under, NULL); break; - + case EXCLUDE_TAG_ALL_OPTION: add_exclusion_tag (arg, exclusion_tag_all, NULL); break; - + + case EXCLUDE_VCS_OPTION: + add_exclude_array (vcs_file_table); + break; + case FORCE_LOCAL_OPTION: force_local_option = true; break; @@ -1586,7 +1845,7 @@ parse_opt (int key, char *arg, struct argp_state *state) group_option = g; else FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg), - _("%s: Invalid group"))); + _("Invalid group"))); } break; @@ -1632,6 +1891,10 @@ parse_opt (int key, char *arg, struct argp_state *state) filename_terminator = '\0'; break; + case NO_NULL_OPTION: + filename_terminator = '\n'; + break; + case NUMERIC_OWNER_OPTION: numeric_owner_option = true; break; @@ -1682,8 +1945,12 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case PAX_OPTION: - args->pax_option = true; - xheader_set_option (arg); + { + char *tmp = expand_pax_option (args, arg); + args->pax_option = true; + xheader_set_option (tmp); + free (tmp); + } break; case POSIX_OPTION: @@ -1694,12 +1961,15 @@ parse_opt (int key, char *arg, struct argp_state *state) /* FIXME: What it is good for? */ same_permissions_option = true; same_order_option = true; + WARN ((0, 0, _("The --preserve option is deprecated, " + "use --preserve-permissions --preserve-order instead"))); break; case RECORD_SIZE_OPTION: { uintmax_t u; - if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK + + if (! (xstrtoumax (arg, NULL, 10, &u, TAR_SIZE_SUFFIXES) == LONGINT_OK && u == (size_t) u)) USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg), _("Invalid record size"))); @@ -1732,9 +2002,13 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case SHOW_DEFAULTS_OPTION: - show_default_settings (stdout); - close_stdout (); - exit (0); + { + char *s = format_default_settings (); + printf ("%s\n", s); + close_stdout (); + free (s); + exit (0); + } case STRIP_COMPONENTS_OPTION: { @@ -1777,7 +2051,7 @@ parse_opt (int key, char *arg, struct argp_state *state) set_transform_expr (arg); break; - case USE_COMPRESS_PROGRAM_OPTION: + case 'I': set_use_compress_program_option (arg); break; @@ -1821,6 +2095,10 @@ parse_opt (int key, char *arg, struct argp_state *state) unquote_option = false; break; + case WARNING_OPTION: + set_warning_option (arg); + break; + case '0': case '1': case '2': @@ -1885,28 +2163,6 @@ parse_opt (int key, char *arg, struct argp_state *state) #endif /* not DEVICE_PREFIX */ - case '?': - tar_help (state); - close_stdout (); - exit (0); - - case USAGE_OPTION: - argp_state_help (state, state->out_stream, ARGP_HELP_USAGE); - close_stdout (); - exit (0); - - case VERSION_OPTION: - version_etc (state->out_stream, "tar", PACKAGE_NAME, VERSION, - "John Gilmore", "Jay Fenlason", (char *) NULL); - close_stdout (); - exit (0); - - case HANG_OPTION: - _argp_hang = atoi (arg ? arg : "3600"); - while (_argp_hang-- > 0) - sleep (1); - break; - default: return ARGP_ERR_UNKNOWN; } @@ -1919,7 +2175,7 @@ static struct argp argp = { N_("[FILE]..."), doc, NULL, - NULL, + tar_help_filter, NULL }; @@ -1934,25 +2190,33 @@ usage (int status) /* Parse the options for tar. */ static struct argp_option * -find_argp_option (struct argp_option *options, int letter) +find_argp_option (struct argp_option *o, 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; + !(o->name == NULL + && o->key == 0 + && o->arg == 0 + && o->flags == 0 + && o->doc == NULL); o++) + if (o->key == letter) + return o; return NULL; } +static const char *tar_authors[] = { + "John Gilmore", + "Jay Fenlason", + NULL +}; + static void decode_options (int argc, char **argv) { - int index; + int idx; struct tar_args args; + argp_version_setup ("tar", tar_authors); + /* Set some default option values. */ args.textual_date = NULL; args.wildcards = default_wildcards; @@ -1963,6 +2227,7 @@ decode_options (int argc, char **argv) args.backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); args.version_control_string = 0; args.input_files = false; + args.compress_autodetect = false; subcommand_option = UNKNOWN_SUBCOMMAND; archive_format = DEFAULT_FORMAT; @@ -1979,6 +2244,12 @@ decode_options (int argc, char **argv) owner_option = -1; group_option = -1; + check_device_option = true; + + incremental_level = -1; + + seek_option = -1; + /* Convert old-style tar call by exploding option element and rearranging options accordingly. */ @@ -2040,8 +2311,7 @@ decode_options (int argc, char **argv) prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv); - if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP, - &index, &args)) + if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, &args)) exit (TAREXIT_FAILURE); @@ -2069,9 +2339,9 @@ decode_options (int argc, char **argv) } /* Handle operands after any "--" argument. */ - for (; index < argc; index++) + for (; idx < argc; idx++) { - name_add_name (argv[index], MAKE_INCL_OPTIONS (&args)); + name_add_name (argv[idx], MAKE_INCL_OPTIONS (&args)); args.input_files = true; } @@ -2110,18 +2380,6 @@ decode_options (int argc, char **argv) _("--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) { /* If no archive file name given, try TAPE from the environment, or @@ -2143,6 +2401,9 @@ decode_options (int argc, char **argv) && NEWER_OPTION_INITIALIZED (newer_mtime_option)) USAGE_ERROR ((0, 0, _("Cannot combine --listed-incremental with --newer"))); + if (incremental_level != -1 && !listed_incremental_option) + WARN ((0, 0, + _("--level is meaningless without --listed-incremental"))); if (volume_label_option) { @@ -2204,8 +2465,20 @@ decode_options (int argc, char **argv) if (recursive_unlink_option) old_files_option = UNLINK_FIRST_OLD_FILES; - - if (test_label_option) + /* Flags for accessing files to be read from or copied into. POSIX says + O_NONBLOCK has unspecified effect on most types of files, but in + practice it never harms and sometimes helps. */ + { + int base_open_flags = + (O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK + | (dereference_option ? 0 : O_NOFOLLOW) + | (atime_preserve_option == system_atime_preserve ? O_NOATIME : 0)); + open_read_flags = O_RDONLY | base_open_flags; + open_searchdir_flags = O_SEARCH | O_DIRECTORY | base_open_flags; + } + fstatat_flags = dereference_option ? 0 : AT_SYMLINK_NOFOLLOW; + + if (subcommand_option == TEST_LABEL_SUBCOMMAND) { /* --test-label is silent if the user has specified the label name to compare against. */ @@ -2215,6 +2488,13 @@ decode_options (int argc, char **argv) else if (utc_option) verbose_option = 2; + if (tape_length_option && tape_length_option < record_size) + USAGE_ERROR ((0, 0, _("Volume length cannot be less than record size"))); + + if (same_order_option && listed_incremental_option) + USAGE_ERROR ((0, 0, _("--preserve-order is not compatible with " + "--listed-incremental"))); + /* Forbid using -c with no input files whatsoever. Check that `-f -', explicit or implied, is used correctly. */ @@ -2224,11 +2504,16 @@ decode_options (int argc, char **argv) if (!args.input_files && !files_from_option) USAGE_ERROR ((0, 0, _("Cowardly refusing to create an empty archive"))); + if (args.compress_autodetect && archive_names + && strcmp (archive_name_array[0], "-")) + set_compression_program_by_suffix (archive_name_array[0], + use_compress_program_option); break; case EXTRACT_SUBCOMMAND: case LIST_SUBCOMMAND: case DIFF_SUBCOMMAND: + case TEST_LABEL_SUBCOMMAND: for (archive_name_cursor = archive_name_array; archive_name_cursor < archive_name_array + archive_names; archive_name_cursor++) @@ -2276,8 +2561,9 @@ decode_options (int argc, char **argv) backup_option = false; } - if (verbose_option) - report_textual_dates (&args); + checkpoint_finish_compile (); + + report_textual_dates (&args); } @@ -2288,7 +2574,7 @@ int main (int argc, char **argv) { set_start_time (); - program_name = argv[0]; + set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); @@ -2311,18 +2597,16 @@ main (int argc, char **argv) obstack_init (&argv_stk); -#ifdef SIGCHLD /* System V fork+wait does not work if SIGCHLD is ignored. */ signal (SIGCHLD, SIG_DFL); -#endif + + /* Try to disable the ability to unlink a directory. */ + priv_set_remove_linkdir (); /* Decode options. */ decode_options (argc, argv); - /* Close all inherited open descriptors, except for the first three */ - closeopen (); - name_init (); /* Main command execution. */ @@ -2334,7 +2618,7 @@ main (int argc, char **argv) { case UNKNOWN_SUBCOMMAND: USAGE_ERROR ((0, 0, - _("You must specify one of the `-Acdtrux' options"))); + _("You must specify one of the `-Acdtrux' or `--test-label' options"))); case CAT_SUBCOMMAND: case UPDATE_SUBCOMMAND: @@ -2368,6 +2652,9 @@ main (int argc, char **argv) diff_init (); read_and (diff_archive); break; + + case TEST_LABEL_SUBCOMMAND: + test_archive_label (); } if (totals_option) @@ -2385,12 +2672,12 @@ main (int argc, char **argv) name_term (); if (exit_status == TAREXIT_FAILURE) - error (0, 0, _("Error exit delayed from previous errors")); + error (0, 0, _("Exiting with failure status due to previous errors")); if (stdlis == stdout) close_stdout (); else if (ferror (stderr) || fclose (stderr) != 0) - exit_status = TAREXIT_FAILURE; + set_exit_status (TAREXIT_FAILURE); return exit_status; } @@ -2401,9 +2688,31 @@ tar_stat_init (struct tar_stat_info *st) memset (st, 0, sizeof (*st)); } +/* Close the stream or file descriptor associated with ST, and remove + all traces of it from ST. Return true if successful, false (with a + diagnostic) otherwise. */ +bool +tar_stat_close (struct tar_stat_info *st) +{ + int status = (st->dirstream ? closedir (st->dirstream) + : 0 < st->fd ? close (st->fd) + : 0); + st->dirstream = 0; + st->fd = 0; + + if (status == 0) + return true; + else + { + close_diag (st->orig_file_name); + return false; + } +} + void tar_stat_destroy (struct tar_stat_info *st) { + tar_stat_close (st); free (st->orig_file_name); free (st->file_name); free (st->link_name); @@ -2411,6 +2720,7 @@ tar_stat_destroy (struct tar_stat_info *st) free (st->gname); free (st->sparse_map); free (st->dumpdir); + xheader_destroy (&st->xhdr); memset (st, 0, sizeof (*st)); } @@ -2427,3 +2737,13 @@ tar_timespec_cmp (struct timespec a, struct timespec b) a.tv_nsec = b.tv_nsec = 0; return timespec_cmp (a, b); } + +/* Set tar exit status to VAL, unless it is already indicating + a more serious condition. This relies on the fact that the + values of TAREXIT_ constants are ranged by severity. */ +void +set_exit_status (int val) +{ + if (val > exit_status) + exit_status = val; +}