X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Ftar.c;h=ba24c4345859ac88a576c5f3856257069572b30d;hb=HEAD;hp=4a58a74b4280bbe6f042be4421c66178875cbfa1;hpb=138fc7e67e3d9845cd7d81aad0e9c7724784f9b9;p=debian%2Ftar diff --git a/src/tar.c b/src/tar.c index 4a58a74b..ba24c434 100644 --- a/src/tar.c +++ b/src/tar.c @@ -1,7 +1,7 @@ /* 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. + Copyright 1988, 1992-1997, 1999-2001, 2003-2007, 2012-2016 Free + Software Foundation, Inc. Written by John Gilmore, starting 1985-08-25. @@ -16,8 +16,7 @@ Public License for more details. You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + with this program. If not, see . */ #include @@ -25,6 +24,7 @@ #include #include #include +#include #include #if ! defined SIGCHLD && defined SIGCLD @@ -42,14 +42,17 @@ #include #include #include -#include +#include #include #include -#include +#include +#include #include #include #include #include +#include +#include /* Local declarations. */ @@ -65,6 +68,12 @@ # define DEFAULT_BLOCKING 20 #endif +/* Print a message if not all links are dumped */ +static int check_links_option; + +/* Number of allocated tape drive names. */ +static size_t allocated_archive_names; + /* Miscellaneous. */ @@ -76,7 +85,7 @@ void request_stdin (const char *option) { if (stdin_used_by) - USAGE_ERROR ((0, 0, _("Options `-%s' and `-%s' both want standard input"), + USAGE_ERROR ((0, 0, _("Options '%s' and '%s' both want standard input"), stdin_used_by, option)); stdin_used_by = option; @@ -159,6 +168,14 @@ set_archive_format (char const *name) archive_format = p->fmt; } +static void +set_xattr_option (int value) +{ + if (value == 1) + set_archive_format ("posix"); + xattrs_option = value; +} + const char * archive_format_string (enum archive_format fmt) { @@ -212,21 +229,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; @@ -238,7 +262,7 @@ tar_set_quoting_style (char *arg) return; } FATAL_ERROR ((0, 0, - _("Unknown quoting style `%s'. Try `%s --quoting-style=help' to get a list."), arg, program_invocation_short_name)); + _("Unknown quoting style '%s'. Try '%s --quoting-style=help' to get a list."), arg, program_name)); } @@ -246,72 +270,72 @@ tar_set_quoting_style (char *arg) enum { - ANCHORED_OPTION = CHAR_MAX + 1, + ACLS_OPTION = CHAR_MAX + 1, ATIME_PRESERVE_OPTION, BACKUP_OPTION, CHECK_DEVICE_OPTION, CHECKPOINT_OPTION, CHECKPOINT_ACTION_OPTION, + CLAMP_MTIME_OPTION, DELAY_DIRECTORY_RESTORE_OPTION, HARD_DEREFERENCE_OPTION, DELETE_OPTION, - EXCLUDE_CACHES_OPTION, - EXCLUDE_CACHES_UNDER_OPTION, - EXCLUDE_CACHES_ALL_OPTION, - EXCLUDE_OPTION, - 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, + GROUP_MAP_OPTION, IGNORE_COMMAND_ERROR_OPTION, IGNORE_FAILED_READ_OPTION, INDEX_FILE_OPTION, + KEEP_DIRECTORY_SYMLINK_OPTION, KEEP_NEWER_FILES_OPTION, + LEVEL_OPTION, + LZIP_OPTION, LZMA_OPTION, + LZOP_OPTION, MODE_OPTION, MTIME_OPTION, NEWER_MTIME_OPTION, - NO_ANCHORED_OPTION, + NO_ACLS_OPTION, + NO_AUTO_COMPRESS_OPTION, NO_CHECK_DEVICE_OPTION, NO_DELAY_DIRECTORY_RESTORE_OPTION, - NO_IGNORE_CASE_OPTION, NO_IGNORE_COMMAND_ERROR_OPTION, NO_OVERWRITE_DIR_OPTION, NO_QUOTE_CHARS_OPTION, - NO_RECURSION_OPTION, NO_SAME_OWNER_OPTION, NO_SAME_PERMISSIONS_OPTION, - NO_UNQUOTE_OPTION, - NO_WILDCARDS_MATCH_SLASH_OPTION, - NO_WILDCARDS_OPTION, - NULL_OPTION, + NO_SEEK_OPTION, + NO_SELINUX_CONTEXT_OPTION, + NO_XATTR_OPTION, NUMERIC_OWNER_OPTION, OCCURRENCE_OPTION, OLD_ARCHIVE_OPTION, ONE_FILE_SYSTEM_OPTION, + ONE_TOP_LEVEL_OPTION, OVERWRITE_DIR_OPTION, OVERWRITE_OPTION, OWNER_OPTION, + OWNER_MAP_OPTION, PAX_OPTION, POSIX_OPTION, - PRESERVE_OPTION, QUOTE_CHARS_OPTION, QUOTING_STYLE_OPTION, RECORD_SIZE_OPTION, - RECURSION_OPTION, RECURSIVE_UNLINK_OPTION, REMOVE_FILES_OPTION, RESTRICT_OPTION, RMT_COMMAND_OPTION, RSH_COMMAND_OPTION, SAME_OWNER_OPTION, + SELINUX_CONTEXT_OPTION, SHOW_DEFAULTS_OPTION, SHOW_OMITTED_DIRS_OPTION, + SHOW_SNAPSHOT_FIELD_RANGES_OPTION, SHOW_TRANSFORMED_NAMES_OPTION, + SKIP_OLD_FILES_OPTION, + SORT_OPTION, + HOLE_DETECTION_OPTION, SPARSE_VERSION_OPTION, STRIP_COMPONENTS_OPTION, SUFFIX_OPTION, @@ -319,20 +343,16 @@ enum TOTALS_OPTION, TO_COMMAND_OPTION, TRANSFORM_OPTION, - UNQUOTE_OPTION, - USAGE_OPTION, - USE_COMPRESS_PROGRAM_OPTION, UTC_OPTION, - VERSION_OPTION, VOLNO_FILE_OPTION, - WILDCARDS_MATCH_SLASH_OPTION, - WILDCARDS_OPTION + WARNING_OPTION, + XATTR_OPTION, + XATTR_EXCLUDE, + XATTR_INCLUDE }; -const char *argp_program_version = "tar (" PACKAGE_NAME ") " VERSION; -const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">"; static char const doc[] = N_("\ -GNU `tar' saves many files together into a single tape or disk archive, \ +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\ @@ -340,7 +360,7 @@ Examples:\n\ tar -tvf archive.tar # List all files in archive.tar verbosely.\n\ tar -xf archive.tar # Extract all files from archive.tar.\n") "\v" -N_("The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +N_("The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control may be set with --backup or VERSION_CONTROL, values are:\n\n\ none, off never make backups\n\ t, numbered make numbered backups\n\ @@ -350,7 +370,7 @@ The version control may be set with --backup or VERSION_CONTROL, values are:\n\n /* NOTE: - Available option letters are DEIJQY and eqy. Consider the following + Available option letters are DEQY and eqy. Consider the following assignments: [For Solaris tar compatibility =/= Is it important at all?] @@ -358,10 +378,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 @@ -397,12 +421,16 @@ static struct argp_option options[] = { {"sparse", 'S', 0, 0, N_("handle sparse files efficiently"), GRID+1 }, + {"hole-detection", HOLE_DETECTION_OPTION, N_("TYPE"), 0, + N_("technique to detect holes"), GRID+1 }, {"sparse-version", SPARSE_VERSION_OPTION, N_("MAJOR[.MINOR]"), 0, N_("set version of the sparse format to use (implies --sparse)"), GRID+1}, {"incremental", 'G', 0, 0, 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, @@ -413,6 +441,8 @@ 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 }, @@ -430,7 +460,11 @@ static struct argp_option options[] = { {"remove-files", REMOVE_FILES_OPTION, 0, 0, N_("remove files after adding them to the archive"), GRID+1 }, {"keep-old-files", 'k', 0, 0, - N_("don't replace existing files when extracting"), GRID+1 }, + N_("don't replace existing files when extracting, " + "treat them as errors"), GRID+1 }, + {"skip-old-files", SKIP_OLD_FILES_OPTION, 0, 0, + N_("don't replace existing files when extracting, silently skip over them"), + GRID+1 }, {"keep-newer-files", KEEP_NEWER_FILES_OPTION, 0, 0, N_("don't replace existing files that are newer than their archive copies"), GRID+1 }, {"overwrite", OVERWRITE_OPTION, 0, 0, @@ -444,6 +478,12 @@ static struct argp_option options[] = { {"overwrite-dir", OVERWRITE_DIR_OPTION, 0, 0, N_("overwrite metadata of existing directories when extracting (default)"), GRID+1 }, + {"keep-directory-symlink", KEEP_DIRECTORY_SYMLINK_OPTION, 0, 0, + N_("preserve existing symlinks to directories when extracting"), + GRID+1 }, + {"one-top-level", ONE_TOP_LEVEL_OPTION, N_("DIR"), OPTION_ARG_OPTIONAL, + N_("create a subdirectory to avoid having loose files extracted"), + GRID+1 }, #undef GRID #define GRID 40 @@ -468,8 +508,14 @@ static struct argp_option options[] = { N_("force NAME as owner for added files"), GRID+1 }, {"group", GROUP_OPTION, N_("NAME"), 0, N_("force NAME as group for added files"), GRID+1 }, + {"owner-map", OWNER_MAP_OPTION, N_("FILE"), 0, + N_("use FILE to map file owner UIDs and names"), GRID+1 }, + {"group-map", GROUP_MAP_OPTION, N_("FILE"), 0, + N_("use FILE to map file owner GIDs and names"), GRID+1 }, {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0, N_("set mtime for added files from DATE-OR-FILE"), GRID+1 }, + {"clamp-mtime", CLAMP_MTIME_OPTION, 0, 0, + N_("only set time when the file is more recent than what was given with --mtime"), GRID+1 }, {"mode", MODE_OPTION, N_("CHANGES"), 0, N_("force (symbolic) mode CHANGES for added files"), GRID+1 }, {"atime-preserve", ATIME_PRESERVE_OPTION, @@ -480,9 +526,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, @@ -492,15 +538,43 @@ static struct argp_option options[] = { {"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)"), GRID+1 }, {"preserve-order", 's', 0, 0, - N_("sort names to extract to match archive"), GRID+1 }, + N_("member arguments are listed in the same order as the " + "files in the archive"), GRID+1 }, {"same-order", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, - {"preserve", PRESERVE_OPTION, 0, 0, - N_("same as both -p and -s"), GRID+1 }, {"delay-directory-restore", DELAY_DIRECTORY_RESTORE_OPTION, 0, 0, N_("delay setting modification times and permissions of extracted" " directories until the end of extraction"), GRID+1 }, {"no-delay-directory-restore", NO_DELAY_DIRECTORY_RESTORE_OPTION, 0, 0, N_("cancel the effect of --delay-directory-restore option"), GRID+1 }, + {"sort", SORT_OPTION, N_("ORDER"), 0, +#if D_INO_IN_DIRENT + N_("directory sorting order: none (default), name or inode" +#else + N_("directory sorting order: none (default) or name" +#endif + ), GRID+1 }, +#undef GRID + +#define GRID 55 + {NULL, 0, NULL, 0, + N_("Handling of extended file attributes:"), GRID }, + + {"xattrs", XATTR_OPTION, 0, 0, + N_("Enable extended attributes support"), GRID+1 }, + {"no-xattrs", NO_XATTR_OPTION, 0, 0, + N_("Disable extended attributes support"), GRID+1 }, + {"xattrs-include", XATTR_INCLUDE, N_("MASK"), 0, + N_("specify the include pattern for xattr keys"), GRID+1 }, + {"xattrs-exclude", XATTR_EXCLUDE, N_("MASK"), 0, + N_("specify the exclude pattern for xattr keys"), GRID+1 }, + {"selinux", SELINUX_CONTEXT_OPTION, 0, 0, + N_("Enable the SELinux context support"), GRID+1 }, + {"no-selinux", NO_SELINUX_CONTEXT_OPTION, 0, 0, + N_("Disable the SELinux context support"), GRID+1 }, + {"acls", ACLS_OPTION, 0, 0, + N_("Enable the POSIX ACLs support"), GRID+1 }, + {"no-acls", NO_ACLS_OPTION, 0, 0, + N_("Disable the POSIX ACLs support"), GRID+1 }, #undef GRID #define GRID 60 @@ -516,7 +590,7 @@ static struct argp_option options[] = { {"rsh-command", RSH_COMMAND_OPTION, N_("COMMAND"), 0, N_("use remote COMMAND instead of rsh"), GRID+1 }, #ifdef DEVICE_PREFIX - {"-[0-7][lmh]", 0, NULL, OPTION_DOC, /* It is OK, since `name' will never be + {"-[0-7][lmh]", 0, NULL, OPTION_DOC, /* It is OK, since 'name' will never be translated */ N_("specify drive and density"), GRID+1 }, #endif @@ -592,72 +666,37 @@ static struct argp_option options[] = { N_("Compression options:"), GRID }, {"auto-compress", 'a', 0, 0, N_("use archive suffix to determine the compression program"), GRID+1 }, - {"bzip2", 'j', 0, 0, - N_("filter the archive through bzip2"), GRID+1 }, - {"gzip", 'z', 0, 0, - N_("filter the archive through gzip"), 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, - N_("filter the archive through compress"), GRID+1 }, + {"compress", 'Z', 0, 0, NULL, GRID+1 }, {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, - {"lzma", LZMA_OPTION, 0, 0, - N_("filter the archive through lzma"), GRID+1 }, - {"use-compress-program", USE_COMPRESS_PROGRAM_OPTION, N_("PROG"), 0, - N_("filter through PROG (must accept -d)"), 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 }, - - {"add-file", ARGP_KEY_ARG, N_("FILE"), 0, - N_("add given FILE to the archive (useful if its name starts with a dash)"), GRID+1 }, - {"directory", 'C', N_("DIR"), 0, - N_("change to directory DIR"), GRID+1 }, - {"files-from", 'T', N_("FILE"), 0, - 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 }, - {"unquote", UNQUOTE_OPTION, 0, 0, - N_("unquote filenames read with -T (default)"), GRID+1 }, - {"no-unquote", NO_UNQUOTE_OPTION, 0, 0, - N_("do not unquote filenames read with -T"), GRID+1 }, - {"exclude", EXCLUDE_OPTION, N_("PATTERN"), 0, - N_("exclude files, given as a PATTERN"), GRID+1 }, - {"exclude-from", 'X', N_("FILE"), 0, - N_("exclude patterns listed in FILE"), GRID+1 }, - {"exclude-caches", EXCLUDE_CACHES_OPTION, 0, 0, - N_("exclude contents of directories containing CACHEDIR.TAG, " - "except for the tag file itself"), GRID+1 }, - {"exclude-caches-under", EXCLUDE_CACHES_UNDER_OPTION, 0, 0, - N_("exclude everything under directories containing CACHEDIR.TAG"), - GRID+1 }, - {"exclude-caches-all", EXCLUDE_CACHES_ALL_OPTION, 0, 0, - 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 }, - {"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 }, - {"no-recursion", NO_RECURSION_OPTION, 0, 0, - N_("avoid descending automatically in directories"), GRID+1 }, {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0, N_("stay in local file system when creating archive"), GRID+1 }, - {"recursion", RECURSION_OPTION, 0, 0, - N_("recurse into directories (default)"), GRID+1 }, {"absolute-names", 'P', 0, 0, - N_("don't strip leading `/'s from file names"), GRID+1 }, + 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 }, + N_("begin at member MEMBER-NAME when reading the archive"), GRID+1 }, {"newer", 'N', N_("DATE-OR-FILE"), 0, N_("only store files newer than DATE-OR-FILE"), GRID+1 }, {"after-date", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, @@ -677,28 +716,7 @@ static struct argp_option options[] = { GRID+1 }, {"transform", TRANSFORM_OPTION, N_("EXPRESSION"), 0, N_("use sed replace EXPRESSION to transform file names"), GRID+1 }, -#undef GRID - -#define GRID 120 - {NULL, 0, NULL, 0, - N_("File name matching options (affect both exclude and include patterns):"), - GRID }, - {"ignore-case", IGNORE_CASE_OPTION, 0, 0, - N_("ignore case"), GRID+1 }, - {"anchored", ANCHORED_OPTION, 0, 0, - N_("patterns match file name start"), GRID+1 }, - {"no-anchored", NO_ANCHORED_OPTION, 0, 0, - N_("patterns match after any `/' (default for exclusion)"), GRID+1 }, - {"no-ignore-case", NO_IGNORE_CASE_OPTION, 0, 0, - N_("case sensitive matching (default)"), GRID+1 }, - {"wildcards", WILDCARDS_OPTION, 0, 0, - N_("use wildcards (default for exclusion)"), GRID+1 }, - {"no-wildcards", NO_WILDCARDS_OPTION, 0, 0, - N_("verbatim string matching"), GRID+1 }, - {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0, - N_("wildcards do not match `/'"), GRID+1 }, - {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0, - N_("wildcards match `/' (default for exclusion)"), GRID+1 }, + {"xform", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, #undef GRID #define GRID 130 @@ -707,6 +725,8 @@ static struct argp_option options[] = { {"verbose", 'v', 0, 0, N_("verbosely list files processed"), GRID+1 }, + {"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 }, @@ -721,7 +741,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, @@ -731,6 +753,8 @@ static struct argp_option options[] = { {"confirmation", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"show-defaults", SHOW_DEFAULTS_OPTION, 0, 0, N_("show tar defaults"), GRID+1 }, + {"show-snapshot-field-ranges", SHOW_SNAPSHOT_FIELD_RANGES_OPTION, 0, 0, + N_("show valid ranges for snapshot-file fields"), GRID+1 }, {"show-omitted-dirs", SHOW_OMITTED_DIRS_OPTION, 0, 0, N_("when listing or extracting, list each directory that does not match search criteria"), GRID+1 }, {"show-transformed-names", SHOW_TRANSFORMED_NAMES_OPTION, 0, 0, @@ -759,14 +783,6 @@ static struct argp_option options[] = { {"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} @@ -786,25 +802,13 @@ static enum atime_preserve const atime_preserve_types[] = (minus 1 for NULL guard) */ ARGMATCH_VERIFY (atime_preserve_args, atime_preserve_types); -/* Wildcard matching settings */ -enum wildcards - { - default_wildcards, /* For exclusion == enable_wildcards, - for inclusion == disable_wildcards */ - disable_wildcards, - enable_wildcards - }; - struct tar_args /* Variables used during option parsing */ { + struct option_locus *loc; + struct textual_date *textual_date; /* Keeps the arguments to --newer-mtime and/or --date option if they are textual dates */ - enum wildcards wildcards; /* Wildcard settings (--wildcards/ - --no-wildcards) */ - int matching_flags; /* exclude_fnmatch options */ - int include_anchored; /* Pattern anchoring options used for - file inclusion */ bool o_option; /* True if -o option was given */ bool pax_option; /* True if --pax-option was given */ char const *backup_suffix_string; /* --suffix option argument */ @@ -815,103 +819,136 @@ struct tar_args /* Variables used during option parsing */ }; -#define MAKE_EXCL_OPTIONS(args) \ - ((((args)->wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0) \ - | (args)->matching_flags \ - | recursion_option) - -#define MAKE_INCL_OPTIONS(args) \ - ((((args)->wildcards == enable_wildcards) ? EXCLUDE_WILDCARDS : 0) \ - | (args)->include_anchored \ - | (args)->matching_flags \ - | recursion_option) - -void -exclude_vcs_files () +static char * +format_default_settings (void) { - int i; - static char *vcs_file[] = { - /* CVS: */ - "CVS", - ".cvsignore", - /* RCS: */ - "RCS", - /* SCCS: */ - "SCCS", - /* SVN: */ - ".svn", - /* git: */ - ".git", - ".gitignore", - /* Arch: */ - ".arch-ids", - "{arch}", - "=RELEASE-ID", - "=meta-update", - "=update", - NULL - }; - - for (i = 0; vcs_file[i]; i++) - add_exclude (excluded, vcs_file[i], 0); + 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 + ); } - -#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"); \ +static void +option_conflict_error (const char *a, const char *b) +{ + /* TRANSLATORS: Both %s in this statement are replaced with + option names. */ + USAGE_ERROR ((0, 0, _("'%s' cannot be used with '%s'"), a, b)); } -#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"); \ + +/* Classes of options that can conflict: */ +enum option_class + { + OC_COMPRESS, /* Compress options: -JjZz, -I, etc. */ + OC_OCCURRENCE, /* --occurrence */ + OC_LISTED_INCREMENTAL, /* --listed-incremental */ + OC_NEWER, /* --newer, --newer-mtime, --after-date */ + OC_VERIFY, /* --verify */ + OC_STARTING_FILE, /* --starting-file */ + OC_SAME_ORDER, /* --same-order */ + OC_ONE_TOP_LEVEL, /* --one-top-level */ + OC_ABSOLUTE_NAMES, /* --absolute-names */ + OC_OLD_FILES, /* --keep-old-files, --overwrite, etc. */ + OC_MAX + }; + +/* Table of locations of potentially conflicting options. Two options can + conflict only if they procede from the command line. Otherwise, options + in command line silently override those defined in TAR_OPTIONS. */ +static struct option_locus *option_class[OC_MAX]; + +/* Save location of an option of class ID. Return location of a previous + occurrence of an option of that class, or NULL. */ +static struct option_locus * +optloc_save (unsigned int id, struct option_locus *loc) +{ + struct option_locus *optloc; + char *p; + size_t s; + + if (id >= sizeof (option_class) / sizeof (option_class[0])) + abort (); + s = sizeof (*loc); + if (loc->name) + s += strlen (loc->name) + 1; + optloc = xmalloc (s); + if (loc->name) + { + p = (char*) optloc + sizeof (*loc); + strcpy (p, loc->name); + optloc->name = p; + } + else + optloc->name = NULL; + optloc->source = loc->source; + optloc->line = loc->line; + optloc->prev = option_class[id]; + option_class[id] = optloc; + return optloc->prev; } -#endif -static void -show_default_settings (FILE *fp) - DECL_SHOW_DEFAULT_SETTINGS(fp, fprintf) +/* Return location of a recent option of class ID */ +static struct option_locus * +optloc_lookup (int id) +{ + return option_class[id]; +} -static void -show_default_settings_fs (argp_fmtstream_t fs) - DECL_SHOW_DEFAULT_SETTINGS(fs, argp_fmtstream_printf) +/* Return true if the latest occurrence of option ID was in the command line */ +static int +option_set_in_cl (int id) +{ + struct option_locus *loc = optloc_lookup (id); + if (!loc) + return 0; + return loc->source == OPTS_COMMAND_LINE; +} +/* Compare two option locations */ +static int +optloc_eq (struct option_locus *a, struct option_locus *b) +{ + if (a->source != b->source) + return 0; + if (a->source == OPTS_COMMAND_LINE) + return 1; + return strcmp (a->name, b->name) == 0; +} + 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', '--delete' or '--test-label' option"))); subcommand_option = subcommand; } static void -set_use_compress_program_option (const char *string) +set_use_compress_program_option (const char *string, struct option_locus *loc) { + struct option_locus *p = optloc_save (OC_COMPRESS, loc); if (use_compress_program_option - && strcmp (use_compress_program_option, string) != 0) + && strcmp (use_compress_program_option, string) != 0 + && p->source == OPTS_COMMAND_LINE) USAGE_ERROR ((0, 0, _("Conflicting compression options"))); use_compress_program_option = string; } -static RETSIGTYPE +static void sigstat (int signo) { compute_duration (); @@ -925,24 +962,27 @@ static void stat_on_signal (int signo) { #ifdef HAVE_SIGACTION +# ifndef SA_RESTART +# define SA_RESTART 0 +# endif struct sigaction act; act.sa_handler = sigstat; sigemptyset (&act.sa_mask); - act.sa_flags = 0; + act.sa_flags = SA_RESTART; sigaction (signo, &act, NULL); #else signal (signo, sigstat); #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 }, @@ -954,7 +994,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) @@ -969,12 +1009,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) { @@ -983,7 +1023,7 @@ get_date_or_file (struct tar_args *args, const char *option, || *str == '.') { struct stat st; - if (deref_stat (dereference_option, str, &st) != 0) + if (stat (str, &st) != 0) { stat_error (str); USAGE_ERROR ((0, 0, _("Date sample file not found"))); @@ -992,22 +1032,24 @@ get_date_or_file (struct tar_args *args, const char *option, } else { - if (! get_date (ts, str, NULL)) + if (! parse_datetime (ts, str, NULL)) { 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 @@ -1017,221 +1059,259 @@ 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; +/* Default density numbers for [0-9][lmh] device specifications */ -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 */ - file_list_skip /* Empty (zero-length) entry encountered, skip it */ - }; +#if defined DEVICE_PREFIX && !defined DENSITY_LETTER +# ifndef LOW_DENSITY_NUM +# define LOW_DENSITY_NUM 0 +# endif -/* 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) +# ifndef MID_DENSITY_NUM +# define MID_DENSITY_NUM 8 +# endif + +# ifndef HIGH_DENSITY_NUM +# define HIGH_DENSITY_NUM 16 +# endif +#endif + + +static char * +tar_help_filter (int key, const char *text, void *input) { - int c; - size_t counter = 0; + struct obstack stk; + char *s; - for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp)) + switch (key) { - if (c == 0) - { - /* We have read a zero separator. The file possibly is - zero-separated */ - return file_list_zero; - } - obstack_1grow (stk, c); - counter++; - } + default: + s = (char*) text; + break; - if (counter == 0 && c != EOF) - return file_list_skip; + case 'j': + s = xasprintf (_("filter the archive through %s"), BZIP2_PROGRAM); + break; - obstack_1grow (stk, 0); + case 'z': + s = xasprintf (_("filter the archive through %s"), GZIP_PROGRAM); + break; - return (counter == 0 && c == EOF) ? file_list_end : file_list_success; -} + case 'Z': + s = xasprintf (_("filter the archive through %s"), COMPRESS_PROGRAM); + break; - -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 */ + case LZIP_OPTION: + s = xasprintf (_("filter the archive through %s"), LZIP_PROGRAM); + break; -/* Prevent recursive inclusion of the same file */ -struct file_id_list -{ - struct file_id_list *next; - ino_t ino; - dev_t dev; -}; + case LZMA_OPTION: + s = xasprintf (_("filter the archive through %s"), LZMA_PROGRAM); + break; -static struct file_id_list *file_id_list; + case LZOP_OPTION: + s = xasprintf (_("filter the archive through %s"), LZOP_PROGRAM); -static void -add_file_id (const char *filename) -{ - struct file_id_list *p; - struct stat st; + case 'J': + s = xasprintf (_("filter the archive through %s"), XZ_PROGRAM); + break; - 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) + case ARGP_KEY_HELP_EXTRA: { - FATAL_ERROR ((0, 0, _("%s: file list already read"), - quotearg_colon (filename))); + 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); } - p = xmalloc (sizeof *p); - p->next = file_id_list; - p->ino = st.st_ino; - p->dev = st.st_dev; - file_id_list = p; + } + return s; } + +static char * +expand_pax_option (struct tar_args *targs, const char *arg) +{ + struct obstack stk; + char *res; -/* Default density numbers for [0-9][lmh] device specifications */ - -#ifndef LOW_DENSITY_NUM -# define LOW_DENSITY_NUM 0 -#endif - -#ifndef MID_DENSITY_NUM -# define MID_DENSITY_NUM 8 -#endif + 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[TIMESPEC_STRSIZE_BOUND]; + char const *s = code_timespec (ts, 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); -#ifndef HIGH_DENSITY_NUM -# define HIGH_DENSITY_NUM 16 -#endif + arg += seglen; + if (*arg) + { + obstack_1grow (&stk, *arg); + arg++; + } + } + obstack_1grow (&stk, 0); + res = xstrdup (obstack_finish (&stk)); + obstack_free (&stk, NULL); + return res; +} -static void -update_argv (const char *filename, struct argp_state *state) + +static uintmax_t +parse_owner_group (char *arg, uintmax_t field_max, char const **name_option) { - 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, "-")) + uintmax_t u = UINTMAX_MAX; + char *end; + char const *name = 0; + char const *invalid_num = 0; + char *colon = strchr (arg, ':'); + + if (colon) { - is_stdin = true; - request_stdin ("-T"); - fp = stdin; + char const *num = colon + 1; + *colon = '\0'; + if (*arg) + name = arg; + if (num && (! (xstrtoumax (num, &end, 10, &u, "") == LONGINT_OK + && u <= field_max))) + invalid_num = num; } else { - add_file_id (filename); - if ((fp = fopen (filename, "r")) == NULL) - open_fatal (filename); - } - - while ((read_state = read_name_from_file (fp, &argv_stk)) != file_list_end) - { - switch (read_state) + uintmax_t u1; + switch ('0' <= *arg && *arg <= '9' + ? xstrtoumax (arg, &end, 10, &u1, "") + : LONGINT_INVALID) { - case file_list_success: - count++; + default: + name = arg; break; - case file_list_end: /* won't happen, just to pacify gcc */ - break; - - case 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; - break; - } - - case file_list_skip: + case LONGINT_OK: + if (u1 <= field_max) + { + u = u1; + break; + } + /* Fall through. */ + case LONGINT_OVERFLOW: + invalid_num = arg; break; } } - if (!is_stdin) - fclose (fp); + if (invalid_num) + FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (invalid_num), + _("Invalid owner or group ID"))); + if (name) + *name_option = name; + return u; +} - if (count == 0) - return; +#define TAR_SIZE_SUFFIXES "bBcGgkKMmPTtw" - start = obstack_finish (&argv_stk); +static char const *const sort_mode_arg[] = { + "none", + "name", +#if D_INO_IN_DIRENT + "inode", +#endif + NULL +}; - if (filename_terminator == 0) - for (p = start; *p; p += strlen (p) + 1) - if (p[0] == '-') - count++; +static int sort_mode_flag[] = { + SAVEDIR_SORT_NONE, + SAVEDIR_SORT_NAME, +#if D_INO_IN_DIRENT + SAVEDIR_SORT_INODE +#endif +}; - 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])); +ARGMATCH_VERIFY (sort_mode_arg, sort_mode_flag); - state->argc = new_argc; +static char const *const hole_detection_args[] = +{ + "raw", "seek", NULL +}; - for (i = state->next, p = start; *p; p += strlen (p) + 1, i++) - { - if (filename_terminator == 0 && p[0] == '-') - state->argv[i++] = "--add-file"; - state->argv[i] = p; - } -} +static int const hole_detection_types[] = +{ + HOLE_DETECTION_RAW, HOLE_DETECTION_SEEK +}; + +ARGMATCH_VERIFY (hole_detection_args, hole_detection_types); static void -tar_help (struct argp_state *state) +set_old_files_option (int code, struct option_locus *loc) { - 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 option_locus *prev; + static char const *const code_to_opt[] = { + "--overwrite-dir", + "--no-overwrite-dir", + "--overwrite", + "--unlink-first", + "--keep-old-files", + "--skip-old-files", + "--keep-newer-files" + }; + + prev = optloc_save (OC_OLD_FILES, loc); + if (prev && optloc_eq (loc, prev) && code != old_files_option) + option_conflict_error (code_to_opt[code], code_to_opt[old_files_option]); + + old_files_option = code; } + static error_t parse_opt (int key, char *arg, struct argp_state *state) { @@ -1241,7 +1321,7 @@ parse_opt (int key, char *arg, struct argp_state *state) { case ARGP_KEY_ARG: /* File name or non-parsed option, because of ARGP_IN_ORDER */ - name_add_name (arg, MAKE_INCL_OPTIONS (args)); + name_add_name (arg); args->input_files = true; break; @@ -1252,7 +1332,11 @@ parse_opt (int key, char *arg, struct argp_state *state) case 'a': args->compress_autodetect = true; break; - + + case NO_AUTO_COMPRESS_OPTION: + args->compress_autodetect = false; + break; + case 'b': { uintmax_t u; @@ -1280,8 +1364,8 @@ parse_opt (int key, char *arg, struct argp_state *state) set_subcommand_option (CREATE_SUBCOMMAND); break; - case 'C': - name_add_dir (arg); + case CLAMP_MTIME_OPTION: + set_mtime_option = CLAMP_MTIME; break; case 'd': @@ -1305,7 +1389,12 @@ 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': + optloc_save (OC_LISTED_INCREMENTAL, args->loc); listed_incremental_option = arg; after_date_option = true; /* Fall through. */ @@ -1326,7 +1415,7 @@ parse_opt (int key, char *arg, struct argp_state *state) 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 @@ -1335,24 +1424,23 @@ 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, args->loc); break; - case 'j': - set_use_compress_program_option ("bzip2"); + case 'J': + set_use_compress_program_option (XZ_PROGRAM, args->loc); break; case 'k': /* Don't replace existing files. */ - old_files_option = KEEP_OLD_FILES; + set_old_files_option (KEEP_OLD_FILES, args->loc); break; case 'K': + optloc_save (OC_STARTING_FILE, args->loc); starting_file_option = true; - addname (arg, 0); + addname (arg, 0, true, NULL); break; case ONE_FILE_SYSTEM_OPTION: @@ -1361,6 +1449,12 @@ parse_opt (int key, char *arg, struct argp_state *state) one_file_system_option = true; break; + case ONE_TOP_LEVEL_OPTION: + optloc_save (OC_ONE_TOP_LEVEL, args->loc); + one_top_level_option = true; + one_top_level_dir = arg; + break; + case 'l': check_links_option = 1; break; @@ -1368,18 +1462,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, args->loc); + break; + case LZMA_OPTION: - set_use_compress_program_option ("lzma"); + set_use_compress_program_option (LZMA_PROGRAM, args->loc); break; - + + case LZOP_OPTION: + set_use_compress_program_option (LZOP_PROGRAM, args->loc); + break; + case 'm': touch_option = true; break; @@ -1393,11 +1509,16 @@ parse_opt (int key, char *arg, struct argp_state *state) case MTIME_OPTION: get_date_or_file (args, "--mtime", arg, &mtime_option); - set_mtime_option = true; + if (set_mtime_option == USE_FILE_MTIME) + set_mtime_option = FORCE_MTIME; break; case 'n': - seekable_archive = true; + seek_option = 1; + break; + + case NO_SEEK_OPTION: + seek_option = 0; break; case 'N': @@ -1405,11 +1526,12 @@ parse_opt (int key, char *arg, struct argp_state *state) /* Fall through. */ case NEWER_MTIME_OPTION: - if (NEWER_OPTION_INITIALIZED (newer_mtime_option)) + if (TIME_OPTION_INITIALIZED (newer_mtime_option)) 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); + optloc_save (OC_NEWER, args->loc); break; case 'o': @@ -1425,6 +1547,7 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case 'P': + optloc_save (OC_ABSOLUTE_NAMES, args->loc); absolute_names_option = true; break; @@ -1445,7 +1568,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case 's': /* Names to extract are sorted. */ - + optloc_save (OC_SAME_ORDER, args->loc); same_order_option = true; break; @@ -1453,6 +1576,16 @@ parse_opt (int key, char *arg, struct argp_state *state) sparse_option = true; break; + case SKIP_OLD_FILES_OPTION: + set_old_files_option (SKIP_OLD_FILES, args->loc); + break; + + case HOLE_DETECTION_OPTION: + hole_detection = XARGMATCH ("--hole-detection", arg, + hole_detection_args, hole_detection_types); + sparse_option = true; + break; + case SPARSE_VERSION_OPTION: sparse_option = true; { @@ -1475,16 +1608,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; - 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; + set_subcommand_option (TEST_LABEL_SUBCOMMAND); break; case 'u': @@ -1492,7 +1616,7 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case 'U': - old_files_option = UNLINK_FIRST_OLD_FILES; + set_old_files_option (UNLINK_FIRST_OLD_FILES, args->loc); break; case UTC_OPTION: @@ -1501,6 +1625,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case 'v': verbose_option++; + warning_option |= WARN_VERBOSE_WARNINGS; break; case 'V': @@ -1512,6 +1637,7 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case 'W': + optloc_save (OC_VERIFY, args->loc); verify_option = true; break; @@ -1519,26 +1645,12 @@ parse_opt (int key, char *arg, struct argp_state *state) set_subcommand_option (EXTRACT_SUBCOMMAND); break; - case 'X': - if (add_exclude_file (add_exclude, excluded, arg, - MAKE_EXCL_OPTIONS (args), '\n') - != 0) - { - int e = errno; - FATAL_ERROR ((0, e, "%s", quotearg_colon (arg))); - } - break; - case 'z': - set_use_compress_program_option ("gzip"); + set_use_compress_program_option (GZIP_PROGRAM, args->loc); break; case 'Z': - set_use_compress_program_option ("compress"); - break; - - case ANCHORED_OPTION: - args->matching_flags |= EXCLUDE_ANCHORED; + set_use_compress_program_option (COMPRESS_PROGRAM, args->loc); break; case ATIME_PRESERVE_OPTION: @@ -1556,11 +1668,11 @@ parse_opt (int key, char *arg, struct argp_state *state) case CHECK_DEVICE_OPTION: check_device_option = true; break; - + case NO_CHECK_DEVICE_OPTION: check_device_option = false; break; - + case CHECKPOINT_OPTION: if (arg) { @@ -1583,7 +1695,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case CHECKPOINT_ACTION_OPTION: checkpoint_compile_action (arg); break; - + case BACKUP_OPTION: backup_option = true; if (arg) @@ -1602,41 +1714,6 @@ parse_opt (int key, char *arg, struct argp_state *state) set_subcommand_option (DELETE_SUBCOMMAND); break; - case EXCLUDE_OPTION: - add_exclude (excluded, arg, MAKE_EXCL_OPTIONS (args)); - break; - - case EXCLUDE_CACHES_OPTION: - add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_contents, - cachedir_file_p); - break; - - case EXCLUDE_CACHES_UNDER_OPTION: - add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_under, - cachedir_file_p); - break; - - case EXCLUDE_CACHES_ALL_OPTION: - add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_all, - cachedir_file_p); - break; - - 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: - exclude_vcs_files (); - break; - case FORCE_LOCAL_OPTION: force_local_option = true; break; @@ -1649,10 +1726,6 @@ parse_opt (int key, char *arg, struct argp_state *state) index_file_name = arg; break; - case IGNORE_CASE_OPTION: - args->matching_flags |= FNM_CASEFOLD; - break; - case IGNORE_COMMAND_ERROR_OPTION: ignore_command_error_option = true; break; @@ -1661,24 +1734,33 @@ parse_opt (int key, char *arg, struct argp_state *state) ignore_failed_read_option = true; break; + case KEEP_DIRECTORY_SYMLINK_OPTION: + keep_directory_symlink_option = true; + break; + case KEEP_NEWER_FILES_OPTION: - old_files_option = KEEP_NEWER_FILES; + set_old_files_option (KEEP_NEWER_FILES, args->loc); 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"))); - } + { + uintmax_t u = parse_owner_group (arg, TYPE_MAXIMUM (gid_t), + &group_name_option); + if (u == UINTMAX_MAX) + { + group_option = -1; + if (group_name_option) + gname_to_gid (group_name_option, &group_option); + } + else + group_option = u; + } break; + case GROUP_MAP_OPTION: + group_map_read (arg); + break; + case MODE_OPTION: mode_option = mode_compile (arg); if (!mode_option) @@ -1687,21 +1769,12 @@ parse_opt (int key, char *arg, struct argp_state *state) umask (initial_umask); break; - case NO_ANCHORED_OPTION: - args->include_anchored = 0; /* Clear the default for comman line args */ - args->matching_flags &= ~ EXCLUDE_ANCHORED; - break; - - case NO_IGNORE_CASE_OPTION: - args->matching_flags &= ~ 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; + set_old_files_option (NO_OVERWRITE_DIR_OLD_FILES, args->loc); break; case NO_QUOTE_CHARS_OPTION: @@ -1709,23 +1782,12 @@ parse_opt (int key, char *arg, struct argp_state *state) set_char_quoting (NULL, *arg, 0); break; - case NO_WILDCARDS_OPTION: - args->wildcards = disable_wildcards; - break; - - case NO_WILDCARDS_MATCH_SLASH_OPTION: - args->matching_flags |= FNM_FILE_NAME; - break; - - case NULL_OPTION: - filename_terminator = '\0'; - break; - case NUMERIC_OWNER_OPTION: numeric_owner_option = true; break; case OCCURRENCE_OPTION: + optloc_save (OC_OCCURRENCE, args->loc); if (!arg) occurrence_option = 1; else @@ -1739,28 +1801,37 @@ parse_opt (int key, char *arg, struct argp_state *state) } break; + case OLD_ARCHIVE_OPTION: + set_archive_format ("v7"); + break; + case OVERWRITE_DIR_OPTION: - old_files_option = DEFAULT_OLD_FILES; + set_old_files_option (DEFAULT_OLD_FILES, args->loc); break; case OVERWRITE_OPTION: - old_files_option = OVERWRITE_OLD_FILES; + set_old_files_option (OVERWRITE_OLD_FILES, args->loc); 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"))); - } + { + uintmax_t u = parse_owner_group (arg, TYPE_MAXIMUM (uid_t), + &owner_name_option); + if (u == UINTMAX_MAX) + { + owner_option = -1; + if (owner_name_option) + uname_to_uid (owner_name_option, &owner_option); + } + else + owner_option = u; + } break; + case OWNER_MAP_OPTION: + owner_map_read (arg); + break; + case QUOTE_CHARS_OPTION: for (;*arg; arg++) set_char_quoting (NULL, *arg, 1); @@ -1771,24 +1842,23 @@ 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: set_archive_format ("posix"); break; - case PRESERVE_OPTION: - /* FIXME: What it is good for? */ - same_permissions_option = true; - same_order_option = true; - 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"))); @@ -1821,7 +1891,16 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case SHOW_DEFAULTS_OPTION: - show_default_settings (stdout); + { + char *s = format_default_settings (); + printf ("%s\n", s); + close_stdout (); + free (s); + exit (0); + } + + case SHOW_SNAPSHOT_FIELD_RANGES_OPTION: + show_snapshot_field_ranges (); close_stdout (); exit (0); @@ -1844,6 +1923,11 @@ parse_opt (int key, char *arg, struct argp_state *state) show_transformed_names_option = true; break; + case SORT_OPTION: + savedir_sort_order = XARGMATCH ("--sort", arg, + sort_mode_arg, sort_mode_flag); + break; + case SUFFIX_OPTION: backup_option = true; args->backup_suffix_string = arg; @@ -1866,48 +1950,60 @@ parse_opt (int key, char *arg, struct argp_state *state) set_transform_expr (arg); break; - case USE_COMPRESS_PROGRAM_OPTION: - set_use_compress_program_option (arg); + case 'I': + set_use_compress_program_option (arg, args->loc); break; case VOLNO_FILE_OPTION: volno_file_option = arg; break; - case WILDCARDS_OPTION: - args->wildcards = enable_wildcards; + case NO_SAME_OWNER_OPTION: + same_owner_option = -1; break; - case WILDCARDS_MATCH_SLASH_OPTION: - args->matching_flags &= ~ FNM_FILE_NAME; + case NO_SAME_PERMISSIONS_OPTION: + same_permissions_option = -1; break; - case NO_RECURSION_OPTION: - recursion_option = 0; + case ACLS_OPTION: + set_archive_format ("posix"); + acls_option = 1; break; - case NO_SAME_OWNER_OPTION: - same_owner_option = -1; + case NO_ACLS_OPTION: + acls_option = -1; break; - case NO_SAME_PERMISSIONS_OPTION: - same_permissions_option = -1; + case SELINUX_CONTEXT_OPTION: + set_archive_format ("posix"); + selinux_context_option = 1; break; - case RECURSION_OPTION: - recursion_option = FNM_LEADING_DIR; + case NO_SELINUX_CONTEXT_OPTION: + selinux_context_option = -1; break; - case SAME_OWNER_OPTION: - same_owner_option = 1; + case XATTR_OPTION: + set_xattr_option (1); + break; + + case NO_XATTR_OPTION: + set_xattr_option (-1); break; - case UNQUOTE_OPTION: - unquote_option = true; + case XATTR_INCLUDE: + case XATTR_EXCLUDE: + set_xattr_option (1); + xattrs_mask_add (arg, (key == XATTR_INCLUDE)); + break; + + case SAME_OWNER_OPTION: + same_owner_option = 1; break; - case NO_UNQUOTE_OPTION: - unquote_option = false; + case WARNING_OPTION: + set_warning_option (arg); break; case '0': @@ -1953,7 +2049,7 @@ parse_opt (int key, char *arg, struct argp_state *state) break; default: - argp_error (state, _("Unknown density: `%c'"), arg[0]); + argp_error (state, _("Unknown density: '%c'"), arg[0]); } sprintf (cursor, "%d", device); @@ -1970,31 +2066,17 @@ parse_opt (int key, char *arg, struct argp_state *state) #else /* not DEVICE_PREFIX */ argp_error (state, - _("Options `-[0-7][lmh]' not supported by *this* tar")); + _("Options '-[0-7][lmh]' not supported by *this* tar")); #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; + case ARGP_KEY_ERROR: + if (args->loc->source == OPTS_FILE) + error (0, 0, _("%s:%lu: location of the error"), args->loc->name, + (unsigned long) args->loc->line); + else if (args->loc->source == OPTS_ENVIRON) + error (0, 0, _("error parsing %s"), args->loc->name); + exit (EX_USAGE); default: return ARGP_ERR_UNKNOWN; @@ -2002,13 +2084,15 @@ parse_opt (int key, char *arg, struct argp_state *state) return 0; } +extern struct argp_child names_argp_children[]; + static struct argp argp = { options, parse_opt, N_("[FILE]..."), doc, - NULL, - NULL, + names_argp_children, + tar_help_filter, NULL }; @@ -2022,8 +2106,8 @@ usage (int status) /* Parse the options for tar. */ -static struct argp_option * -find_argp_option (struct argp_option *o, int letter) +static struct argp_option const * +find_argp_option_key (struct argp_option const *o, int key) { for (; !(o->name == NULL @@ -2031,46 +2115,141 @@ find_argp_option (struct argp_option *o, int letter) && o->arg == 0 && o->flags == 0 && o->doc == NULL); o++) - if (o->key == letter) + if (o->key == key) return o; return NULL; } +static struct argp_option const * +find_argp_option (struct argp *ap, int key) +{ + struct argp_option const *p = NULL; + struct argp_child const *child; + + p = find_argp_option_key (ap->options, key); + if (!p && ap->children) + { + for (child = ap->children; child->argp; child++) + { + p = find_argp_option_key (child->argp->options, key); + if (p) + break; + } + } + return p; +} + +static const char *tar_authors[] = { + "John Gilmore", + "Jay Fenlason", + NULL +}; + +/* Subcommand classes */ +#define SUBCL_READ 0x01 /* subcommand reads from the archive */ +#define SUBCL_WRITE 0x02 /* subcommand writes to the archive */ +#define SUBCL_UPDATE 0x04 /* subcommand updates existing archive */ +#define SUBCL_TEST 0x08 /* subcommand tests archive header or meta-info */ +#define SUBCL_OCCUR 0x10 /* subcommand allows the use of the occurrence + option */ + +static int subcommand_class[] = { + /* UNKNOWN_SUBCOMMAND */ 0, + /* APPEND_SUBCOMMAND */ SUBCL_WRITE|SUBCL_UPDATE, + /* CAT_SUBCOMMAND */ SUBCL_WRITE, + /* CREATE_SUBCOMMAND */ SUBCL_WRITE, + /* DELETE_SUBCOMMAND */ SUBCL_WRITE|SUBCL_UPDATE|SUBCL_OCCUR, + /* DIFF_SUBCOMMAND */ SUBCL_READ|SUBCL_OCCUR, + /* EXTRACT_SUBCOMMAND */ SUBCL_READ|SUBCL_OCCUR, + /* LIST_SUBCOMMAND */ SUBCL_READ|SUBCL_OCCUR, + /* UPDATE_SUBCOMMAND */ SUBCL_WRITE|SUBCL_UPDATE, + /* TEST_LABEL_SUBCOMMAND */ SUBCL_TEST +}; + +/* Return t if the subcommand_option is in class(es) f */ +#define IS_SUBCOMMAND_CLASS(f) (subcommand_class[subcommand_option] & (f)) + +static struct tar_args args; + +void +more_options (int argc, char **argv, struct option_locus *loc) +{ + int idx; + + args.loc = loc; + if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_EXIT, &idx, &args)) + abort (); /* shouldn't happen */ + if (loc->source == OPTS_ENVIRON && args.input_files) + USAGE_ERROR ((0, 0, _("non-option arguments in %s"), loc->name)); +} + +static void +parse_default_options (void) +{ + char *opts = getenv ("TAR_OPTIONS"); + struct wordsplit ws; + struct option_locus loc = { OPTS_ENVIRON, "TAR_OPTIONS", 0, 0 }; + + if (!opts) + return; + + ws.ws_offs = 1; + if (wordsplit (opts, &ws, WRDSF_DEFFLAGS|WRDSF_DOOFFS)) + FATAL_ERROR ((0, 0, _("cannot split TAR_OPTIONS: %s"), + wordsplit_strerror (&ws))); + if (ws.ws_wordc) + { + ws.ws_wordv[0] = (char*) program_name; + more_options (ws.ws_offs + ws.ws_wordc, ws.ws_wordv, &loc); + } + + wordsplit_free (&ws); +} + static void decode_options (int argc, char **argv) { int idx; - struct tar_args args; + struct option_locus loc = { OPTS_COMMAND_LINE, 0, 0, 0 }; + + argp_version_setup ("tar", tar_authors); /* Set some default option values. */ args.textual_date = NULL; - args.wildcards = default_wildcards; - args.matching_flags = 0; - args.include_anchored = EXCLUDE_ANCHORED; args.o_option = false; args.pax_option = false; 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; blocking_factor = DEFAULT_BLOCKING; record_size = DEFAULT_BLOCKING * BLOCKSIZE; excluded = new_exclude (); + hole_detection = HOLE_DETECTION_DEFAULT; + newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t); newer_mtime_option.tv_nsec = -1; + mtime_option.tv_sec = TYPE_MINIMUM (time_t); + mtime_option.tv_nsec = -1; recursion_option = FNM_LEADING_DIR; unquote_option = true; tar_sparse_major = 1; tar_sparse_minor = 0; - owner_option = -1; - group_option = -1; + savedir_sort_order = SAVEDIR_SORT_NONE; + + owner_option = -1; owner_name_option = NULL; + group_option = -1; group_name_option = NULL; check_device_option = true; - + + incremental_level = -1; + + seek_option = -1; + /* Convert old-style tar call by exploding option element and rearranging options accordingly. */ @@ -2101,17 +2280,17 @@ decode_options (int argc, char **argv) for (letter = *in++; *letter; letter++) { - struct argp_option *opt; + struct argp_option const *opt; buffer[1] = *letter; *out++ = xstrdup (buffer); - opt = find_argp_option (options, *letter); + opt = find_argp_option (&argp, *letter); if (opt && opt->arg) { if (in < argv + argc) *out++ = *in++; else - USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."), + USAGE_ERROR ((0, 0, _("Old option '%c' requires an argument."), *letter)); } } @@ -2129,14 +2308,12 @@ decode_options (int argc, char **argv) } /* Parse all options and non-options as they appear. */ + parse_default_options (); - prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv); - - if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP, - &idx, &args)) + args.loc = &loc; + if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, &args)) exit (TAREXIT_FAILURE); - /* Special handling for 'o' option: GNU tar used to say "output old format". @@ -2163,14 +2340,10 @@ decode_options (int argc, char **argv) /* Handle operands after any "--" argument. */ for (; idx < argc; idx++) { - name_add_name (argv[idx], MAKE_INCL_OPTIONS (&args)); + name_add_name (argv[idx]); args.input_files = true; } - /* Warn about implicit use of the wildcards in command line arguments. - See TODO */ - warn_regex_usage = args.wildcards == default_wildcards; - /* Derive option values and check option consistency. */ if (archive_format == DEFAULT_FORMAT) @@ -2194,24 +2367,14 @@ decode_options (int argc, char **argv) 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 (!IS_SUBCOMMAND_CLASS (SUBCL_OCCUR)) + { + if (option_set_in_cl (OC_OCCURRENCE)) + option_conflict_error ("--occurrence", + subcommand_string (subcommand_option)); + else + occurrence_option = 0; + } } if (archive_names == 0) @@ -2225,16 +2388,28 @@ decode_options (int argc, char **argv) archive_name_array[0] = DEFAULT_ARCHIVE; } - /* Allow multiple archives only with `-M'. */ + /* Allow multiple archives only with '-M'. */ if (archive_names > 1 && !multi_volume_option) USAGE_ERROR ((0, 0, - _("Multiple archive files require `-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"))); + && TIME_OPTION_INITIALIZED (newer_mtime_option)) + { + struct option_locus *listed_loc = optloc_lookup (OC_LISTED_INCREMENTAL); + struct option_locus *newer_loc = optloc_lookup (OC_NEWER); + if (optloc_eq (listed_loc, newer_loc)) + option_conflict_error ("--listed-incremental", "--newer"); + else if (listed_loc->source == OPTS_COMMAND_LINE) + listed_incremental_option = NULL; + else + memset (&newer_mtime_option, 0, sizeof (newer_mtime_option)); + } + + if (incremental_level != -1 && !listed_incremental_option) + WARN ((0, 0, + _("--level is meaningless without --listed-incremental"))); if (volume_label_option) { @@ -2267,37 +2442,129 @@ decode_options (int argc, char **argv) USAGE_ERROR ((0, 0, _("Cannot verify multi-volume archives"))); if (use_compress_program_option) USAGE_ERROR ((0, 0, _("Cannot verify compressed archives"))); + if (!IS_SUBCOMMAND_CLASS (SUBCL_WRITE)) + { + if (option_set_in_cl (OC_VERIFY)) + option_conflict_error ("--verify", + subcommand_string (subcommand_option)); + else + verify_option = false; + } } if (use_compress_program_option) { if (multi_volume_option) USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives"))); - if (subcommand_option == UPDATE_SUBCOMMAND - || subcommand_option == APPEND_SUBCOMMAND - || subcommand_option == DELETE_SUBCOMMAND) + if (IS_SUBCOMMAND_CLASS (SUBCL_UPDATE)) USAGE_ERROR ((0, 0, _("Cannot update compressed archives"))); if (subcommand_option == CAT_SUBCOMMAND) USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives"))); } + if (set_mtime_option == CLAMP_MTIME) + { + if (!TIME_OPTION_INITIALIZED (mtime_option)) + USAGE_ERROR ((0, 0, + _("--clamp-mtime needs a date specified using --mtime"))); + } + /* 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)) + && !IS_SUBCOMMAND_CLASS (SUBCL_READ)) USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives"))); + /* star creates non-POSIX typed archives with xattr support, so allow the + extra headers when reading */ + if ((acls_option > 0) + && archive_format != POSIX_FORMAT + && !IS_SUBCOMMAND_CLASS (SUBCL_READ)) + USAGE_ERROR ((0, 0, _("--acls can be used only on POSIX archives"))); + + if ((selinux_context_option > 0) + && archive_format != POSIX_FORMAT + && !IS_SUBCOMMAND_CLASS (SUBCL_READ)) + USAGE_ERROR ((0, 0, _("--selinux can be used only on POSIX archives"))); + + if ((xattrs_option > 0) + && archive_format != POSIX_FORMAT + && !IS_SUBCOMMAND_CLASS (SUBCL_READ)) + USAGE_ERROR ((0, 0, _("--xattrs can be used only on POSIX archives"))); + + if (starting_file_option && !IS_SUBCOMMAND_CLASS (SUBCL_READ)) + { + if (option_set_in_cl (OC_STARTING_FILE)) + option_conflict_error ("--starting-file", + subcommand_string (subcommand_option)); + else + starting_file_option = false; + } + + if (same_order_option && !IS_SUBCOMMAND_CLASS (SUBCL_READ)) + { + if (option_set_in_cl (OC_SAME_ORDER)) + option_conflict_error ("--same-order", + subcommand_string (subcommand_option)); + else + same_order_option = false; + } + + if (one_top_level_option) + { + char *base; + + if (absolute_names_option) + { + struct option_locus *one_top_level_loc = + optloc_lookup (OC_ONE_TOP_LEVEL); + struct option_locus *absolute_names_loc = + optloc_lookup (OC_ABSOLUTE_NAMES); + + if (optloc_eq (one_top_level_loc, absolute_names_loc)) + option_conflict_error ("--one-top-level", "--absolute-names"); + else if (one_top_level_loc->source == OPTS_COMMAND_LINE) + absolute_names_option = false; + else + one_top_level_option = false; + } + + if (one_top_level_option && !one_top_level_dir) + { + /* If the user wants to guarantee that everything is under one + directory, determine its name now and let it be created later. */ + base = base_name (archive_name_array[0]); + one_top_level_dir = strip_compression_suffix (base); + free (base); + + if (!one_top_level_dir) + USAGE_ERROR ((0, 0, + _("Cannot deduce top-level directory name; " + "please set it explicitly with --one-top-level=DIR"))); + } + } + /* If ready to unlink hierarchies, so we are for simpler files. */ 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. */ @@ -2307,7 +2574,24 @@ decode_options (int argc, char **argv) else if (utc_option) verbose_option = 2; - /* Forbid using -c with no input files whatsoever. Check that `-f -', + 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) + { + struct option_locus *preserve_order_loc = optloc_lookup (OC_SAME_ORDER); + struct option_locus *listed_incremental_loc = + optloc_lookup (OC_LISTED_INCREMENTAL); + + if (optloc_eq (preserve_order_loc, listed_incremental_loc)) + option_conflict_error ("--preserve-order", "--listed-incremental"); + else if (preserve_order_loc->source == OPTS_COMMAND_LINE) + listed_incremental_option = false; + else + same_order_option = false; + } + + /* Forbid using -c with no input files whatsoever. Check that '-f -', explicit or implied, is used correctly. */ switch (subcommand_option) @@ -2318,13 +2602,14 @@ decode_options (int argc, char **argv) _("Cowardly refusing to create an empty archive"))); if (args.compress_autodetect && archive_names && strcmp (archive_name_array[0], "-")) - set_comression_program_by_suffix (archive_name_array[0], - use_compress_program_option); + 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++) @@ -2340,7 +2625,7 @@ decode_options (int argc, char **argv) archive_name_cursor++) if (!strcmp (*archive_name_cursor, "-")) USAGE_ERROR ((0, 0, - _("Options `-Aru' are incompatible with `-f -'"))); + _("Options '-Aru' are incompatible with '-f -'"))); default: break; @@ -2351,7 +2636,7 @@ decode_options (int argc, char **argv) { stdlis = fopen (index_file_name, "w"); if (! stdlis) - open_error (index_file_name); + open_fatal (index_file_name); } else stdlis = to_stdout_option ? stderr : stdout; @@ -2373,9 +2658,8 @@ decode_options (int argc, char **argv) } checkpoint_finish_compile (); - - if (verbose_option) - report_textual_dates (&args); + + report_textual_dates (&args); } @@ -2386,7 +2670,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); @@ -2394,7 +2678,8 @@ main (int argc, char **argv) exit_failure = TAREXIT_FAILURE; exit_status = TAREXIT_SUCCESS; - filename_terminator = '\n'; + error_hook = checkpoint_flush_actions; + set_quoting_style (0, DEFAULT_QUOTING_STYLE); /* Make sure we have first three descriptors available */ @@ -2407,12 +2692,11 @@ main (int argc, char **argv) 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 + + /* Try to disable the ability to unlink a directory. */ + priv_set_remove_linkdir (); /* Decode options. */ @@ -2429,7 +2713,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', '--delete' or '--test-label' options"))); case CAT_SUBCOMMAND: case UPDATE_SUBCOMMAND: @@ -2463,8 +2747,13 @@ main (int argc, char **argv) diff_init (); read_and (diff_archive); break; + + case TEST_LABEL_SUBCOMMAND: + test_archive_label (); } + checkpoint_finish (); + if (totals_option) print_total_stats (); @@ -2477,15 +2766,16 @@ main (int argc, char **argv) /* Dispose of allocated memory, and return. */ free (archive_name_array); + xattrs_clear_setup (); 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; } @@ -2496,17 +2786,44 @@ 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); + xheader_xattr_free (st->xattr_map, st->xattr_map_size); free (st->orig_file_name); free (st->file_name); free (st->link_name); free (st->uname); free (st->gname); + free (st->cntx_name); + free (st->acls_a_ptr); + free (st->acls_d_ptr); free (st->sparse_map); free (st->dumpdir); xheader_destroy (&st->xhdr); + info_free_exclist (st); memset (st, 0, sizeof (*st)); } @@ -2523,3 +2840,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; +}