X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Fnames.c;h=037b869d7596c9c44476d28aca72acf46c8851c6;hb=HEAD;hp=73b8741858e70335c0d99c211919143cf78b1cff;hpb=138fc7e67e3d9845cd7d81aad0e9c7724784f9b9;p=debian%2Ftar diff --git a/src/names.c b/src/names.c index 73b87418..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 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} +}; + +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; -struct group *getgrnam (); -struct passwd *getpwnam (); -#if ! HAVE_DECL_GETPWUID -struct passwd *getpwuid (); -#endif -#if ! HAVE_DECL_GETGRGID -struct group *getgrgid (); -#endif + 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) @@ -178,89 +563,164 @@ gname_to_gid (char const *gname, gid_t *gidp) return 1; } + +static struct name * +make_name (const char *file_name) +{ + struct name *p = xzalloc (sizeof (*p)); + if (!file_name) + file_name = ""; + p->name = xstrdup (file_name); + p->length = strlen (p->name); + return p; +} + +static void +free_name (struct name *p) +{ + if (p) + { + free (p->name); + free (p->caname); + free (p); + } +} + /* Names from the command call. */ static struct name *namelist; /* first name in list, if any */ -static struct name **nametail = &namelist; /* end of name list */ +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. */ @@ -274,85 +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. If filename_terminator - is NUL, CHANGE_DIRS is effectively always false. + 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"); +} - Entries of type NELT_FMASK cause updates of the matching_flags - value. */ -struct name_elt * -name_next_elt (int change_dirs) +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 (filename_terminator == '\0') - change_dirs = 0; + 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; +} - while (name_index != names) +/* 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; + + ++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; + } + } + + 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, "-")) + { + request_stdin ("-T"); + ent->v.file.fp = stdin; } - - source = ep->v.name; - source_len = strlen (source); - if (name_buffer_length < source_len) + else { - do + 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; } } @@ -380,8 +1037,7 @@ void name_gather (void) { /* Buffer able to hold a single name. */ - static struct name *buffer; - static size_t allocated_size; + static struct name *buffer = NULL; struct name_elt *ep; @@ -389,46 +1045,25 @@ name_gather (void) { static int change_dir; - if (allocated_size == 0) - { - allocated_size = offsetof (struct name, name) + NAME_FIELD_SIZE + 1; - buffer = xmalloc (allocated_size); - /* FIXME: This memset is overkill, and ugly... */ - memset (buffer, 0, allocated_size); - } - while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR) change_dir = chdir_arg (xstrdup (ep->v.name)); if (ep) { - size_t needed_size; - - buffer->length = strlen (ep->v.name); - needed_size = offsetof (struct name, name) + buffer->length + 1; - if (allocated_size < needed_size) - { - do - { - allocated_size *= 2; - if (! allocated_size) - xalloc_die (); - } - while (allocated_size < needed_size); - - buffer = xrealloc (buffer, allocated_size); - } + free_name (buffer); + buffer = make_name (ep->v.name); buffer->change_dir = change_dir; - strcpy (buffer->name, ep->v.name); buffer->next = 0; buffer->found_count = 0; - buffer->matching_flags = matching_flags; - - namelist = buffer; - nametail = &namelist->next; + buffer->matching_flags = INCLUDE_OPTIONS; + buffer->directory = NULL; + buffer->parent = NULL; + buffer->cmdline = true; + + namelist = nametail = buffer; } else if (change_dir) - addname (0, change_dir); + addname (0, change_dir, false, NULL); } else { @@ -442,11 +1077,11 @@ name_gather (void) change_dir = chdir_arg (xstrdup (ep->v.name)); if (ep) - addname (ep->v.name, change_dir); + addname (ep->v.name, change_dir, true, NULL); else { if (change_dir != change_dir0) - addname (0, change_dir); + addname (NULL, change_dir, false, NULL); break; } } @@ -455,25 +1090,24 @@ name_gather (void) /* Add a name to the namelist. */ struct name * -addname (char const *string, int change_dir) +addname (char const *string, int change_dir, bool cmdline, struct name *parent) { - size_t length = string ? strlen (string) : 0; - struct name *name = xmalloc (offsetof (struct name, name) + length + 1); - - if (string) - strcpy (name->name, string); - else - name->name[0] = 0; + struct name *name = make_name (string); + name->prev = nametail; name->next = NULL; - name->length = length; name->found_count = 0; - name->matching_flags = matching_flags; + name->matching_flags = INCLUDE_OPTIONS; name->change_dir = change_dir; - name->dir_contents = NULL; + name->directory = NULL; + name->parent = parent; + name->cmdline = cmdline; - *nametail = name; - nametail = &name->next; + if (nametail) + nametail->next = name; + else + namelist = name; + nametail = name; return name; } @@ -494,6 +1128,22 @@ namelist_match (char const *file_name, size_t length) return NULL; } +void +remname (struct name *name) +{ + struct name *p; + + if ((p = name->prev) != NULL) + p->next = name->next; + else + namelist = name->next; + + if ((p = name->next) != NULL) + p->prev = name->prev; + else + nametail = name->prev; +} + /* Return true if and only if name FILE_NAME (from an archive) matches any name from the namelist. */ bool @@ -507,12 +1157,12 @@ name_match (const char *file_name) if (!cursor) return true; - + if (cursor->name[0] == 0) { chdir_do (cursor->change_dir); - namelist = 0; - nametail = &namelist; + namelist = NULL; + nametail = NULL; return true; } @@ -525,8 +1175,8 @@ name_match (const char *file_name) if (starting_file_option) { free (namelist); - namelist = 0; - nametail = &namelist; + namelist = NULL; + nametail = NULL; } chdir_do (cursor->change_dir); @@ -565,8 +1215,6 @@ all_names_found (struct tar_stat_info *p) struct name const *cursor; size_t len; - if (test_label_option) - return true; if (!p->file_name || occurrence_option == 0 || p->had_trailing_slash) return false; len = strlen (p->file_name); @@ -579,29 +1227,24 @@ all_names_found (struct tar_stat_info *p) return true; } -static inline int -is_pattern (const char *string) -{ - return strchr (string, '*') || strchr (string, '[') || strchr (string, '?'); -} - -static void +static int regex_usage_warning (const char *name) { static int warned_once = 0; - if (warn_regex_usage && is_pattern (name)) + /* 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, - /* TRANSLATORS: The following three msgids form a single sentence. - */ - _("Pattern matching characters used in file names. Please,"))); + _("Pattern matching characters used in file names"))); WARN ((0, 0, - _("use --wildcards to enable pattern matching, or --no-wildcards to"))); - WARN ((0, 0, - _("suppress this warning."))); + _("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. */ @@ -614,17 +1257,16 @@ 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. */ - namelist = 0; - nametail = &namelist; + namelist = NULL; + nametail = NULL; if (same_order_option) { @@ -638,18 +1280,57 @@ 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 linked LIST of names, of given LENGTH, using COMPARE to order - names. Return the sorted list. 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 +/* 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 is a generic list-sorting function, but it's too painful to + make it both generic and portable in C. */ static struct name * -merge_sort (struct name *list, int length, - int (*compare) (struct name const*, struct name const*)) +merge_sort_sll (struct name *list, int length, + int (*compare) (struct name const*, struct name const*)) { struct name *first_list; struct name *second_list; @@ -687,8 +1368,8 @@ merge_sort (struct name *list, int length, second_list = SUCCESSOR (cursor); SUCCESSOR (cursor) = 0; - first_list = merge_sort (first_list, first_length, compare); - second_list = merge_sort (second_list, second_length, compare); + first_list = merge_sort_sll (first_list, first_length, compare); + second_list = merge_sort_sll (second_list, second_length, compare); merge_point = &result; while (first_list && second_list) @@ -716,30 +1397,51 @@ merge_sort (struct name *list, int length, #undef SUCCESSOR } +/* Sort doubly linked LIST of names, of given LENGTH, using COMPARE + to order names. Return the sorted list. */ +static struct name * +merge_sort (struct name *list, int length, + int (*compare) (struct name const*, struct name const*)) +{ + struct name *head, *p, *prev; + head = merge_sort_sll (list, length, compare); + /* Fixup prev pointers */ + for (prev = NULL, p = head; p; prev = p, p = p->next) + p->prev = prev; + return head; +} + /* A comparison function for sorting names. Put found names last; break ties by string comparison. */ static int -compare_names (struct name const *n1, struct name const *n2) +compare_names_found (struct name const *n1, struct name const *n2) { - int found_diff = WASFOUND(n2) - WASFOUND(n1); + int found_diff = WASFOUND (n2) - WASFOUND (n1); return found_diff ? found_diff : strcmp (n1->name, n2->name); } + +/* Simple comparison by names. */ +static int +compare_names (struct name const *n1, struct name const *n2) +{ + return strcmp (n1->name, n2->name); +} + -/* 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. */ +/* 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) +add_hierarchy_to_namelist (struct tar_stat_info *st, struct name *name) { - char *file_name = name->name; - const char *buffer = get_directory_contents (file_name, device); + const char *buffer; - if (! buffer) - name->dir_contents = "\0\0\0\0"; - else + name->directory = scan_directory (st); + buffer = directory_contents (name->directory); + if (buffer) { + struct name *child_head = NULL, *child_tail = NULL; size_t name_length = name->length; size_t allocated_length = (name_length >= NAME_FIELD_SIZE ? name_length + NAME_FIELD_SIZE @@ -750,8 +1452,7 @@ add_hierarchy_to_namelist (struct name *name, dev_t device) size_t string_length; int change_dir = name->change_dir; - name->dir_contents = buffer; - strcpy (namebuf, file_name); + strcpy (namebuf, name->name); if (! ISSLASH (namebuf[name_length - 1])) { namebuf[name_length++] = '/'; @@ -764,6 +1465,8 @@ add_hierarchy_to_namelist (struct name *name, dev_t device) if (*string == 'D') { struct name *np; + struct tar_stat_info subdir; + int subfd; if (allocated_length <= name_length + string_length) { @@ -778,15 +1481,96 @@ add_hierarchy_to_namelist (struct name *name, dev_t device) namebuf = xrealloc (namebuf, allocated_length + 1); } strcpy (namebuf + name_length, string + 1); - np = addname (namebuf, change_dir); - add_hierarchy_to_namelist (np, device); + np = addname (namebuf, change_dir, false, name); + if (!child_head) + child_head = np; + else + child_tail->sibling = np; + child_tail = np; + + 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); } } free (namebuf); + name->child = child_head; } } +/* Auxiliary functions for hashed table of struct name's. */ + +static size_t +name_hash (void const *entry, size_t n_buckets) +{ + struct name const *name = entry; + return hash_string (name->caname, n_buckets); +} + +/* Compare two directories for equality of their names. */ +static bool +name_compare (void const *entry1, void const *entry2) +{ + struct name const *name1 = entry1; + struct name const *name2 = entry2; + return strcmp (name1->caname, name2->caname) == 0; +} + + +/* Rebase 'name' member of CHILD and all its siblings to + the new PARENT. */ +static void +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; + char *newp = xmalloc (size + 1); + strcpy (newp, new_prefix); + strcat (newp, child->name + old_prefix_len); + free (child->name); + child->name = newp; + child->length = size; + + rebase_directory (child->directory, + child->parent->name, old_prefix_len, + new_prefix, new_prefix_len); + } +} + /* Collect all the names from argv[] (or whatever), expand them into a directory tree, and sort them. This gets only subdirectories, not all files. */ @@ -795,57 +1579,136 @@ void collect_and_sort_names (void) { struct name *name; - struct name *next_name; + struct name *next_name, *prev_name = NULL; int num_names; - struct stat statbuf; + Hash_table *nametab; name_gather (); + if (!namelist) + addname (".", 0, false, NULL); + if (listed_incremental_option) - read_directory_file (); + { + switch (chdir_count ()) + { + case 0: + break; + + case 1: + if (namelist->change_dir == 0) + USAGE_ERROR ((0, 0, + _("Using -C option inside file list is not " + "allowed with --listed-incremental"))); + break; + + default: + USAGE_ERROR ((0, 0, + _("Only one -C option is allowed with " + "--listed-incremental"))); + } - if (!namelist) - addname (".", 0); + read_directory_file (); + } - for (name = namelist; name; name = next_name) + num_names = 0; + for (name = namelist; name; name = name->next, num_names++) { - next_name = name->next; - if (name->found_count || name->dir_contents) + struct tar_stat_info st; + + if (name->found_count || name->directory) continue; if (name->matching_flags & EXCLUDE_WILDCARDS) /* NOTE: EXCLUDE_ANCHORED is not relevant here */ /* FIXME: just skip regexps for now */ continue; chdir_do (name->change_dir); + 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); + 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); } - num_names = 0; - for (name = namelist; name; name = name->next) - num_names++; namelist = merge_sort (namelist, num_names, compare_names); - for (name = namelist; name; name = name->next) - name->found_count = 0; + num_names = 0; + 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->change_dir, name->name); + if (prev_name) + { + struct name *p = hash_lookup (nametab, name); + if (p) + { + /* Keep the one listed in the command line */ + if (!name->parent) + { + if (p->child) + rebase_child_list (p->child, name); + hash_delete (nametab, name); + /* FIXME: remove_directory (p->caname); ? */ + remname (p); + free_name (p); + num_names--; + } + else + { + if (name->child) + rebase_child_list (name->child, p); + /* FIXME: remove_directory (name->caname); ? */ + remname (name); + free_name (name); + continue; + } + } + } + name->found_count = 0; + if (!hash_insert (nametab, name)) + xalloc_die (); + prev_name = name; + num_names++; + } + nametail = prev_name; + hash_free (nametab); + + namelist = merge_sort (namelist, num_names, compare_names_found); if (listed_incremental_option) { for (name = namelist; name && name->name[0] == 0; name++) ; if (name) - name->dir_contents = append_incremental_renames (name->dir_contents); + append_incremental_renames (name->directory); } } @@ -886,7 +1749,7 @@ name_scan (const char *file_name) find and return all the non-found names in the namelist. */ struct name *gnu_list_name; -char * +struct name const * name_from_list (void) { if (!gnu_list_name) @@ -898,9 +1761,9 @@ name_from_list (void) { gnu_list_name->found_count++; chdir_do (gnu_list_name->change_dir); - return gnu_list_name->name; + return gnu_list_name; } - return 0; + return NULL; } void @@ -913,65 +1776,19 @@ 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) -{ - 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); - return buffer; -} - -/* Return nonzero if file NAME is excluded. */ -bool -excluded_name (char const *name) +make_file_name (const char *directory_name, const char *name) { - return excluded_file_name (excluded, name + FILE_SYSTEM_PREFIX_LEN (name)); -} - -/* Names to avoid dumping. */ -static Hash_table *avoided_name_table; - -/* Remember to not archive NAME. */ -void -add_avoided_name (char const *name) -{ - hash_string_insert (&avoided_name_table, name); -} - -/* Should NAME be avoided when archiving? */ -bool -is_avoided_name (char const *name) -{ - return hash_string_lookup (avoided_name_table, 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); + 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; }