From 94391c4bf180062345ae0ce816f69f1c971d3969 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 20 Aug 2026 15:16:41 -0700 Subject: [PATCH] tar: reject -C x --one-top-dir=/abs/olute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not clear what ‘-C x --one-top-dir=/abs/olute’ should mean, so for now let’s play it safe and reject that combination. * src/misc.c (chdir_arg): Diagnose attempts to combine -C with --one-top-level=/abs/olute. * tests/onetop06.at: Move last test to onetop07.at, since it now differs in behavior and it’s better to test it separately. * tests/onetop07.at: New file. * tests/Makefile.am (TESTSUITE_AT): Add it. * tests/testsuite.at: Include it. --- NEWS | 9 +++++---- doc/tar.texi | 10 +++++----- src/misc.c | 20 ++++++++++++-------- tests/Makefile.am | 1 + tests/onetop06.at | 2 -- tests/onetop07.at | 35 +++++++++++++++++++++++++++++++++++ tests/testsuite.at | 1 + 7 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 tests/onetop07.at diff --git a/NEWS b/NEWS index abdeb348..fa95ffd2 100644 --- a/NEWS +++ b/NEWS @@ -38,10 +38,11 @@ option. ** The --one-top-level option now requires either -x or -d mode. Previously the behavior was unspecified in other operation modes. -** If DIR is absolute, the --one-top-level=DIR option now extracts all - files relative to DIR, without stripping leading DIR from file names. - Any -C options are ignored. Formerly, the behavior was - undocumented and differed from one tar version to the next. +** If DIR is absolute, the --one-top-level=DIR option is now incompatible + with the --directory (-C) option, and it now extracts all files + relative to DIR, without stripping leading DIR from file names. + Formerly, the behavior was undocumented and differed from one tar + version to the next. * Bug fixes diff --git a/doc/tar.texi b/doc/tar.texi index cdf745e0..f44d7b58 100644 --- a/doc/tar.texi +++ b/doc/tar.texi @@ -3291,8 +3291,7 @@ Extract files relative to @var{dir}, ignoring any @var{dir} prefixes in the archive. An absent @var{dir} defaults to the base name of the archive minus any recognized archive suffix. -If @var{dir} is relative, it is relative to the working directory; -if absolute, any @option{--directory} (@option{-C}) options are irrelevant. +If @var{dir} is relative, it is relative to the working directory. @xref{directory}. @opsummary{overwrite} @@ -9465,8 +9464,7 @@ Recognized file name suffixes are @samp{.tar}, and any compression suffixes recognizable by @xref{--auto-compress}. If @var{dir} is absolute, extract all files under that directory; -any @option{-C} options are irrelevant, -and @var{dir} itself is exempt from the usual safety rules +@var{dir} itself is exempt from the usual safety rules prohibiting extraction outside the working directory. The two main differences between @samp{--one-top-level=/abs/olute} and @samp{-C /abs/olute} are that the former affects all files @@ -9477,7 +9475,9 @@ whereas in the latter @file{/abs/olute} must already exist. The @option{--one-top-level} option can be used only when extracting, comparing, or reading from the archive. -It is incompatible with the @option{--absolute-names} (@option{-P}) option. +It is incompatible with the @option{--absolute-names} (@option{-P}) option, +and if @var{dir} is absolute it is incompatible with the +@option{--directory} (@option{-C}) option. @end table All filename arguments appearing after a diff --git a/src/misc.c b/src/misc.c index 8507f35f..f3fffac7 100644 --- a/src/misc.c +++ b/src/misc.c @@ -979,7 +979,10 @@ struct wd directory. Ordinarily the remaining entries are for -C options. But if --one-top-level, each entry is followed by another entry for its --one-top-level counterpart, so that ordinary entries are - even-numbered and --one-top-level entries are odd-numbered. */ + even-numbered and --one-top-level entries are odd-numbered. + And if --one-top-level specifies an absolute directory, + there are just two entries, one for the initial working directory + and one for the absolute directory. */ static struct wd *wd; /* The number of working directories in the vector. */ @@ -1046,13 +1049,18 @@ ensure_wd (void) } /* DIR is the operand of a -C option; add it to vector of chdir targets, - and return the index of its location. If --one-top-level-dir, add - two targets to the vector. However, if DIR is "." or an equivalent, - or if --one-top-level-dir is an absolute file name, + and return the index of its location. If --one-top-level-dir=ONETOP + is specified, add two targets to the vector if ONETOP is relative + and report an error otherwise. However, if DIR is "." or an equivalent, just reuse the last item in the vector. */ idx_t chdir_arg (char *dir) { + /* Unless this is the trivial chdir_arg (".") at start, this is an + error when combined with --one-top-level=X where X is absolute. */ + if (wd && one_top_level_dir && IS_ABSOLUTE_FILE_NAME (one_top_level_dir)) + paxfatal (0, _("-C and --one-top-level='/...' are incompatible")); + ensure_wd (); /* Optimize the common special case of the working directory, @@ -1064,10 +1072,6 @@ chdir_arg (char *dir) return wd_count - 1; } - /* Optimize --one-top-level=X where X is an absolute file name. */ - if (one_top_level_dir && IS_ABSOLUTE_FILE_NAME (one_top_level_dir)) - return wd_count - 1; - ptrdiff_t shortage = 1 + !!one_top_level_dir - (wd_alloc - wd_count); if (0 < shortage) wd = xpalloc (wd, &wd_alloc, shortage, -1, sizeof *wd); diff --git a/tests/Makefile.am b/tests/Makefile.am index 8e4d6bd4..81ac3075 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -205,6 +205,7 @@ TESTSUITE_AT = \ onetop04.at\ onetop05.at\ onetop06.at\ + onetop07.at\ opcomp01.at\ opcomp02.at\ opcomp03.at\ diff --git a/tests/onetop06.at b/tests/onetop06.at index dec81eeb..04b18787 100644 --- a/tests/onetop06.at +++ b/tests/onetop06.at @@ -32,8 +32,6 @@ diff d e/d/d || exit tar -cf test1.tar e/c || exit tar -xf test1.tar -C f --one-top-level=e/c || exit diff e/c/d f/e/c/d || exit -tar -xf test1.tar -C f --one-top-level=$PWD/g || exit -diff e/c/d g/e/c/d || exit ]) AT_CLEANUP diff --git a/tests/onetop07.at b/tests/onetop07.at new file mode 100644 index 00000000..ef59fa3d --- /dev/null +++ b/tests/onetop07.at @@ -0,0 +1,35 @@ +# Process this file with autom4te to create testsuite. -*- Autotest -*- +# +# Test suite for GNU tar. +# Copyright 2026 Free Software Foundation, Inc. +# +# This file is part of GNU tar. +# +# GNU tar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GNU tar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +AT_SETUP([tar -C and --one-top-level=/abs/olute]) +AT_KEYWORDS([extract onetop onetop07]) + +AT_TAR_CHECK([ +mkdir d f && genfile --file d/file || framework_failure_ +tar -cf test.tar d || exit +tar -xf test.tar -C f --one-top-level=$PWD/g +], +[2], +[], +[tar: -C and --one-top-level='/...' are incompatible +tar: Error is not recoverable: exiting now +]) + +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 4b438849..6243f0d8 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -544,6 +544,7 @@ m4_include([onetop03.at]) m4_include([onetop04.at]) m4_include([onetop05.at]) m4_include([onetop06.at]) +m4_include([onetop07.at]) AT_BANNER([Star tests]) m4_include([star/gtarfail.at])