]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Fix spurious "Not found in archive" errors.
authorSergey Poznyakoff <gray@gnu.org>
Wed, 14 May 2025 12:17:09 +0000 (15:17 +0300)
committerSergey Poznyakoff <gray@gnu.org>
Wed, 14 May 2025 12:28:55 +0000 (15:28 +0300)
* src/names.c (namelist_match_from): New function.
(namelist_match): Rewrite as a wrapper over it.
(register_match): New function.
(name_match)" Update all possible matches in the name list.

* tests/extrac29.at: New test.
* tests/Makefile.am: Add new test.
* tests/testsuite.at: Likewise.

src/names.c
tests/Makefile.am
tests/extrac29.at [new file with mode: 0644]
tests/testsuite.at

index 0d849fbf490e981df221674cc5002f6dcd1669c7..d3a36bcd527eeb5ad0b2c66ae52ef1d877d9502b 100644 (file)
@@ -1280,11 +1280,9 @@ add_starting_file (char const *file_name)
 /* 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)
@@ -1295,6 +1293,12 @@ namelist_match (char const *file_name, bool exact)
   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)
 {
@@ -1311,6 +1315,15 @@ 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
@@ -1344,12 +1357,33 @@ name_match (const char *file_name)
        }
       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
index aaa972112f0d2522993bf5c7f43e0e2a090d00c6..bd13bfb2fa94214428335fca6f1614be9936ea44 100644 (file)
@@ -138,6 +138,7 @@ TESTSUITE_AT = \
  extrac26.at\
  extrac27.at\
  extrac28.at\
+ extrac29.at\
  filerem01.at\
  filerem02.at\
  grow.at\
diff --git a/tests/extrac29.at b/tests/extrac29.at
new file mode 100644 (file)
index 0000000..464c436
--- /dev/null
@@ -0,0 +1,44 @@
+# 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
index 9eda62d4a7b1f8b9672a8af353fea9e2aba55e06..701f03896783d621b756ac8e2566f0f7b3684a76 100644 (file)
@@ -94,7 +94,7 @@ m4_define([AT_SORT_PREREQ],[
 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 $[]$
@@ -355,6 +355,7 @@ m4_include([extrac25.at])
 m4_include([extrac26.at])
 m4_include([extrac27.at])
 m4_include([extrac28.at])
+m4_include([extrac29.at])
 
 m4_include([backup01.at])