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.
This commit is contained in:
Sergey Poznyakoff
2023-07-10 21:09:57 +03:00
parent b3a71dbdb9
commit 10954cf163
8 changed files with 121 additions and 20 deletions

4
NEWS
View File

@@ -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

View File

@@ -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);

View File

@@ -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))

View File

@@ -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,10 +1921,12 @@ 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)
{
if (!gnu_list_name->is_wildcard)
gnu_list_name->found_count++;
chdir_do (gnu_list_name->change_dir);
return gnu_list_name;

View File

@@ -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);
}
}

View File

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

View File

@@ -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
tests/update04.at Normal file
View File

@@ -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