浏览代码

Fix --update --wildcards

* src/common.h (name): New field: is_wildcard.
(name_scan): Change protoype.
* src/delete.c: Update calls to name_scan.
* src/names.c (addname, add_starting_file): Initialize is_wildcard.
(namelist_match): Take two arguments.  If second one is true, return
only exact matches.
(name_scan): Likewise.  All callers updated.
(name_from_list): Skip patterns.
* src/update.c (remove_exact_name): New function.
(update_archive): Do not remove matching name, if it is a pattern.
Instead, add a new entry with the matching file name.

* tests/update04.at: New test.
* tests/Makefile.am: Add new test.
* tests/testsuite.at: Include new test.

* NEWS: Update.
Sergey Poznyakoff 1 年之前
父节点
当前提交
10954cf163
共有 8 个文件被更改,包括 121 次插入20 次删除
  1. 3 1
      NEWS
  2. 2 1
      src/common.h
  3. 2 2
      src/delete.c
  4. 27 11
      src/names.c
  5. 29 5
      src/update.c
  6. 1 0
      tests/Makefile.am
  7. 1 0
      tests/testsuite.at
  8. 56 0
      tests/update04.at

+ 3 - 1
NEWS

@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2023-01-06
+GNU tar NEWS - User visible changes. 2023-07-10
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 
 version 1.34.90 (git)
@@ -11,6 +11,8 @@ version 1.34.90 (git)
 
 * Bug fixes
 
+** Fix interaction of --update with --wildcards
+
 ** Warn "file changed as we read it" less often.
    Formerly, tar warned if the file's size or ctime changed.
    However, this generated a false positive if tar read a file

+ 2 - 1
src/common.h

@@ -376,6 +376,7 @@ struct name
     char *name;                 /* File name or globbing pattern */
     size_t length;		/* cached strlen (name) */
     int matching_flags;         /* wildcard flags if name is a pattern */
+    bool is_wildcard;           /* true if this is a wildcard pattern */
     bool cmdline;               /* true if this name was given in the
 				   command line */
 
@@ -783,7 +784,7 @@ bool name_match (const char *name);
 void names_notfound (void);
 void label_notfound (void);
 void collect_and_sort_names (void);
-struct name *name_scan (const char *name);
+struct name *name_scan (const char *name, bool exact);
 struct name const *name_from_list (void);
 void blank_name_list (void);
 char *make_file_name (const char *dir_name, const char *name);

+ 2 - 2
src/delete.c

@@ -184,7 +184,7 @@ delete_archive_members (void)
 	  abort ();
 
 	case HEADER_SUCCESS:
-	  if ((name = name_scan (current_stat_info.file_name)) == NULL)
+	  if ((name = name_scan (current_stat_info.file_name, false)) == NULL)
 	    {
 	      skim_member (acting_as_filter);
 	      break;
@@ -279,7 +279,7 @@ delete_archive_members (void)
 	      /* Found another header.  */
 	      xheader_decode (&current_stat_info);
 
-	      if ((name = name_scan (current_stat_info.file_name)) != NULL)
+	      if ((name = name_scan (current_stat_info.file_name, false)) != NULL)
 		{
 		  name->found_count++;
 		  if (ISFOUND (name))

+ 27 - 11
src/names.c

@@ -1153,6 +1153,13 @@ name_next (int change_dirs)
   return nelt ? nelt->v.name : NULL;
 }
 
+static bool
+name_is_wildcard (struct name const *name)
+{
+  return (name->matching_flags & EXCLUDE_WILDCARDS) &&
+    fnmatch_pattern_has_wildcards (name->name, name->matching_flags);
+}
+
 /* Gather names in a list for scanning.  Could hash them later if we
    really care.
 
@@ -1189,6 +1196,7 @@ name_gather (void)
 	  buffer->directory = NULL;
 	  buffer->parent = NULL;
 	  buffer->cmdline = true;
+	  buffer->is_wildcard = name_is_wildcard (buffer);
 
 	  namelist = nametail = buffer;
 	}
@@ -1232,6 +1240,7 @@ addname (char const *string, int change_dir, bool cmdline, struct name *parent)
   name->directory = NULL;
   name->parent = parent;
   name->cmdline = cmdline;
+  name->is_wildcard = name_is_wildcard (name);
 
   if (nametail)
     nametail->next = name;
@@ -1265,19 +1274,22 @@ add_starting_file (char const *file_name)
   name->directory = NULL;
   name->parent = NULL;
   name->cmdline = true;
+  name->is_wildcard = name_is_wildcard (name);
 
   starting_file_option = true;
 }
 
-/* Find a match for FILE_NAME in the name list.  */
+/* Find a match for FILE_NAME in the name list.  If EXPECT is true,
+   look for exact match (no wildcards). */
 static struct name *
-namelist_match (char const *file_name)
+namelist_match (char const *file_name, bool exact)
 {
   struct name *p;
 
   for (p = namelist; p; p = p->next)
     {
       if (p->name[0]
+	  && (exact ? !p->is_wildcard : true)
 	  && exclude_fnmatch (p->name, file_name, p->matching_flags))
 	return p;
     }
@@ -1321,7 +1333,7 @@ name_match (const char *file_name)
 	  return true;
 	}
 
-      cursor = namelist_match (file_name);
+      cursor = namelist_match (file_name, false);
       if (starting_file_option)
 	{
 	  /* If starting_file_option is set, the head of the list is the name
@@ -1870,13 +1882,14 @@ collect_and_sort_names (void)
     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. */
+    returns TRUE.
+    3. If EXPECT is true, it looks for exact matches only (no wildcards). */
 struct name *
-name_scan (const char *file_name)
+name_scan (const char *file_name, bool exact)
 {
   while (1)
     {
-      struct name *cursor = namelist_match (file_name);
+      struct name *cursor = namelist_match (file_name, exact);
       if (cursor)
 	return cursor;
 
@@ -1896,9 +1909,10 @@ name_scan (const char *file_name)
     }
 }
 
-/* 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.  */
+/* This returns a name from the namelist which is an exact match (i.e.
+   not a pattern) and 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 *
@@ -1907,11 +1921,13 @@ name_from_list (void)
   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->is_wildcard ||
+	     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++;
+      if (!gnu_list_name->is_wildcard)
+	gnu_list_name->found_count++;
       chdir_do (gnu_list_name->change_dir);
       return gnu_list_name;
     }

+ 29 - 5
src/update.c

@@ -76,6 +76,25 @@ append_file (char *file_name)
     close_error (file_name);
 }
 
+/* If NAME is not a pattern, remove it from the namelist.  Otherwise,
+   remove the FILE_NAME that matched it.  Take care to look for exact
+   match when removing it. */
+static void
+remove_exact_name (struct name *name, char const *file_name)
+{
+  if (name->is_wildcard)
+    {
+      struct name *match = name_scan (file_name, true);
+      name->found_count++;
+      if (match)
+	name = match;
+      else
+	return;
+    }
+
+  remname (name);
+}
+
 /* Implement the 'r' (add files to end of archive), and 'u' (add files
    to end of archive if they aren't there, or are more up to date than
    the version in the archive) commands.  */
@@ -113,7 +132,7 @@ update_archive (void)
 	    archive_format = current_format;
 
 	    if (subcommand_option == UPDATE_SUBCOMMAND
-		&& (name = name_scan (current_stat_info.file_name)) != NULL)
+		&& (name = name_scan (current_stat_info.file_name, false)) != NULL)
 	      {
 		struct stat s;
 
@@ -122,10 +141,10 @@ update_archive (void)
 		  {
 		    if (S_ISDIR (s.st_mode))
 		      {
-			char *p, *dirp = tar_savedir (name->name, 1);
+			char *p, *dirp = tar_savedir (current_stat_info.file_name, 1);
 			if (dirp)
 			  {
-			    namebuf_t nbuf = namebuf_create (name->name);
+			    namebuf_t nbuf = namebuf_create (current_stat_info.file_name);
 
 			    for (p = dirp; *p; p += strlen (p) + 1)
 			      addname (namebuf_name (nbuf, p),
@@ -134,13 +153,18 @@ update_archive (void)
 			    namebuf_free (nbuf);
 			    free (dirp);
 
-			    remname (name);
+			    remove_exact_name (name, current_stat_info.file_name);
 			  }
 		      }
 		    else if (tar_timespec_cmp (get_stat_mtime (&s),
 					       current_stat_info.mtime)
 			     <= 0)
-		      remname (name);
+		      {
+			remove_exact_name (name, current_stat_info.file_name);
+		      }
+		    else if (name->is_wildcard)
+		      addname (current_stat_info.file_name,
+			       name->change_dir, false, NULL);
 		  }
 	      }
 

+ 1 - 0
tests/Makefile.am

@@ -254,6 +254,7 @@ TESTSUITE_AT = \
  update01.at\
  update02.at\
  update03.at\
+ update04.at\
  volsize.at\
  volume.at\
  verbose.at\

+ 1 - 0
tests/testsuite.at

@@ -455,6 +455,7 @@ m4_include([update.at])
 m4_include([update01.at])
 m4_include([update02.at])
 m4_include([update03.at])
+m4_include([update04.at])
 
 AT_BANNER([Verifying the archive])
 m4_include([verify.at])

+ 56 - 0
tests/update04.at

@@ -0,0 +1,56 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+# Test suite for GNU tar.
+# Copyright 2016-2023 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/>.
+
+AT_SETUP([update with wildcards])
+AT_KEYWORDS([update update04 wildcards])
+
+AT_TAR_CHECK([
+genfile --file file.a
+genfile --file file.b
+genfile --file file.c
+echo Create
+tar cf archive ./file.*
+sleep 1
+echo "update" > file.b
+echo First update
+tar ufv archive --wildcards './file.*'
+
+echo "Second update"
+tar ufv archive --wildcards './file.*'
+
+echo "Non-matching pattern"
+tar ufv archive --wildcards './file.*' './foo.*'
+echo $?
+],
+[0],
+[Create
+First update
+./file.b
+Second update
+Non-matching pattern
+2
+],
+[tar: ./foo.*: Not found in archive
+tar: Exiting with failure status due to previous errors
+])
+
+AT_CLEANUP
+
+
+