* 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.
-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>
\f
version TBD
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
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.
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
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
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,
--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
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))
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 */
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
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;
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)
{
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);
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);
['+'] = 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)
{
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
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. */
}
}
- 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:
#include <system.h>
#include <regex.h>
#include <mcel.h>
+#include <quotearg.h>
#include "common.h"
enum transform_type
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)
{
{
_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
"failed-read",
"missing-zero-blocks",
"verbose",
+ "empty-transform",
NULL
};
WARN_FAILED_READ,
WARN_MISSING_ZERO_BLOCKS,
WARN_VERBOSE_WARNINGS,
+ WARN_EMPTY_TRANSFORM
};
ARGMATCH_VERIFY (warning_args, warning_types);
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],