On 2026-01-30 11:56, Pavel Cahyna wrote:
+ if (one_top_level_option)
+ {
+ if (!IS_RELATIVE_FILE_NAME(one_top_level_dir))
+ paxusage (_("Top-level directory name must be a relative path"));
+ }
A good start. I fleshed it out by installing the attached. This also
clarifies the doc.
By the way, what should '--one-top-level=' (with an empty DIR) do? The
documentation is unclear. An empty DIR is not a relative file name....
3. If FOO/DIR (or DIR) cannot be opened, we mkdir it, by opening its parent
directory with openat2 (so that the parent directory does not escape FOO)
and then using mkdir on that directory. I.e., just one level: we should not
use the equivalent of mkdir -p.
Do you mean that the argument to --one-top-level (DIR) should not have
multiple components ( no a/b ), or that it could, but only the last
component ( b ) would be created if missing?
The latter. I am leery of mkdir -p semantics in general, as it has too
many issues (what permissions should the intermediate directories use?).
4. We do (2) and (3) lazily, i.e., only when we need FOO/DIR (or DIR) open
to extract a file underneath it.
Is it necessary? I would assume that it is simpler to do it
unconditionally at the beginning, and that we need it almost always
anyway.
I am worried about combinations like '-C a -C b file', where we should
create b/DIR (for b/DIR/file) but need not create a/DIR.
One possible, stricter alternative is to insist on (3) rather than (2).
I.e., we do not allow --one-top-level to extract over an existing
subdirectory. This would be more in the spirit of --one-top-level as I
understand it.
Sure, but it introduces more incompatibility. (I am not using
--one-top-level myself, and I don't know how often it is used and how,
but I see that currently an existing subdirectory is allowed.)
Hmm, I see your point but then I am also dubious that such uses are
advisable.
Come to think of it, how did you discover the openat2 changes vs
--one-top-level issue? Was it a user bug report? If so, what was the
user doing?
Note also that after the steps above it is still needed to transform the
file names to remove the prefix if they already contain it, to reproduce
the current and documented behavior.
Note also that --one-top-level also affects the output of
--show-transformed, which shows the prefix that gets added. See the
tests onetop04.at and onetop02.at. I think your proposal would affect
that. I suppose this is more for testing and it is worth to sacrifice
compatibility in this case.
I was hoping that we cleverly redo the internals so that none of that
stuff changes. But you're right, if that's a pain we can sacrifice
compatibility; I doubt whether anyone will care.From 678dbc679a1478da58c884de509ab2844eb04cdb Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Fri, 30 Jan 2026 12:48:48 -0800
Subject: [PATCH] tar: --one-top-level=DIR must be relative
* src/tar.c (decode_options): Require --one-top-level operand
to be relative.
---
NEWS | 9 +++++++--
doc/tar.texi | 12 ++++++++----
src/tar.c | 5 ++++-
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/NEWS b/NEWS
index 99a22896..083d5831 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2025-11-26
+GNU tar NEWS - User visible changes. 2026-01-30
Please send GNU tar bug reports to <[email protected]>
version 1.35.90 (git)
@@ -24,7 +24,9 @@ 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
+* Changes to behavior
+
+** 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
@@ -33,6 +35,9 @@ 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.
+
* Bug fixes
** When extracting, tar no longer follows symbolic links to targets
diff --git a/doc/tar.texi b/doc/tar.texi
index 211b5960..c1b6f7d2 100644
--- a/doc/tar.texi
+++ b/doc/tar.texi
@@ -3274,10 +3274,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 guard against
-tarbombs. In the absence of @var{dir} argument, the name of the new directory
-will be equal to the base name of the archive (file name minus the
-archive suffix, if recognized). Any member names that do not begin
+(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
+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.
+Any member names that do not begin
with that directory name (after
transformations from @option{--transform} and
@option{--strip-components}) will be prefixed with it. Recognized
diff --git a/src/tar.c b/src/tar.c
index 9c53bdbd..9376b59b 100644
--- a/src/tar.c
+++ b/src/tar.c
@@ -2675,7 +2675,7 @@ decode_options (int argc, char **argv)
one_top_level_option = false;
}
- if (one_top_level_option && !one_top_level_dir)
+ if (!one_top_level_dir && one_top_level_option)
{
/* If the user wants to guarantee that everything is under one
directory, determine its name now and let it be created later. */
@@ -2687,6 +2687,9 @@ decode_options (int argc, char **argv)
paxusage (_("Cannot deduce top-level directory name; "
"please set it explicitly with --one-top-level=DIR"));
}
+
+ if (one_top_level_dir && !IS_RELATIVE_FILE_NAME (one_top_level_dir))
+ paxusage(_("--one-top-level=DIR must use a relative file name"));
}
/* If ready to unlink hierarchies, so we are for simpler files. */
--
2.51.0