Skip file or archive member if its transformed name is empty.

* NEWS: Document changes.
* doc/tar.texi: Document changes.
* src/common.h (transform_stat_info): Change return value.
(transform_name_fp): Change signature.
(WARN_EMPTY_TRANSFORM): New constant.
* src/create.c: Check return from transform_name.  Skip file, if it
is false.
* src/list.c (transform_stat_info): Return bool.
(read_and): Skip member if transform_stat_info returns false.
* src/transform.c (_transform_name_to_obstack): Change return type.
Always allocate result in obstack.
(transform_name_fp): Change arguments.  Return true on
success (transformed string not empty).  Otherwise return false and
don't change the source string.
* src/warning.c: New warning class: empty-transform.
* tests/extrac17.at: Use --warning=empty-transform.
This commit is contained in:
Sergey Poznyakoff
2025-05-06 15:23:03 +03:00
parent bfc3346394
commit 6131dd2805
8 changed files with 131 additions and 45 deletions

11
NEWS
View File

@@ -1,4 +1,4 @@
GNU tar NEWS - User visible changes. 2024-11-02
GNU tar NEWS - User visible changes. 2025-05-06
Please send GNU tar bug reports to <bug-tar@gnu.org>
version TBD
@@ -24,6 +24,15 @@ as argument to --mtime option (see GNU tar manual, chapter 4
Defines output format for the COMMAND set by the above option. If
used, command output will be parsed using strptime(3).
* Skip file or archive member if transformed name is empty
If applying file name transformations (--transform and
--strip-component options) to a file or member name results in an
empty string that file or member is skipped and a warning is printed.
The warning can be suppressed using the --warning=empty-transform
option.
* Bug fixes
** Fixed O(n^2) time complexity bug for large numbers of directories when

View File

@@ -4639,7 +4639,7 @@ The subsections below discuss allowed values for @var{keyword} along with the
warning messages they control.
@menu
* General Warnings:: Keywords applicable for @command{tar --create}.
* General Warnings:: Keywords controlling @command{tar} operation.
* Archive Creation Warnings:: Keywords applicable for @command{tar --create}.
* Archive Extraction Warnings:: Keywords applicable for @command{tar --extract}.
* Incremental Extraction Warnings:: Keywords controlling incremental extraction.
@@ -4670,6 +4670,11 @@ suppressed if @option{--ignore-zeros} is in effect (@pxref{Ignore
Zeros}).
@end defvr
@defvr {warning} empty-transform
@cindex @samp{transforms to empty name}, warning message.
@samp{transforms to empty name}. @xref{transform}.
@end defvr
@defvr {warning} missing-zero-blocks
@cindex @samp{Terminating zero blocks missing}, warning message.
@samp{Terminating zero blocks missing at %s}. This warning is
@@ -8906,7 +8911,9 @@ $ @kbd{tar -xf usr.tar --strip=2 usr/include/stdlib.h}
The option @option{--strip=2} instructs @command{tar} to strip the
two leading components (@file{usr/} and @file{include/}) off the file
name.
name, before extracting it. Notice, that archive members to extract are
searched before that modification, hence the file name is specified in
full.
If you add the @option{--verbose} (@option{-v}) option to the invocation
above, you will note that the verbose listing still contains the
@@ -9022,7 +9029,6 @@ follows the GNU @command{sed} implementation in this regard, so
the interaction is defined to be: ignore matches before the
@var{number}th, and then match and replace all matches from the
@var{number}th on.
@end table
In addition, several @dfn{transformation scope} flags are supported,
@@ -9162,6 +9168,42 @@ $ @kbd{tar -cf arch.tar \
--transform='s,/usr/var,/var/;s,/usr/local,/usr/,'}
@end smallexample
Applying transformations to some file names may produce empty
strings. This may indicate errors in regular expressions, but it may
as well happen during normal operation, for instance, when using
@option{--strip-components} option with a value which is greater then
or equal to the number of directory components in the topmost
directory stored in the archive. Consider the following example. Let
the archive @file{usr.tar} contain:
@example
@group
$ @kbd{tar tf usr.tar}
usr/
usr/local/
...
@end group
@end example
Extracting from this archive with the @option{--strip=1} option will
transform the name of its first entry (@samp{usr/}) to an empty
string. When this happens, @GNUTAR{} prints a warning and skips this
member:
@example
@group
$ @kbd{tar --strip=1 -xf usr.tar}
tar: usr: transforms to empty name
@end group
@end example
If an empty name results from transformations applied when creating or
updating the archive, the warning is output and the file is not
archived.
If this is intended, you can suppress the warning using the
@option{--warning=no-empty-transform} option (@pxref{warnings}).
@node after
@section Operating Only on New Files

View File

@@ -611,7 +611,7 @@ extern idx_t recent_long_link_blocks;
void decode_header (union block *header, struct tar_stat_info *stat_info,
enum archive_format *format_pointer, bool do_user_group);
void transform_stat_info (char typeflag, struct tar_stat_info *stat_info);
bool transform_stat_info (char typeflag, struct tar_stat_info *stat_info);
char const *tartime (struct timespec t, bool full_time);
#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
@@ -972,7 +972,7 @@ enum
void set_transform_expr (const char *expr);
bool transform_name (char **pinput, int type);
bool transform_name_fp (char **pinput, int type,
char *(*fun) (char *, int), int dat);
char const *(*fun) (char const *, int));
bool transform_program_p (void);
/* Module suffix.c */
@@ -1014,7 +1014,8 @@ enum
WARN_XATTR_WRITE = 1 << 21,
WARN_RECORD_SIZE = 1 << 22,
WARN_FAILED_READ = 1 << 23,
WARN_MISSING_ZERO_BLOCKS = 1 << 24
WARN_MISSING_ZERO_BLOCKS = 1 << 24,
WARN_EMPTY_TRANSFORM = 1 << 25
};
/* These warnings are enabled by default in verbose mode: */
enum

View File

@@ -1478,7 +1478,11 @@ file_count_links (struct tar_stat_info *st)
assign_string (&linkname, safer_name_suffix (st->orig_file_name, true,
absolute_names_option));
transform_name (&linkname, XFORM_LINK);
if (!transform_name (&linkname, XFORM_LINK))
{
free (linkname);
return;
}
lp = xmalloc (FLEXNSIZEOF (struct link, name, strlen (linkname) + 1));
lp->ino = st->stat.st_ino;
@@ -1612,7 +1616,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
assign_string (&st->file_name,
safer_name_suffix (p, false, absolute_names_option));
transform_name (&st->file_name, XFORM_REGFILE);
if (!transform_name (&st->file_name, XFORM_REGFILE))
return NULL;
if (parentfd < 0 && ! top_level)
{
@@ -1797,7 +1802,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
if (!ok)
{
warnopt (WARN_FILE_CHANGED, 0, _("%s: file changed as we read it"),
warnopt (WARN_FILE_CHANGED, 0,
_("%s: file changed as we read it"),
quotearg_colon (p));
if (! ignore_failed_read_option)
set_exit_status (TAREXIT_DIFFERS);
@@ -1824,7 +1830,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
file_removed_diag (p, top_level, readlink_diag);
return allocated;
}
transform_name (&st->link_name, XFORM_SYMLINK);
if (!transform_name (&st->link_name, XFORM_SYMLINK))
return allocated;
if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
< strlen (st->link_name))
write_long_link (st);

View File

@@ -75,8 +75,8 @@ static char const base64_map[UCHAR_MAX + 1] = {
['+'] = 62 + 1, ['/'] = 63 + 1,
};
static char *
decode_xform (char *file_name, int type)
static char const *
decode_xform (char const *file_name, int type)
{
switch (type)
{
@@ -111,7 +111,7 @@ decode_xform (char *file_name, int type)
static bool
transform_member_name (char **pinput, int type)
{
return transform_name_fp (pinput, type, decode_xform, type);
return transform_name_fp (pinput, type, decode_xform);
}
static void
@@ -140,26 +140,31 @@ enforce_one_top_level (char **pfile_name)
free (file_name);
}
void
bool
transform_stat_info (char typeflag, struct tar_stat_info *stat_info)
{
if (typeflag == GNUTYPE_VOLHDR)
/* Name transformations don't apply to volume headers. */
return;
return true;
transform_member_name (&stat_info->file_name, XFORM_REGFILE);
if (!transform_member_name (&stat_info->file_name, XFORM_REGFILE))
return false;
switch (typeflag)
{
case SYMTYPE:
transform_member_name (&stat_info->link_name, XFORM_SYMLINK);
if (!transform_member_name (&stat_info->link_name, XFORM_SYMLINK))
return false;
break;
case LNKTYPE:
transform_member_name (&stat_info->link_name, XFORM_LINK);
if (!transform_member_name (&stat_info->link_name, XFORM_LINK))
return false;
break;
}
if (one_top_level_option)
enforce_one_top_level (&stat_info->file_name);
return true;
}
/* Main loop for reading an archive. */
@@ -223,9 +228,11 @@ read_and (void (*do_something) (void))
}
}
transform_stat_info (current_header->header.typeflag,
&current_stat_info);
if (transform_stat_info (current_header->header.typeflag,
&current_stat_info))
(*do_something) ();
else
skip_member ();
continue;
case HEADER_ZERO_BLOCK:

View File

@@ -17,6 +17,7 @@
#include <system.h>
#include <regex.h>
#include <mcel.h>
#include <quotearg.h>
#include "common.h"
enum transform_type
@@ -561,11 +562,11 @@ _single_transform_name_to_obstack (struct transform *tf, char *input)
free (rmp);
}
static bool
static void
_transform_name_to_obstack (int flags, char *input, char **output)
{
struct transform *tf;
bool alloced = false;
bool ok = false;
if (!stk_init)
{
@@ -579,38 +580,53 @@ _transform_name_to_obstack (int flags, char *input, char **output)
{
_single_transform_name_to_obstack (tf, input);
input = obstack_finish (&stk);
alloced = true;
ok = true;
}
}
if (!ok)
{
obstack_grow0 (&stk, input, strlen (input));
input = obstack_finish (&stk);
}
*output = input;
return alloced;
}
/* Transform name *PINPUT of a file or archive member of type TYPE
(a single XFORM_* bit). If FUN is not NULL, call this function
to further transform the result. Arguments to FUN are the transformed
name and type, it's return value is the new transformed name.
If transformation results in a non-empty string, store the result in
*PINPUT and return true. Otherwise, if it results in an empty string,
issue a warning, return false and don't modify PINPUT.
*/
bool
transform_name_fp (char **pinput, int flags,
char *(*fun) (char *, int), int dat)
transform_name_fp (char **pinput, int type,
char const *(*fun) (char const *, int))
{
char *str;
bool ret = _transform_name_to_obstack (flags, *pinput, &str);
if (ret)
char const *result;
_transform_name_to_obstack (type, *pinput, &str);
result = (str[0] != 0 && fun) ? fun (str, type) : str;
if (result[0] == 0)
{
assign_string (pinput, fun ? fun (str, dat) : str);
warnopt (WARN_EMPTY_TRANSFORM, 0,
_("%s: transforms to empty name"), quotearg_colon (*pinput));
obstack_free (&stk, str);
return false;
}
else if (fun)
{
*pinput = NULL;
assign_string (pinput, fun (str, dat));
free (str);
ret = true;
}
return ret;
assign_string (pinput, result);
obstack_free (&stk, str);
return true;
}
bool
transform_name (char **pinput, int type)
{
return transform_name_fp (pinput, type, NULL, 0);
return transform_name_fp (pinput, type, NULL);
}
bool

View File

@@ -50,6 +50,7 @@ static char const *const warning_args[] = {
"failed-read",
"missing-zero-blocks",
"verbose",
"empty-transform",
NULL
};
@@ -81,6 +82,7 @@ static int warning_types[] = {
WARN_FAILED_READ,
WARN_MISSING_ZERO_BLOCKS,
WARN_VERBOSE_WARNINGS,
WARN_EMPTY_TRANSFORM
};
ARGMATCH_VERIFY (warning_args, warning_types);

View File

@@ -38,7 +38,9 @@ genfile --file dir/subdir2/file2
tar cf dir.tar dir
tar -x -v -f dir.tar -C out --strip-components=2 --warning=no-timestamp \
tar -x -v -f dir.tar -C out --strip-components=2 \
--warning=no-empty-transform \
--warning=no-timestamp \
dir/subdir1/
],
[0],