fdbase_opendir copies a requested directory name into the cache before
opening it. If open_subdir fails, this leaves the new name associated
with the old file descriptor. A retry after tar creates the missing
parent can then extract into the old directory.
Close an unowned old descriptor before trying to open the new dir and
invalidate the cache entry if that fails. Add a regression test using
equal-length sibling directory names.
* src/misc.c (fdbase_opendir): Invalidate the cache after a failed
replacement open.
* tests/extrac35.at: New test.
* tests/Makefile.am (TESTSUITE_AT): Add it.
* tests/testsuite.at: Include it.
* src/extract.c (rename_directory): Redo to accommodate
new semantics of fdbase1, and to use fds more intelligently.
* src/misc.c (fdbase_opendir): If ALTERNATE, it is now OK to use,
though not update, the main cache. This makes it possible for the
caller to do linkat (N, "A", N, "B") instead of the
linkat (N, "A", N + 1, "B") it previously did, and this
saves some open and close calls on directories.
Without this patch, tar would open the parent of a directory
for search, then open the directory itself for search.
It's more efficient to just open the directory for search.
* src/create.c (create_archive, restore_parent_fd, chdir_do):
Use open_searchdir instead of opening by hand.
* src/misc.c (chdir_do): Use fdbase_opendir and fdbase_close
instead of openat and close, so that the fd is cached properly.
(chdirable): New function.
(fdbase_clear): Use it to avoid closing chdirable FDs.
(fdbase_close): New function.
(open_subdir): New arg OFLAGS. All callers changed.
(fdbase_opendir): New arg CHILD_OFLAGS. All callers changed.
Prefer AT_FDCWD for absolute file names, so that we need not
worry about chdir_fd == BADFD in that case.
Do not allow empty file names.
When replacing an old directory, do not close its fd if it
is chdirable.
(open_searchdir): New function.
* src/tar.c (decode_options): Do not worry about one_top_level_dir
and IS_ABSOLUTE_FILE_NAME (one_top_level_dir), as fdbase_opendir
now prefers AT_FDCWD for absolute file names.
(tar_stat_close): Use fdbase_close instead of close,
so that the fd is cached properly.
Bring back documentation for --one-top-level absolute file names,
and clarify its behavior. Allow --one-top-level only with
-x, -d, -t.
* src/misc.c (chdir_do): Do not pass -1 to openat. Simplify now
that open_searchdir_how no longer has RESOLVE_BENEATH when
one_top_level_dir is absolute. Fix errno confusion in messages.
* src/tar.c (decode_options): Allow --one-top-level only with -x,
-d, and -t. Do not use RESOLVE_BENEATH if --one-top-level=/foo is
used; this simplifies chdir_do.
This merely refactors.
* src/misc.c: Don’t include assert.h.
(struct wd.one_top_level): Remove. All uses changed to say that
it’s a one_top_level directory iff its index is odd and
one_top_level_dir is nonnull. This simplifies the code and
removes the need for a couple of ‘assert’s. This code
uses ~+X instead of ~X to pacify gcc -Wbool-operation.
Prefer faccessat to fstatat for dir checking.
Also, don’t bother doing either check if caller
is about to check the rough equivalent anyway.
* src/extract.c (make_directories): Now static.
2nd arg is now a three-way flag. All uses changed.
Use faccessat instead of fstatat, to avoid EOVERFLOW issues.
(create_dir): New function, for use outside this module.
External caller changed.
(maybe_recoverable): Set *INTERDIR_MADE if make_directories
said we made the parent.
* src/extract.c (make_directories): New arg JUST_PARENT.
All callers changed.
* src/misc.c (struct wd.name, add_wd, chdir_arg):
Use char *, not char const *, to let make_directories
temporarily alter slashes. All uses changed.
(chdir_do): Do not create a new name with trailing "/."
because make_directories no longer needs this.
* src/extract.c (make_directories): Return a 3-way int rather than
an int (actually a bool) and another optional bool via a pointer.
All callers changed. In comment, make it clearer what the
return value means. Also, improve behavior in a small way: when
issuing a diagnostic on MS-Windows-like systems, use the original
directory name rather than translating '\\' to '/'.
* src/extract.c (is_directory_link): Remove; no longer needed.
(extract_dir): If --keep-directory-symlink, avoid the need
to call issymlinkat if either --dereference is not specified,
or if the plain stat fails. This saves a syscall, simplifies
the code a bit, and should simplify future changes.
* src/misc.c (add_wd, ensure_wd): New functions, replacing ...
(grow_wd): .. this function, for clarity. All uses changed.
(chdir_arg): Omit 2nd arg, to keep callers simpler.
All uses changed. Instead of being recursive, be iterative;
this is part of the simplification.
It turns out that make_directories can do what we need (create all
directories in a path) if we append a dummy "." component, as it creates
directories up to and excluding the last component of the path.
Also, do not avoid using delay_set_stat on the newly created
directories. This matches the rest of the code and avoids potentially
leaving the newly-created directories with too open permissions.
This requires some workarounds to cope with
apply_nonancestor_delayed_set_stat and mark_metadata_set problems: do it
as in the rest of the code - apply the stats before proceeding with
extraction of anything else.
The patch leverages the existing -C code. In order to do that, every
entry in the wd[] table gets another companion entry in the table that
represents the --one-top-level directory. There is one additional field
in each entry that allows skipping the companion entries if they are not
desired.
The actual directory is created lazily by chdir_do() if needed, as you
requested, to avoid empty "a/foo" after --one-top-level=foo -C a -C b.
The patch "by the way" fixes also extraction of hardlinks with
--one-top-level which currently is broken in the typical case (the
transform is not applied to the target, so the hardlink is wrong).
The patch does not yet handle the --show-transformed case with
--one-top-level that you discussed in another subthread. As a result, two
tests now fail (onetop02.at and onetop04.at). I suppose that this would
be quite easy to fix.
Another issue that I am aware of is that I am not sure whether to call
repair_delayed_set_stat and/or delay_set_stat on the newly created
directories like extract_dir() does (the whole delay_set code is abit
mysterious to me).
Use of --create together with --one-top-level should probably be
forbidden, as unlink.c uses wd[] in a way that will likely break in the
presence of companion entries (the chdir_do call in
flush_deferred_unlinks).
Show the one-top-level arg as a transformation
Since commit b8d8a61b, tar started honoring size field in headers
of the directory archive members when listing and extracting. That
doesn't seem right: although GNU tar always stores 0 for such members,
there are other tar implementation that don't. As a result, GNU tar
skips the actual directory contents when listing or extracting archives
created by such implementations.
This commit fixes that by assuming that size is 0 for directory archive
members.
* src/list.c (member_is_dir): Restore function.
(skim_member): Don't apply skim_file to directory members.
* tests/skipdir.at: Fix expectations.
* src/common.h (dumpdir_ok): New proto.
* src/incremen.c (dumpdir_ok): Take size as the second argument.
Verify if the last byte is 0 and the dumpdir contains 0 or more
nul-terminated strings.
(get_gnu_dumpdir): Check if the obtained dumpdir is ok. Clear the
is_dumpdir flag if it is not.
* src/xheader.c (dumpdir_decoder) Verify if the obtained dumpdir is
ok.
Problem reported by Antonio Teixeira.
* src/list.c (read_header): When POSIX says a size field must
be zero or does not represent a data count, treat it as zero.
* tests/extrac32.at: Update to match new behavior.
We now treat hard link sizes as zero even when the size fields are
nonzero, and this means the “injected” file is treated as valid
regardless of whether we list or extract.
* tests/extrac34.at: New test.
* tests/Makefile.am (TESTSUITE_AT), tests/testsuite.at: Add it.
Extraction over symlink to a directory was broken in 75b03fdff4.
See https://savannah.gnu.org/bugs/index.php?68368
* src/tar.c (decode_options): Don't enable RESOLVE_BENEATH mode
if --dereference is given.
* tests/extrac33.at: New test.
* tests/Makefile.am: Add new test.
* tests/testsuite.at: Likewise.
* tests/extrac13.at: Add a keyword.
* tests/extrac31.at: Remove --absolute-names keyword: that option
is never used in the testcase. Add CVE-2025-45582 keyword instead.
* doc/Makefile.am: Use tidy mode for texi2dvi; don't use
the deprecated texi2html.
* doc/tar.texi: Fix rendering of o-umlaut.
* doc/texify.sed: Likewise.
This fixes discrepancies reported in https://savannah.gnu.org/bugs/?68253
and https://savannah.gnu.org/bugs/?68408.
* doc/tar.texi: Emphasize that the -C option does not affect filename
arguments to another options, such as -f, -T or the like. Improve
the description of -T.
* doc/tar.1: Fix descriptions of -C and -T.
This fixes a false positive with gcc 16.1.1 20260501 (Red Hat
16.1.1-1) on x86-64.
* src/extract.c (set_stat): Use differently-worded but equivalent test
to help the compiler see that ts[0].tv_sec is used only if sedt.
This fixes an interaction of -C with incremental 'X'.
Problem reported by Pavel Cahyna.
* gnulib.modules: Remove mkdtemp.
Add tempname, since our mkdtempat uses it.
* lib/mkdtempat.c, lib/mkdtempat.h: New files.
* lib/Makefile.am (noinst_HEADERS): Add mkdtempat.h.
(libtar_a_SOURCES): Add mkdtempat.c.
* src/incremen.c: Include mkdtempat.h.
(purge_directory): Use mkdtempat, not mkdtemp.
* src/tar.c (one_top_level_option): Remove. All uses removed.
All external uses changed to equivalent of !!one_top_level_dir.
(decode_options): Set one_top_level_dir to be consistent with
the old one_top_level_option.
* src/list.c (enforce_one_top_level): Do not strip prefixes like
"../" (which should not be allowed unless -P) and ".../" (which
are ordinary file names). The "../" not being allowed should be
addressed in a different way.
* src/sparse.c (pax_dump_header_0, floorlog10)
(pax_dump_header_1, struct ok_n_block_ptr, decode_num)
(pax_decode_header): Prefer signed to unsigned integers
where either will do, as this allows for better runtime
checking for overflow. The integers in question cannot
be negative or greater than INTMAX_MAX anyway.
Problem identified by Michał Majchrowicz and Marcin Wyczechowski,
members of the AFINE Team.
* gnulib.modules: Add mempcpy, which we were already using anyway.
* src/incremen.c (purge_directory):
Use a safer suffix for 'X' entries, too.
Also, do not turn "/" to "//" or "//" to "///" (possible only if -P).
* src/buffer.c, src/compare.c, src/exclist.c, src/extract.c:
* src/sparse.c, src/tar.c, src/xheader.c:
UNNAMED is for when an identifier is never used and so does not
need a name. Prefer it to MAYBE_UNUSED when that is the case.
Also, drop MAYBE_UNUSED in some places where the identifier
is always used.
Problem reported by Guillermo de Angel in:
https://lists.gnu.org/r/bug-tar/2026-03/msg00007.html
* THANKS: Add him, and sort.
* src/extract.c (extract_dir, extract_file):
* src/incremen.c (purge_directory):
Do not call skip_member, as the caller now does that, and does it
more reliably.
* src/extract.c (extract_file):
Mark file as skipped when we’ve read it.
(extract_archive): Always call skip_member after extracting,
as it suppresses the skip as needed.
* src/incremen.c (try_purge_directory): Remove; no longer
needed. Move internals to purge_directory.
* src/list.c (read_header): Do not treat LNKTYPE header as having
size zero, as it can be nonzero (e.g., ‘pax -o linkdata’).
Set info->skipped field according to how the header was read.
(member_is_dir): Remove; no longer needed.
(skim_member): Skip directory data too, unless it’s already been
skipped (i.e., read).
* tests/extrac32.at: New file.
* tests/Makefile.am (TESTSUITE_AT):
* tests/testsuite.at:
Add it.
* tests/skipdir.at (skip directory members):
Fix test to match the correct behavior.
This fixes a bug introduced in commit
b009124ffd
dated 2025-05-12 17:17:21 +0300.
* src/misc.c (file_removed_diag): Guard set_exit_status call
with !ignore_failed_read_option, consistent with stat_diag and
other diagnostic functions.
* tests/filerem03.at: New test.
* tests/testsuite.at: Include it.
* tests/Makefile.am: Add it.
Copyright-paperwork-exempt: yes
Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Use fstat(origfd) instead of fstat(parentfd) when verifying the
alternatively-opened directory; parentfd is invalid in this branch.
Copyright-paperwork-exempt: yes