+/*-------------------------------------------------------------------------.
+| Gather names in a list for scanning. Could hash them later if we really |
+| care. |
+| |
+| If the names are already sorted to match the archive, we just read them |
+| one by one. name_gather reads the first one, and it is called by |
+| name_match as appropriate to read the next ones. At EOF, the last name |
+| read is just left in the buffer. This option lets users of small |
+| machines extract an arbitrary number of files by doing "tar t" and |
+| editing down the list of files. |
+`-------------------------------------------------------------------------*/
+
+void
+name_gather (void)
+{
+ /* Buffer able to hold a single name. */
+ static struct name *buffer;
+ static size_t allocated_length = 0;
+
+ char const *name;
+
+ if (same_order_option)
+ {
+ char *change_dir = NULL;
+
+ if (allocated_length == 0)
+ {
+ allocated_length = sizeof (struct name) + NAME_FIELD_SIZE;
+ buffer = (struct name *) xmalloc (allocated_length);
+ /* FIXME: This memset is overkill, and ugly... */
+ memset (buffer, 0, allocated_length);
+ }
+
+ while ((name = name_next (0)) && strcmp (name, "-C") == 0)
+ {
+ char const *dir = name_next (0);
+ if (! dir)
+ FATAL_ERROR ((0, 0, _("Missing file name after -C")));
+ if (change_dir)
+ free (change_dir);
+ change_dir = xstrdup (dir);
+ }
+
+ if (name)
+ {
+ buffer->length = strlen (name);
+ if (sizeof (struct name) + buffer->length >= allocated_length)
+ {
+ allocated_length = sizeof (struct name) + buffer->length;
+ buffer = (struct name *) xrealloc (buffer, allocated_length);
+ }
+ buffer->change_dir = change_dir;
+ strncpy (buffer->name, name, (size_t) buffer->length);
+ buffer->name[buffer->length] = 0;
+ buffer->next = NULL;
+ buffer->found = 0;
+
+ /* FIXME: Poorly named globals, indeed... */
+ namelist = buffer;
+ namelast = namelist;
+ }
+ else if (change_dir)
+ free (change_dir);
+
+ return;
+ }
+
+ /* Non sorted names -- read them all in. */
+
+ for (;;)
+ {
+ char *change_dir = NULL;
+ while ((name = name_next (0)) && strcmp (name, "-C") == 0)
+ {
+ char const *dir = name_next (0);
+ if (! dir)
+ FATAL_ERROR ((0, 0, _("Missing file name after -C")));
+ if (change_dir)
+ free (change_dir);
+ change_dir = xstrdup (dir);
+ }
+ if (name)
+ addname (name, change_dir);
+ else
+ {
+ if (change_dir)
+ free (change_dir);
+ break;
+ }
+ }
+}
+
+/*-----------------------------.
+| Add a name to the namelist. |
+`-----------------------------*/
+
+void
+addname (char const *string, char const *change_dir)
+{
+ struct name *name;
+ size_t length;
+
+ length = string ? strlen (string) : 0;
+ name = (struct name *) xmalloc (sizeof (struct name) + length);
+ memset (name, 0, sizeof (struct name) + length);
+ name->next = NULL;
+
+ if (string)
+ {
+ name->fake = 0;
+ name->length = length;
+ /* FIXME: Possibly truncating a string, here? Tss, tss, tss! */
+ strncpy (name->name, string, length);
+ name->name[length] = '\0';
+ }
+ else
+ name->fake = 1;
+
+ name->found = 0;
+ name->regexp = 0; /* assume not a regular expression */
+ name->firstch = 1; /* assume first char is literal */
+ name->change_dir = change_dir;
+ name->dir_contents = 0;
+
+ if (string && is_pattern (string))
+ {
+ name->regexp = 1;
+ if (string[0] == '*' || string[0] == '[' || string[0] == '?')
+ name->firstch = 0;
+ }
+
+ if (namelast)
+ namelast->next = name;
+ namelast = name;
+ if (!namelist)
+ namelist = name;
+}
+
+/*------------------------------------------------------------------------.
+| Return true if and only if name PATH (from an archive) matches any name |
+| from the namelist. |
+`------------------------------------------------------------------------*/
+
+int
+name_match (const char *path)
+{
+ size_t length = strlen (path);
+
+ while (1)
+ {
+ struct name *cursor = namelist;
+
+ if (!cursor)
+ return 1; /* empty namelist is easy */
+
+ if (cursor->fake)
+ {
+ chdir_from_initial_wd (cursor->change_dir);
+ namelist = 0;
+ return 1;
+ }
+
+ for (; cursor; cursor = cursor->next)
+ {
+ /* If first chars don't match, quick skip. */
+
+ if (cursor->firstch && cursor->name[0] != path[0])
+ continue;
+
+ /* Regular expressions (shell globbing, actually). */
+
+ if (cursor->regexp)
+ {
+ if (fnmatch (cursor->name, path, FNM_LEADING_DIR) == 0)
+ {
+ cursor->found = 1; /* remember it matched */
+ if (starting_file_option)
+ {
+ free (namelist);
+ namelist = NULL;
+ }
+ chdir_from_initial_wd (cursor->change_dir);
+
+ /* We got a match. */
+ return 1;
+ }
+ continue;
+ }
+
+ /* Plain Old Strings. */
+
+ if (cursor->length <= length
+ /* archive length >= specified */
+ && (path[cursor->length] == '\0'
+ || path[cursor->length] == '/')
+ /* full match on file/dirname */
+ && strncmp (path, cursor->name, cursor->length) == 0)
+ /* name compare */
+ {
+ cursor->found = 1; /* remember it matched */
+ if (starting_file_option)
+ {
+ free ((void *) namelist);
+ namelist = 0;
+ }
+ chdir_from_initial_wd (cursor->change_dir);
+
+ /* We got a match. */
+ return 1;
+ }
+ }
+
+ /* 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 will remain
+ on. If not, we loop to compare the newly read name. */
+
+ if (same_order_option && namelist->found)
+ {
+ name_gather (); /* read one more */
+ if (namelist->found)
+ return 0;
+ }
+ else
+ return 0;
+ }
+}
+
+/*------------------------------------------------------------------.
+| Print the names of things in the namelist that were not matched. |
+`------------------------------------------------------------------*/
+
+void
+names_notfound (void)
+{
+ struct name *cursor;
+ struct name *next;
+
+ for (cursor = namelist; cursor; cursor = next)
+ {
+ next = cursor->next;
+ if (!cursor->found && !cursor->fake)
+ ERROR ((0, 0, _("%s: Not found in archive"), cursor->name));
+
+ /* We could free the list, but the process is about to die anyway, so
+ save some CPU time. Amigas and other similarly broken software
+ will need to waste the time, though. */
+
+#ifdef amiga
+ if (!same_order_option)
+ free (cursor);