From: Sergey Poznyakoff Date: Tue, 6 May 2025 12:23:03 +0000 (+0300) Subject: Skip file or archive member if its transformed name is empty. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6131dd28053d62906a833850368ac803389001b8;p=thirdparty%2Ftar.git 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. --- diff --git a/NEWS b/NEWS index dd1e2ae9..39986f23 100644 --- a/NEWS +++ b/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 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 diff --git a/doc/tar.texi b/doc/tar.texi index 8fb8c542..64d08cdb 100644 --- a/doc/tar.texi +++ b/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 diff --git a/src/common.h b/src/common.h index 7fba6325..5d4f83bf 100644 --- a/src/common.h +++ b/src/common.h @@ -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 diff --git a/src/create.c b/src/create.c index 8564e992..8458f215 100644 --- a/src/create.c +++ b/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); diff --git a/src/list.c b/src/list.c index 68ee3576..ed901909 100644 --- a/src/list.c +++ b/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: diff --git a/src/transform.c b/src/transform.c index 5e22d630..2e6d7187 100644 --- a/src/transform.c +++ b/src/transform.c @@ -17,6 +17,7 @@ #include #include #include +#include #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 diff --git a/src/warning.c b/src/warning.c index 44e1d1e9..161ac877 100644 --- a/src/warning.c +++ b/src/warning.c @@ -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); diff --git a/tests/extrac17.at b/tests/extrac17.at index 5c08ed3a..e38eac7c 100644 --- a/tests/extrac17.at +++ b/tests/extrac17.at @@ -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],