X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Fnames.c;h=037b869d7596c9c44476d28aca72acf46c8851c6;hb=HEAD;hp=1146020b2458f5a07fa5c534ea1511e596f3b703;hpb=22f1eb8bc17e5be72dd23d42d6aaa60196ac22e6;p=debian%2Ftar diff --git a/src/names.c b/src/names.c index 1146020b..037b869d 100644 --- a/src/names.c +++ b/src/names.c @@ -1,7 +1,7 @@ /* Various processing of names. - Copyright (C) 1988, 1992, 1994, 1996, 1997, 1998, 1999, 2000, 2001, - 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc. + Copyright 1988, 1992, 1994, 1996-2001, 2003-2007, 2009, 2013-2016 + Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the @@ -14,27 +14,414 @@ 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 #include #include #include +#include +#include #include "common.h" -/* User and group names. */ +static void name_add_option (int option, const char *arg); +static void name_add_dir (const char *name); +static void name_add_file (const char *name); + +enum + { + EXCLUDE_BACKUPS_OPTION = 256, + EXCLUDE_CACHES_OPTION, + EXCLUDE_CACHES_UNDER_OPTION, + EXCLUDE_CACHES_ALL_OPTION, + EXCLUDE_OPTION, + EXCLUDE_IGNORE_OPTION, + EXCLUDE_IGNORE_RECURSIVE_OPTION, + EXCLUDE_TAG_OPTION, + EXCLUDE_TAG_UNDER_OPTION, + EXCLUDE_TAG_ALL_OPTION, + EXCLUDE_VCS_OPTION, + EXCLUDE_VCS_IGNORES_OPTION, + IGNORE_CASE_OPTION, + NO_IGNORE_CASE_OPTION, + ANCHORED_OPTION, + NO_ANCHORED_OPTION, + RECURSION_OPTION, + NO_RECURSION_OPTION, + UNQUOTE_OPTION, + NO_UNQUOTE_OPTION, + NO_VERBATIM_FILES_FROM_OPTION, + NO_WILDCARDS_MATCH_SLASH_OPTION, + NO_WILDCARDS_OPTION, + NULL_OPTION, + NO_NULL_OPTION, + VERBATIM_FILES_FROM_OPTION, + WILDCARDS_MATCH_SLASH_OPTION, + WILDCARDS_OPTION + }; + +static struct argp_option names_options[] = { +#define GRID 100 + {NULL, 0, NULL, 0, + N_("Local file name 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; implies --verbatim-files-from"), + 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 input file or member names (default)"), GRID+1 }, + {"no-unquote", NO_UNQUOTE_OPTION, 0, 0, + N_("do not unquote input file or member names"), GRID+1 }, + {"verbatim-files-from", VERBATIM_FILES_FROM_OPTION, 0, 0, + N_("-T reads file names verbatim (no option handling)"), GRID+1 }, + {"no-verbatim-files-from", NO_VERBATIM_FILES_FROM_OPTION, 0, 0, + N_("-T treats file names starting with dash as options (default)"), + 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-ignore", EXCLUDE_IGNORE_OPTION, N_("FILE"), 0, + N_("read exclude patterns for each directory from FILE, if it exists"), + GRID+1 }, + {"exclude-ignore-recursive", EXCLUDE_IGNORE_RECURSIVE_OPTION, N_("FILE"), 0, + N_("read exclude patterns for each directory and its subdirectories " + "from FILE, if it exists"), 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-vcs-ignores", EXCLUDE_VCS_IGNORES_OPTION, NULL, 0, + N_("read exclude patterns from the VCS ignore files"), GRID+1 }, + {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0, + N_("exclude backup and lock files"), GRID+1 }, + {"recursion", RECURSION_OPTION, 0, 0, + N_("recurse into directories (default)"), GRID+1 }, + {"no-recursion", NO_RECURSION_OPTION, 0, 0, + N_("avoid descending automatically in directories"), GRID+1 }, +#undef GRID + +#define GRID 120 + {NULL, 0, NULL, 0, + N_("File name matching options (affect both exclude and include patterns):"), + GRID }, + {"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 }, + {"ignore-case", IGNORE_CASE_OPTION, 0, 0, + N_("ignore case"), 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 }, + {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0, + N_("wildcards match '/' (default for exclusion)"), GRID+1 }, + {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0, + N_("wildcards do not match '/'"), GRID+1 }, +#undef GRID + + {NULL} +}; -struct group *getgrnam (); -struct passwd *getpwnam (); -#if ! HAVE_DECL_GETPWUID -struct passwd *getpwuid (); -#endif -#if ! HAVE_DECL_GETGRGID -struct group *getgrgid (); -#endif +static bool +is_file_selection_option (int key) +{ + struct argp_option *p; + + for (p = names_options; + !(p->name == NULL && p->key == 0 && p->doc == NULL); p++) + if (p->key == key) + return true; + return false; +} + +/* Either NL or NUL, as decided by the --null option. */ +static char filename_terminator = '\n'; +/* Treat file names read from -T input verbatim */ +static bool verbatim_files_from_option; + +static error_t +names_parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'C': + name_add_dir (arg); + break; + + case 'T': + name_add_file (arg); + /* Indicate we've been given -T option. This is for backward + compatibility only, so that `tar cfT archive /dev/null will + succeed */ + files_from_option = true; + break; + + default: + if (is_file_selection_option (key)) + name_add_option (key, arg); + else + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Wildcard matching settings */ +enum wildcards + { + default_wildcards, /* For exclusion == enable_wildcards, + for inclusion == disable_wildcards */ + disable_wildcards, + enable_wildcards + }; + +static enum wildcards wildcards = default_wildcards; + /* Wildcard settings (--wildcards/--no-wildcards) */ +static int matching_flags = 0; + /* exclude_fnmatch options */ +static int include_anchored = EXCLUDE_ANCHORED; + /* Pattern anchoring options used for file inclusion */ + +#define EXCLUDE_OPTIONS \ + (((wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0) \ + | matching_flags \ + | recursion_option) + +#define INCLUDE_OPTIONS \ + (((wildcards == enable_wildcards) ? EXCLUDE_WILDCARDS : 0) \ + | include_anchored \ + | matching_flags \ + | recursion_option) + +static char const * const vcs_file_table[] = { + /* CVS: */ + "CVS", + ".cvsignore", + /* RCS: */ + "RCS", + /* SCCS: */ + "SCCS", + /* SVN: */ + ".svn", + /* git: */ + ".git", + ".gitignore", + ".gitattributes", + ".gitmodules", + /* Arch: */ + ".arch-ids", + "{arch}", + "=RELEASE-ID", + "=meta-update", + "=update", + /* Bazaar */ + ".bzr", + ".bzrignore", + ".bzrtags", + /* Mercurial */ + ".hg", + ".hgignore", + ".hgtags", + /* darcs */ + "_darcs", + NULL +}; + +static char const * const backup_file_table[] = { + ".#*", + "*~", + "#*#", + NULL +}; + +static void +add_exclude_array (char const * const * fv, int opts) +{ + int i; + + for (i = 0; fv[i]; i++) + add_exclude (excluded, fv[i], opts); +} + +static void +handle_file_selection_option (int key, const char *arg) +{ + switch (key) + { + case EXCLUDE_BACKUPS_OPTION: + add_exclude_array (backup_file_table, EXCLUDE_WILDCARDS); + break; + + case EXCLUDE_OPTION: + add_exclude (excluded, arg, EXCLUDE_OPTIONS); + 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_IGNORE_OPTION: + excfile_add (arg, EXCL_NON_RECURSIVE); + break; + + case EXCLUDE_IGNORE_RECURSIVE_OPTION: + excfile_add (arg, EXCL_RECURSIVE); + 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: + add_exclude_array (vcs_file_table, 0); + break; + + case EXCLUDE_VCS_IGNORES_OPTION: + exclude_vcs_ignores (); + break; + + case RECURSION_OPTION: + recursion_option = FNM_LEADING_DIR; + break; + + case NO_RECURSION_OPTION: + recursion_option = 0; + break; + + case UNQUOTE_OPTION: + unquote_option = true; + break; + + case NO_UNQUOTE_OPTION: + unquote_option = false; + break; + + case NULL_OPTION: + filename_terminator = '\0'; + verbatim_files_from_option = true; + break; + + case NO_NULL_OPTION: + filename_terminator = '\n'; + verbatim_files_from_option = false; + break; + + case 'X': + if (add_exclude_file (add_exclude, excluded, arg, EXCLUDE_OPTIONS, '\n') + != 0) + { + int e = errno; + FATAL_ERROR ((0, e, "%s", quotearg_colon (arg))); + } + break; + + case ANCHORED_OPTION: + matching_flags |= EXCLUDE_ANCHORED; + break; + + case NO_ANCHORED_OPTION: + include_anchored = 0; /* Clear the default for comman line args */ + matching_flags &= ~ EXCLUDE_ANCHORED; + break; + + case IGNORE_CASE_OPTION: + matching_flags |= FNM_CASEFOLD; + break; + + case NO_IGNORE_CASE_OPTION: + matching_flags &= ~ FNM_CASEFOLD; + break; + + case WILDCARDS_OPTION: + wildcards = enable_wildcards; + break; + + case NO_WILDCARDS_OPTION: + wildcards = disable_wildcards; + break; + + case WILDCARDS_MATCH_SLASH_OPTION: + matching_flags &= ~ FNM_FILE_NAME; + break; + + case NO_WILDCARDS_MATCH_SLASH_OPTION: + matching_flags |= FNM_FILE_NAME; + break; + + case VERBATIM_FILES_FROM_OPTION: + verbatim_files_from_option = true; + break; + + case NO_VERBATIM_FILES_FROM_OPTION: + verbatim_files_from_option = false; + break; + + default: + FATAL_ERROR ((0, 0, "unhandled positional option %d", key)); + } +} + +static struct argp names_argp = { + names_options, + names_parse_opt, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +struct argp_child names_argp_children[] = { + { &names_argp, 0, "", 0 }, + { NULL } +}; + +/* User and group names. */ /* Make sure you link with the proper libraries if you are running the Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS. @@ -56,8 +443,6 @@ static char *cached_no_such_gname; static uid_t cached_no_such_uid; static gid_t cached_no_such_gid; -static void register_individual_file (char const *name); - /* Given UID, find the corresponding UNAME. */ void uid_to_uname (uid_t uid, char **uname) @@ -179,7 +564,7 @@ gname_to_gid (char const *gname, gid_t *gidp) } -struct name * +static struct name * make_name (const char *file_name) { struct name *p = xzalloc (sizeof (*p)); @@ -190,7 +575,7 @@ make_name (const char *file_name) return p; } -void +static void free_name (struct name *p) { if (p) @@ -207,83 +592,135 @@ free_name (struct name *p) static struct name *namelist; /* first name in list, if any */ static struct name *nametail; /* end of name list */ -/* File name arguments are processed in two stages: first a - name_array (see below) is filled, then the names from it +/* File name arguments are processed in two stages: first a + name element list (see below) is filled, then the names from it are moved into the namelist. This awkward process is needed only to implement --same-order option, which is meant to help process large archives on machines with limited memory. With this option on, namelist contains at most one entry, which diminishes the memory consumption. - + However, I very much doubt if we still need this -- Sergey */ -/* A name_array element contains entries of three types: */ +/* A name_list element contains entries of three types: */ -#define NELT_NAME 0 /* File name */ -#define NELT_CHDIR 1 /* Change directory request */ -#define NELT_FMASK 2 /* Change fnmatch options request */ +enum nelt_type + { + NELT_NAME, /* File name */ + NELT_CHDIR, /* Change directory request */ + NELT_FILE, /* Read file names from that file */ + NELT_NOOP, /* No operation */ + NELT_OPTION /* Filename-selection option */ + }; struct name_elt /* A name_array element. */ { - char type; /* Element type, see NELT_* constants above */ + struct name_elt *next, *prev; + enum nelt_type type; /* Element type, see NELT_* constants above */ union { const char *name; /* File or directory name */ - int matching_flags;/* fnmatch options if type == NELT_FMASK */ + struct /* File, if type == NELT_FILE */ + { + const char *name;/* File name */ + size_t line; /* Input line number */ + int term; /* File name terminator in the list */ + bool verbatim; /* Verbatim handling of file names: no white-space + trimming, no option processing */ + FILE *fp; + } file; + struct + { + int option; + char const *arg; + } opt; /* NELT_OPTION */ } v; }; -static struct name_elt *name_array; /* store an array of names */ -static size_t allocated_names; /* how big is the array? */ -static size_t names; /* how many entries does it have? */ -static size_t name_index; /* how many of the entries have we scanned? */ +static struct name_elt *name_head; /* store a list of names */ +size_t name_count; /* how many of the entries are names? */ -/* Check the size of name_array, reallocating it as necessary. */ -static void -check_name_alloc () +static struct name_elt * +name_elt_alloc (void) { - if (names == allocated_names) + struct name_elt *elt; + + elt = xmalloc (sizeof (*elt)); + if (!name_head) { - if (allocated_names == 0) - allocated_names = 10; /* Set initial allocation */ - name_array = x2nrealloc (name_array, &allocated_names, - sizeof (name_array[0])); + name_head = elt; + name_head->prev = name_head->next = NULL; + name_head->type = NELT_NOOP; + elt = xmalloc (sizeof (*elt)); } + + elt->prev = name_head->prev; + if (name_head->prev) + name_head->prev->next = elt; + elt->next = name_head; + name_head->prev = elt; + return elt; } -/* Add to name_array the file NAME with fnmatch options MATCHING_FLAGS */ +static void +name_list_adjust (void) +{ + if (name_head) + while (name_head->prev) + name_head = name_head->prev; +} + +static void +name_list_advance (void) +{ + struct name_elt *elt = name_head; + name_head = elt->next; + if (name_head) + name_head->prev = NULL; + free (elt); +} + + +/* Add to name_array the file NAME with fnmatch options MATFLAGS */ void -name_add_name (const char *name, int matching_flags) +name_add_name (const char *name) { - static int prev_flags = 0; /* FIXME: Or EXCLUDE_ANCHORED? */ - struct name_elt *ep; + struct name_elt *ep = name_elt_alloc (); - check_name_alloc (); - ep = &name_array[names++]; - if (prev_flags != matching_flags) - { - ep->type = NELT_FMASK; - ep->v.matching_flags = matching_flags; - prev_flags = matching_flags; - check_name_alloc (); - ep = &name_array[names++]; - } ep->type = NELT_NAME; ep->v.name = name; + name_count++; +} + +static void +name_add_option (int option, const char *arg) +{ + struct name_elt *elt = name_elt_alloc (); + elt->type = NELT_OPTION; + elt->v.opt.option = option; + elt->v.opt.arg = arg; } /* Add to name_array a chdir request for the directory NAME */ -void +static void name_add_dir (const char *name) { - struct name_elt *ep; - check_name_alloc (); - ep = &name_array[names++]; + struct name_elt *ep = name_elt_alloc (); ep->type = NELT_CHDIR; ep->v.name = name; -} +} +static void +name_add_file (const char *name) +{ + struct name_elt *ep = name_elt_alloc (); + + ep->type = NELT_FILE; + ep->v.file.name = name; + ep->v.file.line = 0; + ep->v.file.fp = NULL; +} /* Names from external name file. */ @@ -297,81 +734,282 @@ name_init (void) { name_buffer = xmalloc (NAME_FIELD_SIZE + 2); name_buffer_length = NAME_FIELD_SIZE; + name_list_adjust (); } void name_term (void) { free (name_buffer); - free (name_array); } + +/* Prevent recursive inclusion of the same file */ +struct file_id_list +{ + struct file_id_list *next; + ino_t ino; + dev_t dev; + const char *from_file; +}; -static int matching_flags; /* exclude_fnmatch options */ +static struct file_id_list *file_id_list; -/* Get the next NELT_NAME element from name_array. Result is in - static storage and can't be relied upon across two calls. +/* Return the name of the file from which the file names and options + are being read. +*/ +static const char * +file_list_name (void) +{ + struct name_elt *elt; - If CHANGE_DIRS is true, treat any entries of type NELT_CHDIR as - the request to change to the given directory. - - Entries of type NELT_FMASK cause updates of the matching_flags - value. */ -struct name_elt * -name_next_elt (int change_dirs) + for (elt = name_head; elt; elt = elt->next) + if (elt->type == NELT_FILE && elt->v.file.fp) + return elt->v.file.name; + return _("command line"); +} + +static int +add_file_id (const char *filename) { - static struct name_elt entry; - const char *source; - char *cursor; + struct file_id_list *p; + struct stat st; + const char *reading_from; + + if (stat (filename, &st)) + stat_fatal (filename); + reading_from = file_list_name (); + for (p = file_id_list; p; p = p->next) + if (p->ino == st.st_ino && p->dev == st.st_dev) + { + int oldc = set_char_quoting (NULL, ':', 1); + ERROR ((0, 0, + _("%s: file list requested from %s already read from %s"), + quotearg_n (0, filename), + reading_from, p->from_file)); + set_char_quoting (NULL, ':', oldc); + return 1; + } + p = xmalloc (sizeof *p); + p->next = file_id_list; + p->ino = st.st_ino; + p->dev = st.st_dev; + p->from_file = reading_from; + file_id_list = p; + return 0; +} + +/* Chop trailing slashes. */ +static void +chopslash (char *str) +{ + char *p = str + strlen (str) - 1; + while (p > str && ISSLASH (*p)) + *p-- = '\0'; +} + +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 */ + }; + +/* 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 (struct name_elt *ent) +{ + int c; + size_t counter = 0; + FILE *fp = ent->v.file.fp; + int term = ent->v.file.term; - while (name_index != names) + ++ent->v.file.line; + for (c = getc (fp); c != EOF && c != term; c = getc (fp)) { - struct name_elt *ep; - size_t source_len; - - ep = &name_array[name_index++]; - if (ep->type == NELT_FMASK) + if (counter == name_buffer_length) + name_buffer = x2realloc (name_buffer, &name_buffer_length); + name_buffer[counter++] = c; + if (c == 0) { - matching_flags = ep->v.matching_flags; - continue; + /* We have read a zero separator. The file possibly is + zero-separated */ + return file_list_zero; } - - source = ep->v.name; - source_len = strlen (source); - if (name_buffer_length < source_len) + } + + if (counter == 0 && c != EOF) + return file_list_skip; + + if (counter == name_buffer_length) + name_buffer = x2realloc (name_buffer, &name_buffer_length); + name_buffer[counter] = 0; + chopslash (name_buffer); + return (counter == 0 && c == EOF) ? file_list_end : file_list_success; +} + +static int +handle_option (const char *str, struct name_elt const *ent) +{ + struct wordsplit ws; + int i; + struct option_locus loc; + + while (*str && isspace (*str)) + ++str; + if (*str != '-') + return 1; + + ws.ws_offs = 1; + if (wordsplit (str, &ws, WRDSF_DEFFLAGS|WRDSF_DOOFFS)) + FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"), + str, wordsplit_strerror (&ws))); + ws.ws_wordv[0] = (char *) program_name; + loc.source = OPTS_FILE; + loc.name = ent->v.file.name; + loc.line = ent->v.file.line; + more_options (ws.ws_wordc+ws.ws_offs, ws.ws_wordv, &loc); + for (i = 0; i < ws.ws_wordc+ws.ws_offs; i++) + ws.ws_wordv[i] = NULL; + + wordsplit_free (&ws); + return 0; +} + +static int +read_next_name (struct name_elt *ent, struct name_elt *ret) +{ + if (!ent->v.file.fp) + { + if (!strcmp (ent->v.file.name, "-")) { - do + request_stdin ("-T"); + ent->v.file.fp = stdin; + } + else + { + if (add_file_id (ent->v.file.name)) { - name_buffer_length *= 2; - if (! name_buffer_length) - xalloc_die (); + name_list_advance (); + return 1; } - while (name_buffer_length < source_len); - - free (name_buffer); - name_buffer = xmalloc (name_buffer_length + 2); + if ((ent->v.file.fp = fopen (ent->v.file.name, "r")) == NULL) + open_fatal (ent->v.file.name); } - strcpy (name_buffer, source); + ent->v.file.term = filename_terminator; + ent->v.file.verbatim = verbatim_files_from_option; + } - /* Zap trailing slashes. */ + while (1) + { + switch (read_name_from_file (ent)) + { + case file_list_skip: + continue; - cursor = name_buffer + strlen (name_buffer) - 1; - while (cursor > name_buffer && ISSLASH (*cursor)) - *cursor-- = '\0'; + case file_list_zero: + WARNOPT (WARN_FILENAME_WITH_NULS, + (0, 0, N_("%s: file name read contains nul character"), + quotearg_colon (ent->v.file.name))); + ent->v.file.term = 0; + /* fall through */ + case file_list_success: + if (unquote_option) + unquote_string (name_buffer); + if (!ent->v.file.verbatim && handle_option (name_buffer, ent) == 0) + { + name_list_adjust (); + return 1; + } + ret->type = NELT_NAME; + ret->v.name = name_buffer; + return 0; - if (change_dirs && ep->type == NELT_CHDIR) + case file_list_end: + if (strcmp (ent->v.file.name, "-")) + fclose (ent->v.file.fp); + ent->v.file.fp = NULL; + name_list_advance (); + return 1; + } + } +} + +static void +copy_name (struct name_elt *ep) +{ + const char *source; + size_t source_len; + + source = ep->v.name; + source_len = strlen (source); + if (name_buffer_length < source_len) + { + do { - if (chdir (name_buffer) < 0) - chdir_fatal (name_buffer); + name_buffer_length *= 2; + if (! name_buffer_length) + xalloc_die (); } - else + while (name_buffer_length < source_len); + + free (name_buffer); + name_buffer = xmalloc(name_buffer_length + 2); + } + strcpy (name_buffer, source); + chopslash (name_buffer); +} + + +/* Get the next NELT_NAME element from name_array. Result is in + static storage and can't be relied upon across two calls. + + If CHANGE_DIRS is true, treat any entries of type NELT_CHDIR as + the request to change to the given directory. + +*/ +static struct name_elt * +name_next_elt (int change_dirs) +{ + static struct name_elt entry; + struct name_elt *ep; + + while ((ep = name_head) != NULL) + { + switch (ep->type) { + case NELT_NOOP: + name_list_advance (); + break; + + case NELT_FILE: + if (read_next_name (ep, &entry) == 0) + return &entry; + continue; + + case NELT_CHDIR: + if (change_dirs) + { + chdir_do (chdir_arg (xstrdup (ep->v.name))); + name_list_advance (); + break; + } + /* fall through */ + case NELT_NAME: + copy_name (ep); if (unquote_option) unquote_string (name_buffer); - if (incremental_option) - register_individual_file (name_buffer); entry.type = ep->type; entry.v.name = name_buffer; + name_list_advance (); return &entry; + + case NELT_OPTION: + handle_file_selection_option (ep->v.opt.option, ep->v.opt.arg); + name_list_advance (); + continue; } } @@ -417,11 +1055,11 @@ name_gather (void) buffer->change_dir = change_dir; buffer->next = 0; buffer->found_count = 0; - buffer->matching_flags = matching_flags; + buffer->matching_flags = INCLUDE_OPTIONS; buffer->directory = NULL; buffer->parent = NULL; buffer->cmdline = true; - + namelist = nametail = buffer; } else if (change_dir) @@ -459,7 +1097,7 @@ addname (char const *string, int change_dir, bool cmdline, struct name *parent) name->prev = nametail; name->next = NULL; name->found_count = 0; - name->matching_flags = matching_flags; + name->matching_flags = INCLUDE_OPTIONS; name->change_dir = change_dir; name->directory = NULL; name->parent = parent; @@ -519,7 +1157,7 @@ name_match (const char *file_name) if (!cursor) return true; - + if (cursor->name[0] == 0) { chdir_do (cursor->change_dir); @@ -589,12 +1227,15 @@ all_names_found (struct tar_stat_info *p) return true; } -static void +static int regex_usage_warning (const char *name) { static int warned_once = 0; - if (warn_regex_usage && fnmatch_pattern_has_wildcards (name, 0)) + /* Warn about implicit use of the wildcards in command line arguments. + (Default for tar prior to 1.15.91, but changed afterwards) */ + if (wildcards == default_wildcards + && fnmatch_pattern_has_wildcards (name, 0)) { warned_once = 1; WARN ((0, 0, @@ -603,6 +1244,7 @@ regex_usage_warning (const char *name) _("Use --wildcards to enable pattern matching," " or --no-wildcards to suppress this warning"))); } + return warned_once; } /* Print the names of things in the namelist that were not matched. */ @@ -615,12 +1257,11 @@ names_notfound (void) if (!WASFOUND (cursor) && cursor->name[0]) { regex_usage_warning (cursor->name); - if (cursor->found_count == 0) - ERROR ((0, 0, _("%s: Not found in archive"), - quotearg_colon (cursor->name))); - else - ERROR ((0, 0, _("%s: Required occurrence not found in archive"), - quotearg_colon (cursor->name))); + ERROR ((0, 0, + (cursor->found_count == 0) ? + _("%s: Not found in archive") : + _("%s: Required occurrence not found in archive"), + quotearg_colon (cursor->name))); } /* Don't bother freeing the name list; we're about to exit. */ @@ -639,14 +1280,50 @@ names_notfound (void) } } } + +void +label_notfound (void) +{ + struct name const *cursor; + + if (!namelist) + return; + + for (cursor = namelist; cursor; cursor = cursor->next) + if (WASFOUND (cursor)) + return; + + if (verbose_option) + error (0, 0, _("Archive label mismatch")); + set_exit_status (TAREXIT_DIFFERS); + + for (cursor = namelist; cursor; cursor = cursor->next) + { + if (regex_usage_warning (cursor->name)) + break; + } + + /* Don't bother freeing the name list; we're about to exit. */ + namelist = NULL; + nametail = NULL; + + if (same_order_option) + { + const char *name; + + while ((name = name_next (1)) != NULL + && regex_usage_warning (name) == 0) + ; + } +} /* Sorting name lists. */ /* Sort *singly* linked LIST of names, of given LENGTH, using COMPARE to order names. Return the sorted list. Note that after calling - this function, the `prev' links in list elements are messed up. - - Apart from the type `struct name' and the definition of SUCCESSOR, + this function, the 'prev' links in list elements are messed up. + + Apart from the type 'struct name' and the definition of SUCCESSOR, this is a generic list-sorting function, but it's too painful to make it both generic and portable in C. */ @@ -752,17 +1429,15 @@ compare_names (struct name const *n1, struct name const *n2) } -/* Add all the dirs under NAME, which names a directory, to the namelist. - If any of the files is a directory, recurse on the subdirectory. - DEVICE is the device not to leave, if the -l option is specified. - CMDLINE is true, if the NAME appeared on the command line. */ +/* Add all the dirs under ST to the namelist NAME, descending the + directory hierarchy recursively. */ static void -add_hierarchy_to_namelist (struct name *name, dev_t device, bool cmdline) +add_hierarchy_to_namelist (struct tar_stat_info *st, struct name *name) { const char *buffer; - - name_fill_directory (name, device, cmdline); + + name->directory = scan_directory (st); buffer = directory_contents (name->directory); if (buffer) { @@ -790,6 +1465,8 @@ add_hierarchy_to_namelist (struct name *name, dev_t device, bool cmdline) if (*string == 'D') { struct name *np; + struct tar_stat_info subdir; + int subfd; if (allocated_length <= name_length + string_length) { @@ -810,7 +1487,38 @@ add_hierarchy_to_namelist (struct name *name, dev_t device, bool cmdline) else child_tail->sibling = np; child_tail = np; - add_hierarchy_to_namelist (np, device, false); + + tar_stat_init (&subdir); + subdir.parent = st; + if (st->fd < 0) + { + subfd = -1; + errno = - st->fd; + } + else + subfd = subfile_open (st, string + 1, + open_read_flags | O_DIRECTORY); + if (subfd < 0) + open_diag (namebuf); + else + { + subdir.fd = subfd; + if (fstat (subfd, &subdir.stat) != 0) + stat_diag (namebuf); + else if (! (O_DIRECTORY || S_ISDIR (subdir.stat.st_mode))) + { + errno = ENOTDIR; + open_diag (namebuf); + } + else + { + subdir.orig_file_name = xstrdup (namebuf); + add_hierarchy_to_namelist (&subdir, np); + restore_parent_fd (&subdir); + } + } + + tar_stat_destroy (&subdir); } } @@ -838,7 +1546,7 @@ name_compare (void const *entry1, void const *entry2) } -/* Rebase `name' member of CHILD and all its siblings to +/* Rebase 'name' member of CHILD and all its siblings to the new PARENT. */ static void rebase_child_list (struct name *child, struct name *parent) @@ -846,7 +1554,7 @@ rebase_child_list (struct name *child, struct name *parent) size_t old_prefix_len = child->parent->length; size_t new_prefix_len = parent->length; char *new_prefix = parent->name; - + for (; child; child = child->sibling) { size_t size = child->length - old_prefix_len + new_prefix_len; @@ -858,7 +1566,7 @@ rebase_child_list (struct name *child, struct name *parent) child->length = size; rebase_directory (child->directory, - child->parent->name, old_prefix_len, + child->parent->name, old_prefix_len, new_prefix, new_prefix_len); } } @@ -871,11 +1579,10 @@ void collect_and_sort_names (void) { struct name *name; - struct name *next_name, *prev_name; + struct name *next_name, *prev_name = NULL; int num_names; - struct stat statbuf; Hash_table *nametab; - + name_gather (); if (!namelist) @@ -903,10 +1610,12 @@ collect_and_sort_names (void) read_directory_file (); } - + num_names = 0; for (name = namelist; name; name = name->next, num_names++) { + struct tar_stat_info st; + if (name->found_count || name->directory) continue; if (name->matching_flags & EXCLUDE_WILDCARDS) @@ -918,28 +1627,44 @@ collect_and_sort_names (void) if (name->name[0] == 0) continue; - if (deref_stat (dereference_option, name->name, &statbuf) != 0) + tar_stat_init (&st); + + if (deref_stat (name->name, &st.stat) != 0) { stat_diag (name->name); continue; } - if (S_ISDIR (statbuf.st_mode)) + if (S_ISDIR (st.stat.st_mode)) { - name->found_count++; - add_hierarchy_to_namelist (name, statbuf.st_dev, true); + int dir_fd = openat (chdir_fd, name->name, + open_read_flags | O_DIRECTORY); + if (dir_fd < 0) + open_diag (name->name); + else + { + st.fd = dir_fd; + if (fstat (dir_fd, &st.stat) != 0) + stat_diag (name->name); + else if (O_DIRECTORY || S_ISDIR (st.stat.st_mode)) + { + st.orig_file_name = xstrdup (name->name); + name->found_count++; + add_hierarchy_to_namelist (&st, name); + } + } } + + tar_stat_destroy (&st); } namelist = merge_sort (namelist, num_names, compare_names); num_names = 0; - nametab = hash_initialize (0, 0, - name_hash, - name_compare, NULL); + nametab = hash_initialize (0, 0, name_hash, name_compare, NULL); for (name = namelist; name; name = next_name) { next_name = name->next; - name->caname = normalize_filename (name->name); + name->caname = normalize_filename (name->change_dir, name->name); if (prev_name) { struct name *p = hash_lookup (nametab, name); @@ -950,6 +1675,7 @@ collect_and_sort_names (void) { if (p->child) rebase_child_list (p->child, name); + hash_delete (nametab, name); /* FIXME: remove_directory (p->caname); ? */ remname (p); free_name (p); @@ -1024,7 +1750,7 @@ name_scan (const char *file_name) struct name *gnu_list_name; struct name const * -name_from_list () +name_from_list (void) { if (!gnu_list_name) gnu_list_name = namelist; @@ -1050,49 +1776,21 @@ blank_name_list (void) name->found_count = 0; } -/* Yield a newly allocated file name consisting of FILE_NAME concatenated to - NAME, with an intervening slash if FILE_NAME does not already end in one. */ +/* Yield a newly allocated file name consisting of DIR_NAME concatenated to + NAME, with an intervening slash if DIR_NAME does not already end in one. */ char * -new_name (const char *file_name, const char *name) +make_file_name (const char *directory_name, const char *name) { - size_t file_name_len = strlen (file_name); - size_t namesize = strlen (name) + 1; - int slash = file_name_len && ! ISSLASH (file_name[file_name_len - 1]); - char *buffer = xmalloc (file_name_len + slash + namesize); - memcpy (buffer, file_name, file_name_len); - buffer[file_name_len] = '/'; - memcpy (buffer + file_name_len + slash, name, namesize); + size_t dirlen = strlen (directory_name); + size_t namelen = strlen (name) + 1; + int slash = dirlen && ! ISSLASH (directory_name[dirlen - 1]); + char *buffer = xmalloc (dirlen + slash + namelen); + memcpy (buffer, directory_name, dirlen); + buffer[dirlen] = '/'; + memcpy (buffer + dirlen + slash, name, namelen); return buffer; } -/* Return nonzero if file NAME is excluded. */ -bool -excluded_name (char const *name) -{ - return excluded_file_name (excluded, name + FILE_SYSTEM_PREFIX_LEN (name)); -} - -static Hash_table *individual_file_table; - -static void -register_individual_file (char const *name) -{ - struct stat st; - - if (deref_stat (dereference_option, name, &st) != 0) - return; /* Will be complained about later */ - if (S_ISDIR (st.st_mode)) - return; - - hash_string_insert (&individual_file_table, name); -} - -bool -is_individual_file (char const *name) -{ - return hash_string_lookup (individual_file_table, name); -} - /* Return the size of the prefix of FILE_NAME that is removed after