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:
4
NEWS
4
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (¤t_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))
|
||||
|
||||
36
src/names.c
36
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,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;
|
||||
|
||||
34
src/update.c
34
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,7 @@ TESTSUITE_AT = \
|
||||
update01.at\
|
||||
update02.at\
|
||||
update03.at\
|
||||
update04.at\
|
||||
volsize.at\
|
||||
volume.at\
|
||||
verbose.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
tests/update04.at
Normal file
56
tests/update04.at
Normal 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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user