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:
11
NEWS
11
NEWS
@@ -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
|
||||
|
||||
48
doc/tar.texi
48
doc/tar.texi
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
15
src/create.c
15
src/create.c
@@ -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);
|
||||
|
||||
29
src/list.c
29
src/list.c
@@ -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,
|
||||
¤t_stat_info);
|
||||
(*do_something) ();
|
||||
if (transform_stat_info (current_header->header.typeflag,
|
||||
¤t_stat_info))
|
||||
(*do_something) ();
|
||||
else
|
||||
skip_member ();
|
||||
continue;
|
||||
|
||||
case HEADER_ZERO_BLOCK:
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
assign_string (pinput, fun ? fun (str, dat) : str);
|
||||
obstack_free (&stk, str);
|
||||
}
|
||||
else if (fun)
|
||||
{
|
||||
*pinput = NULL;
|
||||
assign_string (pinput, fun (str, dat));
|
||||
free (str);
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
char *str;
|
||||
char const *result;
|
||||
|
||||
_transform_name_to_obstack (type, *pinput, &str);
|
||||
result = (str[0] != 0 && fun) ? fun (str, type) : str;
|
||||
|
||||
if (result[0] == 0)
|
||||
{
|
||||
warnopt (WARN_EMPTY_TRANSFORM, 0,
|
||||
_("%s: transforms to empty name"), quotearg_colon (*pinput));
|
||||
obstack_free (&stk, str);
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user