+
+ tar_stat_destroy (&subdir);
+ }
+ }
+
+ free (namebuf);
+ name->child = child_head;
+ }
+}
+\f
+/* 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;
+}
+
+\f
+/* 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. */
+
+void
+collect_and_sort_names (void)
+{
+ struct name *name;
+ struct name *next_name, *prev_name = NULL;
+ int num_names;
+ Hash_table *nametab;
+
+ name_gather ();
+
+ if (!namelist)
+ addname (".", 0, false, NULL);
+
+ if (listed_incremental_option)
+ {
+ 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")));
+ }
+
+ 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)
+ /* 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;
+
+ tar_stat_init (&st);
+
+ if (deref_stat (dereference_option, name->name, &st.stat) != 0)
+ {
+ stat_diag (name->name);
+ continue;
+ }
+ if (S_ISDIR (st.stat.st_mode))
+ {
+ st.fd = open (name->name, open_read_flags | O_DIRECTORY);
+ if (st.fd < 0)
+ open_diag (name->name);
+ else if (fstat (st.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);
+ for (name = namelist; name; name = next_name)
+ {
+ next_name = name->next;
+ name->caname = normalize_filename (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)
+ append_incremental_renames (name->directory);
+ }
+}
+
+/* This is like name_match, except that
+ 1. It returns a pointer to the name it matched, and doesn't set FOUND
+ in structure. The caller will have to do that if it wants to.
+ 2. If the namelist is empty, it returns null, unlike name_match, which
+ returns TRUE. */
+struct name *
+name_scan (const char *file_name)
+{
+ size_t length = strlen (file_name);
+
+ while (1)
+ {
+ struct name *cursor = namelist_match (file_name, length);
+ if (cursor)
+ return cursor;
+
+ /* Filename from archive not found in namelist. If we have the whole
+ namelist here, just return 0. Otherwise, read the next name in and
+ compare it. If this was the last name, namelist->found_count will
+ remain on. If not, we loop to compare the newly read name. */
+
+ if (same_order_option && namelist && namelist->found_count)
+ {
+ name_gather (); /* read one more */
+ if (namelist->found_count)
+ return 0;
+ }
+ else
+ return 0;
+ }
+}
+
+/* This returns a name from the namelist which doesn't have ->found
+ set. It sets ->found before returning, so successive calls will
+ find and return all the non-found names in the namelist. */
+struct name *gnu_list_name;
+
+struct name const *
+name_from_list ()
+{
+ if (!gnu_list_name)
+ gnu_list_name = namelist;
+ while (gnu_list_name
+ && (gnu_list_name->found_count || gnu_list_name->name[0] == 0))
+ gnu_list_name = gnu_list_name->next;
+ if (gnu_list_name)
+ {
+ gnu_list_name->found_count++;
+ chdir_do (gnu_list_name->change_dir);
+ return gnu_list_name;
+ }
+ return NULL;
+}
+
+void
+blank_name_list (void)
+{
+ struct name *name;
+
+ gnu_list_name = 0;
+ for (name = namelist; name; name = name->next)
+ 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. */
+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)
+{
+ 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
+ stripping NUM leading file name components. NUM must be
+ positive. */
+
+size_t
+stripped_prefix_len (char const *file_name, size_t num)
+{
+ char const *p = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+ while (ISSLASH (*p))
+ p++;
+ while (*p)
+ {
+ bool slash = ISSLASH (*p);
+ p++;
+ if (slash)
+ {
+ if (--num == 0)
+ return p - file_name;
+ while (ISSLASH (*p))
+ p++;
+ }
+ }
+ return -1;
+}
+\f
+/* Return nonzero if NAME contains ".." as a file name component. */
+bool
+contains_dot_dot (char const *name)
+{
+ char const *p = name + FILE_SYSTEM_PREFIX_LEN (name);
+
+ for (;; p++)
+ {
+ if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))
+ return 1;
+
+ while (! ISSLASH (*p))
+ {
+ if (! *p++)
+ return 0;