--one-top-level clarifications

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 commit is contained in:
Paul Eggert
2026-07-29 23:12:46 -07:00
parent 05f21fe936
commit 941f62b24d
4 changed files with 50 additions and 42 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
GNU tar NEWS - User visible changes. 2026-06-20
GNU tar NEWS - User visible changes. 2026-07-22
Please send GNU tar bug reports to <bug-tar@gnu.org>
version 1.35.90 (git)
@@ -35,8 +35,8 @@ empty string that file or member is skipped and a warning is printed.
The warning can be suppressed using the --warning=empty-transform
option.
** --one-top-level=DIR now requires DIR to be relative.
Previously this restriction was alluded to in the manual but not enforced.
** The --one-top-level option now requires either -x or -d mode.
Previously the behavior was unspecified in other operation modes.
* Bug fixes
+10 -7
View File
@@ -3287,14 +3287,14 @@ directory.
@opsummary{one-top-level}
@item --one-top-level[=@var{dir}]
Tells @command{tar} to create a new directory beneath the extraction directory
(or the one passed to @option{-C}) and use it to prevent @command{tar}
from modifying files outside that directory.
If @var{dir} is present, it must be a relative file name.
If it is absent, the name of the new directory
Tells @command{tar} to use a directory beneath the extraction directory
(or the one passed to @option{-C}) to prevent @command{tar}
from accessing files outside that directory.
If @var{dir} is absent, the name of the directory
is the base name of the archive minus any recognized archive suffix.
If multiple @option{-C} options are present,
each has its own subdirectory with the same name.
If @var{dir} is an absolute file name, that is the only such directory;
otherwise, the working directory and the directories specified by
any @option{-C} options each have the named subdirectory.
Any member names that do not begin
with that directory name (after
transformations from @option{--transform} and
@@ -3302,6 +3302,9 @@ transformations from @option{--transform} and
file name suffixes are @samp{.tar}, and any compression suffixes
recognizable by @xref{--auto-compress}.
This option can be used only when extracting, comparing, or reading
from the archive.
@opsummary{overwrite}
@item --overwrite
+32 -32
View File
@@ -1079,7 +1079,8 @@ idx_t chdir_current;
/* Value suitable for use as the first argument to openat, and in
similar locations for fstatat, etc. This is an open file
descriptor, or AT_FDCWD if the working directory is current. It is
descriptor, or AT_FDCWD if the working directory is current,
or BADFD if the directory has not been opened yet. It is
valid until the next invocation of chdir_do. */
static int chdir_fd = AT_FDCWD;
@@ -1095,7 +1096,7 @@ chdir_do (idx_t i, bool create)
int fd = curr->fd;
bool one_top_level = !!one_top_level_dir;
/* Nothing to create unless we are at the one_top_level dir that has
/* Nothing to create unless we are at a one_top_level dir that has
not been created yet. */
create &= i & one_top_level & (fd == BADFD || fd == 0);
@@ -1103,42 +1104,41 @@ chdir_do (idx_t i, bool create)
{
if (! fd || create)
{
if (! IS_ABSOLUTE_FILE_NAME (curr->name))
chdir_do ((i - 1) & ~+one_top_level, false);
int dfd;
if (IS_ABSOLUTE_FILE_NAME (curr->name))
dfd = AT_FDCWD;
else
{
chdir_do ((i - 1) & ~+one_top_level, false);
dfd = chdir_fd;
}
fd = openat (chdir_fd, curr->name,
fd = openat (dfd, curr->name,
open_searchdir_how.flags & ~O_NOFOLLOW);
if (fd < 0)
{
if (create)
if (errno == ENOENT)
{
struct open_how saved_open_searchdir_how = open_searchdir_how;
/* Don't use O_BENEATH during creation of the
directory. The one-top-level directory is
allowed to be given as an absolute path. */
open_searchdir_how.resolve = 0;
if (create_dir (curr->name))
/* Directory likely exists now; retry. */
fd = openat (chdir_fd, curr->name,
open_searchdir_how.flags & ~O_NOFOLLOW);
open_searchdir_how = saved_open_searchdir_how;
/* Either the creation or open failed */
if (fd < 0)
open_fatal (curr->name);
}
else if ((i & one_top_level) && errno == ENOENT)
{
/* We are requested to not create the directory now. Mark it
as to be created later when called with create == true. */
chdir_fd = curr->fd = BADFD;
chdir_current = i;
/* Do not add it to the cache */
return;
}
else
{
open_fatal (curr->name);
if (create)
{
if (!create_dir (curr->name))
fatal_exit ();
/* Directory likely exists now; retry. */
fd = openat (dfd, curr->name,
open_searchdir_how.flags & ~O_NOFOLLOW);
}
else if (i & one_top_level)
{
/* Mark it to be created later if called with CREATE. */
chdir_fd = curr->fd = BADFD;
chdir_current = i;
/* Do not add it to the cache. */
return;
}
}
if (fd < 0)
open_fatal (curr->name);
}
curr->fd = fd;
+5
View File
@@ -2656,6 +2656,10 @@ decode_options (int argc, char **argv)
if (optloc_lookup (OC_ONE_TOP_LEVEL))
{
if (!is_subcommand_class (SUBCL_READ))
option_conflict_error ("--one-top-level",
subcommand_string (subcommand_option));
if (absolute_names_option)
{
struct option_locus *one_top_level_loc =
@@ -2712,6 +2716,7 @@ decode_options (int argc, char **argv)
open_searchdir_how.flags = (search_flags | nofollow_flag
| O_BINARY | O_CLOEXEC | O_DIRECTORY);
if (!absolute_names_option && !dereference_option
&& ! (one_top_level_dir && IS_ABSOLUTE_FILE_NAME (one_top_level_dir))
&& (subcommand_option == EXTRACT_SUBCOMMAND
|| subcommand_option == DIFF_SUBCOMMAND))
open_searchdir_how.resolve = RESOLVE_BENEATH;