/* 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
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 <http://www.gnu.org/licenses/>. */
#include <system.h>
#include <fnmatch.h>
#include <hash.h>
#include <quotearg.h>
+#include <wordsplit.h>
+#include <argp.h>
#include "common.h"
\f
-/* 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);
+\f
+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;
+}
+\f
+/* 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;
+}
+\f
+/* 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)
+\f
+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);
+}
+\f
+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 }
+};
+\f
+/* 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.
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)
}
\f
-struct name *
+static struct name *
make_name (const char *file_name)
{
struct name *p = xzalloc (sizeof (*p));
return p;
}
-void
+static void
free_name (struct name *p)
{
if (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;
+}
\f
/* Names from external name file. */
{
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);
}
+\f
+/* 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';
+}
+\f
+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;
+ }
+ }
+}
+\f
+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);
+}
+
+\f
+/* 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;
}
}
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)
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;
if (!cursor)
return true;
-
+
if (cursor->name[0] == 0)
{
chdir_do (cursor->change_dir);
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,
_("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. */
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. */
}
}
}
+
+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)
+ ;
+ }
+}
\f
/* 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. */
}
\f
-/* 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)
{
if (*string == 'D')
{
struct name *np;
+ struct tar_stat_info subdir;
+ int subfd;
if (allocated_length <= name_length + string_length)
{
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);
}
}
}
\f
-/* 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)
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;
child->length = size;
rebase_directory (child->directory,
- child->parent->name, old_prefix_len,
+ child->parent->name, old_prefix_len,
new_prefix, new_prefix_len);
}
}
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)
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)
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);
{
if (p->child)
rebase_child_list (p->child, name);
+ hash_delete (nametab, name);
/* FIXME: remove_directory (p->caname); ? */
remname (p);
free_name (p);
struct name *gnu_list_name;
struct name const *
-name_from_list ()
+name_from_list (void)
{
if (!gnu_list_name)
gnu_list_name = namelist;
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));
-}
-\f
-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);
-}
-
\f
/* Return the size of the prefix of FILE_NAME that is removed after