+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 (name->name, &st.stat) != 0)
+ {
+ stat_diag (name->name);
+ continue;
+ }
+ if (S_ISDIR (st.stat.st_mode))
+ {
+ 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);
+ for (name = namelist; name; name = next_name)
+ {
+ next_name = name->next;
+ name->caname = normalize_filename (name->change_dir, name->name);
+ if (prev_name)