/* Find a match for FILE_NAME in the name list. If EXACT is true,
look for exact match (no wildcards). */
static struct name *
-namelist_match (char const *file_name, bool exact)
+namelist_match_from (struct name *p, char const *file_name, bool exact)
{
- struct name *p;
-
- for (p = namelist; p; p = p->next)
+ for (; p; p = p->next)
{
if (p->name[0]
&& (exact ? !p->is_wildcard : true)
return NULL;
}
+static COMMON_INLINE struct name *
+namelist_match (char const *file_name, bool exact)
+{
+ return namelist_match_from (namelist, file_name, exact);
+}
+
void
remname (struct name *name)
{
nametail = name->prev;
}
+/* Update CURSOR to remember that it matched FILE_NAME. */
+static COMMON_INLINE void
+register_match (struct name *cursor, const char *file_name)
+{
+ if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
+ || cursor->found_count == 0)
+ cursor->found_count++;
+}
+
/* Return true if and only if name FILE_NAME (from an archive) matches any
name from the namelist. */
bool
}
if (cursor)
{
- if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
- || cursor->found_count == 0)
- cursor->found_count++; /* remember it matched */
- chdir_do (cursor->change_dir);
- /* We got a match. */
- return isfound (cursor);
+ /*
+ * Found the first match. It is still possible that the namelist
+ * contains other entries that also match that filename. Find all
+ * such entries and update their found_count to avoid spurious
+ * "Not found in archive" errors at the end of the run.
+ *
+ * FIXME: name matching logic should be rewritten to avoid O(N^2)
+ * time complexity.
+ *
+ * On the first entry to the loop below, the first match itself is
+ * updated.
+ */
+ struct name *found = NULL;
+ while (cursor)
+ {
+ register_match (cursor, file_name);
+ if (!found && isfound (cursor))
+ found = cursor;
+ cursor = namelist_match_from (cursor->next, file_name, false);
+ }
+
+ if (!found)
+ return false;
+
+ /* We got a match. */
+ chdir_do (found->change_dir);
+ return true;
}
/* Filename from archive not found in namelist. If we have the whole
--- /dev/null
+# Test suite for GNU tar. -*- autotest -*-
+# Copyright 2025 Free Software Foundation, Inc.
+#
+# This file is part of GNU tar.
+#
+# GNU tar is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# GNU tar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Description: When listing or extracting from an archive a given subset
+# of members, only the first matching argument was counted. This led
+# to spurious "Not found in archive" errors. For example, tar 1.35
+# when used in the test below, outputs
+#
+# tar: dir/a1: Not found in archive
+# tar: dir/b1: Not found in archive
+#
+# References: https://savannah.gnu.org/bugs/?67114
+
+AT_SETUP([Keeping track of matched names])
+AT_KEYWORDS([extract extrac29])
+AT_TAR_CHECK([
+AT_SORT_PREREQ
+mkdir dir
+touch dir/a1 dir/b1
+tar cf a.tar dir
+rm -rf dir
+tar -xvf a.tar dir dir/a1 dir/b1 | sort
+],
+[0],
+[dir/
+dir/a1
+dir/b1
+])
+AT_CLEANUP
test -z "`sort < /dev/null 2>&1`" || AT_SKIP_TEST
])
-dnl AT_UNPRIVILEGED_PREREQ - Skip test if running at root privileges
+dnl AT_UNPRIVILEGED_PREREQ - Skip test if running with root privileges
m4_define([AT_UNPRIVILEGED_PREREQ],[
echo "test" > $[]$
chmod 0 $[]$
m4_include([extrac26.at])
m4_include([extrac27.at])
m4_include([extrac28.at])
+m4_include([extrac29.at])
m4_include([backup01.at])