tar/tar-1.35-fix-absolute-one-top-level.patch
Pavel Cahyna f31e585904 Fix a regression with --one-top-level=/absolute
Introduced with RHEL-121245

Also, fix CVE-2026-18508.

Resolves: RHEL-143906
Resolves: RHEL-223959
2026-08-21 22:38:04 +02:00

794 lines
25 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff --git a/NEWS b/NEWS
index ffb4f54f..45fedbf5 100644
--- a/NEWS
+++ b/NEWS
@@ -1,12 +1,12 @@
-GNU tar NEWS - User visible changes. 2026-01-30
+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)
* Changes to behavior
-** --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
diff --git a/doc/tar.texi b/doc/tar.texi
index 9b04b94b..91302f05 100644
--- a/doc/tar.texi
+++ b/doc/tar.texi
@@ -3271,14 +3271,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
@@ -3286,6 +3286,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
diff --git a/src/common.h b/src/common.h
index ac0e7eef..fb732f0b 100644
--- a/src/common.h
+++ b/src/common.h
@@ -246,7 +246,6 @@ GLOBAL bool numeric_owner_option;
GLOBAL bool one_file_system_option;
/* Create a top-level directory for extracting based on the archive name. */
-GLOBAL bool one_top_level_option;
GLOBAL char *one_top_level_dir;
/* Specified value to be put into tar file in place of stat () results, or
@@ -545,6 +544,7 @@ void verify_volume (void);
/* Module extract.c. */
void extr_init (void);
+int make_directories (char *file_name, bool *interdir_made);
void extract_archive (void);
void extract_finish (void);
bool rename_directory (char *src, char *dst);
@@ -635,6 +635,20 @@ void skim_member (bool must_copy);
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) < (b) ? (b) : (a))
+/* A directory FD, and a file name BASE that matches the regexp "[^/]*[/]*".
+ If BASE is absolute (i.e., matches "/+"), FD is valid but irrelevant.
+ Otherwise if FD == AT_FDCWD, BASE is relative to the current directory.
+ Otherwise if FD == BADFD, the parent directory could not be opened
+ and BASE is merely the original file name's basename.
+ Otherwise, FD is open to a parent directory,
+ and BASE is relative to that directory.
+ BASE points to storage managed elsewhere; do not free it directly. */
+struct fdbase
+ {
+ int fd;
+ char const *base;
+ };
+
char const *quote_n_colon (int n, char const *arg);
void assign_string_or_null (char **dest, const char *src)
ATTRIBUTE_NONNULL ((1));
@@ -657,6 +671,8 @@ void namebuf_free (namebuf_t buf);
char *namebuf_name (namebuf_t buf, const char *name);
const char *tar_dirname (void);
+char *transform_top_level (const char *name)
+ _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
/* Represent N using a signed integer I such that (uintmax_t) I == N.
With a good optimizing compiler, this is equivalent to (intmax_t) i
@@ -728,10 +744,10 @@ size_t blocking_write (int fd, void const *buf, size_t count);
enum { BADFD = AT_FDCWD == -1 ? -2 : -1 };
extern int chdir_current;
-int chdir_arg (char const *dir);
-void chdir_do (int dir);
+int chdir_arg (char const *dir, bool one_top_level);
+void chdir_do (int dir, bool create);
struct chdir_id { int err; dev_t st_dev; ino_t st_ino; } chdir_id (void);
-struct fdbase { int fd; char const *base; } fdbase (char const *);
+struct fdbase fdbase (char const *);
struct fdbase fdbase1 (char const *);
void fdbase_clear (void);
int chdir_count (void);
diff --git a/src/create.c b/src/create.c
index 51c3f213..582ca4c6 100644
--- a/src/create.c
+++ b/src/create.c
@@ -1266,7 +1266,7 @@ ensure_slash (char **pstr)
/* If we just ran out of file descriptors, release a file descriptor
in the directory chain somewhere leading from DIR->parent->parent
up through the root. Return true if successful, false (preserving
- errno == EMFILE) otherwise.
+ errno) otherwise.
Do not release DIR's file descriptor, or DIR's parent, as other
code assumes that they work. On some operating systems, another
@@ -1277,7 +1277,8 @@ ensure_slash (char **pstr)
static bool
open_failure_recover (struct tar_stat_info const *dir)
{
- if (errno == EMFILE && dir && dir->parent)
+ int err = errno;
+ if ((err == EMFILE || err == ENFILE) && dir && dir->parent)
{
struct tar_stat_info *p;
for (p = dir->parent->parent; p; p = p->parent)
@@ -1286,7 +1287,7 @@ open_failure_recover (struct tar_stat_info const *dir)
tar_stat_close (p);
return true;
}
- errno = EMFILE;
+ errno = err;
}
return false;
diff --git a/src/extract.c b/src/extract.c
index 4685f83c..a31f560b 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -698,7 +698,7 @@ fixup_delayed_set_stat (char const *src, char const *dst)
create all required directories. Return zero if all the required
directories were created, nonzero (issuing a diagnostic) otherwise.
Set *INTERDIR_MADE (unless NULL) if at least one directory was created. */
-static int
+int
make_directories (char *file_name, bool *interdir_made)
{
char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
@@ -741,7 +741,7 @@ make_directories (char *file_name, bool *interdir_made)
repair_delayed_set_stat may need to update the struct. */
delay_set_stat (file_name,
0, mode & ~ current_umask, MODE_RWX,
- desired_mode, AT_SYMLINK_NOFOLLOW);
+ desired_mode, AT_SYMLINK_NOFOLLOW);
if (interdir_made)
*interdir_made = true;
print_for_mkdir (file_name, desired_mode);
@@ -974,7 +974,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
&& memcmp (file_name, data->file_name, data->file_name_len) == 0))
break;
- chdir_do (data->change_dir);
+ chdir_do (data->change_dir, false);
if (check_for_renamed_directories)
{
@@ -1844,7 +1844,7 @@ extract_archive (void)
{
int dir = chdir_current;
apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
- chdir_do (dir);
+ chdir_do (dir, false);
}
/* Take a safety backup of a previously existing file. */
@@ -1864,8 +1864,35 @@ extract_archive (void)
typeflag = sparse_member_p (&current_stat_info) ?
GNUTYPE_SPARSE : current_header->header.typeflag;
- bool ok = prepare_to_extract (current_stat_info.file_name, typeflag, &fun)
- && fun (current_stat_info.file_name, typeflag) == 0;
+ bool ok = false;
+ if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
+ {
+ if (one_top_level_dir)
+ {
+ /* Create one_top_level dir if it does not exist. */
+ chdir_do (chdir_current, true);
+ /* Flush delayed stat to mirror the code above that does it
+ before extracting a new entry. Creating the one_top_level
+ dir may have created new delayed_set_stat interdir
+ entries, so repeat the operation. Ideally this should not
+ be needed, but the newly-created interdir entries have
+ st_dev/st_ino uninitialized, which would be a problem if
+ there is a "." entry afterwards:
+ apply_nonancestor_delayed_set_stat would use the
+ uninitialized values. Ideally, st_dev/st_ino would be
+ initialized by mark_metadata_set, but this one does not
+ take chdir into account, so it stats a wrong file. */
+ if (!delay_directory_restore_option)
+ {
+ int dir = chdir_current;
+ apply_nonancestor_delayed_set_stat (current_stat_info.file_name,
+ false);
+ chdir_do (dir, false);
+ }
+ }
+ if (fun (current_stat_info.file_name, typeflag) == 0)
+ ok = true;
+ }
skip_member ();
if (!ok && backup_option)
undo_last_backup ();
@@ -1878,7 +1905,7 @@ apply_delayed_link (struct delayed_link *ds)
struct string_list *sources = ds->sources;
char const *valid_source = NULL;
- chdir_do (ds->change_dir);
+ chdir_do (ds->change_dir, false);
for (sources = ds->sources; sources; sources = sources->next)
{
diff --git a/src/list.c b/src/list.c
index 7d7cde5a..068ca5fb 100644
--- a/src/list.c
+++ b/src/list.c
@@ -128,16 +128,28 @@ enforce_one_top_level (char **pfile_name)
int pos = strlen (one_top_level_dir);
if (strncmp (p, one_top_level_dir, pos) == 0)
{
- if (ISSLASH (p[pos]) || p[pos] == 0)
- return;
+ /* Remove the one_top_level_dir prefix if it ends at
+ component boundary. */
+ if (ISSLASH (p[pos]))
+ {
+ *pfile_name = xstrdup (p[pos+1] ? &p[pos+1] : ".");
+ free (file_name);
+ return;
+ }
+ else if (p[pos] == 0)
+ {
+ *pfile_name = xstrdup (".");
+ free (file_name);
+ return;
+ }
}
-
- *pfile_name = make_file_name (one_top_level_dir, file_name);
- normalize_filename_x (*pfile_name);
+ /* If the prefix does not match, do nothing. */
}
else
- *pfile_name = xstrdup (one_top_level_dir);
- free (file_name);
+ {
+ *pfile_name = xstrdup (".");
+ free (file_name);
+ }
}
void
@@ -158,8 +170,15 @@ transform_stat_info (int typeflag, struct tar_stat_info *stat_info)
transform_member_name (&stat_info->link_name, XFORM_LINK);
}
- if (one_top_level_option)
- enforce_one_top_level (&current_stat_info.file_name);
+ if (one_top_level_dir)
+ {
+ enforce_one_top_level (&stat_info->file_name);
+ /* Hard links are interpreted relative to cwd, and --one-top-level
+ works by means of a hidden change of cwd to the requested directory.
+ Adjust hard link targets as well. */
+ if (typeflag == LNKTYPE)
+ enforce_one_top_level (&stat_info->link_name);
+ }
}
/* Main loop for reading an archive. */
@@ -1159,9 +1178,9 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
int sizelen;
if (show_transformed_names_option)
- temp_name = st->file_name ? st->file_name : st->orig_file_name;
+ temp_name = transform_top_level (st->file_name ? st->file_name : st->orig_file_name);
else
- temp_name = st->orig_file_name ? st->orig_file_name : st->file_name;
+ temp_name = xstrdup (st->orig_file_name ? st->orig_file_name : st->file_name);
if (block_number_option)
{
@@ -1354,6 +1373,7 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
}
fflush (stdlis);
xattrs_print (st);
+ free (temp_name);
}
diff --git a/src/misc.c b/src/misc.c
index 575d13d9..f6c9a819 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -23,6 +23,7 @@
#include <xgetcwd.h>
#include <unlinkdir.h>
#include <utimens.h>
+#include <assert.h>
#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
@@ -946,6 +947,7 @@ struct wd
to be used. */
int fd;
+ bool one_top_level;
/* If ID.err is zero, the directory's identity;
if positive, a failure indication with errno = ID.err;
if negative, no attempt has been made yet to get the identity. */
@@ -977,9 +979,17 @@ static size_t wdcache_count;
int
chdir_count (void)
{
- if (wd_count == 0)
- return wd_count;
- return wd_count - 1;
+ int count = 0;
+ if (wd_count)
+ {
+ /* Do not count the initial CWD entry -> start at 1. */
+ for (int i = 1; i < wd_count; i++)
+ {
+ if (! wd[i].one_top_level)
+ count++;
+ }
+ }
+ return count;
}
/* Grow the WD table by at least one entry. */
@@ -996,15 +1006,27 @@ grow_wd (void)
wd[wd_count].abspath = NULL;
wd[wd_count].fd = AT_FDCWD;
wd[wd_count].id.err = -1;
+ wd[wd_count].one_top_level = false;
wd_count++;
+ if (one_top_level_dir)
+ {
+ wd[wd_count].name = one_top_level_dir;
+ wd[wd_count].abspath = NULL;
+ wd[wd_count].fd = 0;
+ wd[wd_count].id.err = -1;
+ wd[wd_count].one_top_level = true;
+ wd_count++;
+ }
}
}
/* DIR is the operand of a -C option; add it to vector of chdir targets,
and return the index of its location. */
int
-chdir_arg (char const *dir)
+chdir_arg (char const *dir, bool one_top_level)
{
+ if (one_top_level)
+ chdir_arg (dir, false);
if (wd_count == wd_alloc)
grow_wd ();
@@ -1014,13 +1036,22 @@ chdir_arg (char const *dir)
{
dir += dotslashlen (dir);
if (! dir[dir[0] == '.'])
- return wd_count - 1;
+ {
+ if (wd[wd_count - 1].one_top_level == one_top_level)
+ return wd_count - 1;
+ else
+ return wd_count - 2;
+ }
}
+ if (one_top_level)
+ dir = one_top_level_dir;
+
wd[wd_count].name = dir;
wd[wd_count].abspath = NULL;
wd[wd_count].fd = 0;
wd[wd_count].id.err = -1;
+ wd[wd_count].one_top_level = one_top_level;
return wd_count++;
}
@@ -1039,21 +1070,74 @@ static int chdir_fd = AT_FDCWD;
working directory; otherwise, I must be a value returned by
chdir_arg. */
void
-chdir_do (int i)
+chdir_do (int i, bool create)
{
- if (chdir_current != i)
- {
- struct wd *curr = &wd[i];
- int fd = curr->fd;
+ struct wd *curr = &wd[i];
+ int fd = curr->fd;
- if (! fd)
+ /* Nothing to create unless we are at the one_top_level dir that has
+ not been created yet. */
+ create = create && curr->one_top_level && (fd == BADFD || fd == 0);
+
+ if (chdir_current != i || create)
+ {
+ if (! fd || create)
{
if (! IS_ABSOLUTE_FILE_NAME (curr->name))
- chdir_do (i - 1);
+ {
+ int j = i - 1;
+ if (wd[j].one_top_level)
+ {
+ j--;
+ assert (! wd[j].one_top_level);
+ }
+ chdir_do (j, false);
+ }
fd = openat (chdir_fd, curr->name,
open_searchdir_how.flags & ~O_NOFOLLOW);
if (fd < 0)
- open_fatal (curr->name);
+ {
+ if (create)
+ {
+ char *dir_with_dot;
+ 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;
+ /* Append a dot. make_directories creates
+ directories up to and excluding the last
+ component of the path. So, in order to create
+ "a/b", we need to pass "a/b/." to it. */
+ {
+ namebuf_t nbuf = namebuf_create (curr->name);
+ namebuf_add_dir (nbuf, ".");
+ dir_with_dot = namebuf_finish (nbuf);
+ }
+ if (make_directories (dir_with_dot, NULL) == 0)
+ /* Directory created, retry */
+ fd = openat (chdir_fd, curr->name,
+ open_searchdir_how.flags & ~O_NOFOLLOW);
+ open_searchdir_how = saved_open_searchdir_how;
+ free (dir_with_dot);
+ /* Either the creation or open failed */
+ if (fd < 0)
+ open_fatal (curr->name);
+ }
+ else if (errno == ENOENT && curr->one_top_level)
+ {
+ /* 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);
+ }
+ }
curr->fd = fd;
@@ -1071,7 +1155,7 @@ chdir_do (int i)
}
}
- if (0 < fd)
+ if (0 < fd && /* no assumption about sign of BADFD */ fd != BADFD)
{
/* Move the i value to the front of the cache. This is
O(CHDIR_CACHE_SIZE), but the cache is small. */
@@ -1175,6 +1259,14 @@ fdbase_opendir (char const *file_name, bool alternate)
{
char const *name = file_name;
+ if (chdir_fd == BADFD && ! IS_ABSOLUTE_FILE_NAME (file_name))
+ {
+ /* BADFD is a sentinel value meaning that the chdir directory
+ needs to be created lazily, therefore if we encounter it, the
+ directory does not exist yet. */
+ errno = ENOENT;
+ return (struct fdbase) { .fd = chdir_fd, .base = name };
+ }
/* Skip past leading "./"s,
but not past the last "./" if that ends the name. */
idx_t dslen = dotslashlen (name);
@@ -1219,13 +1311,13 @@ fdbase_opendir (char const *file_name, bool alternate)
if (subfd < 0)
{
/* Keep the old directory cached and report open failure,
- unless EMFILE means it's possible that falling
+ unless EMFILE/ENFILE means it's possible that falling
through to close the old directory would mean we
could successfully retry from the chdir_fd level.
When reporting failure, there is no need to
null-terminate the old directory, since the code does
not assume null termination. */
- if (errno != EMFILE)
+ if (errno != EMFILE && errno != ENFILE)
return (struct fdbase) { .fd = BADFD, .base = base };
}
else
@@ -1279,6 +1371,29 @@ tar_dirname (void)
return wd[chdir_current].name;
}
+/* Return a newly allocated string that shows NAME from the user's
+ viewpoint, given that --one-top-level may be in effect. */
+char *
+transform_top_level (const char *name)
+{
+ if (wd[chdir_current].one_top_level)
+ {
+ if (strcmp (name, ".") == 0)
+ {
+ /* nothing to append - .../. is the same as ... */
+ return xstrdup (wd[chdir_current].name);
+ }
+ else
+ {
+ namebuf_t nbuf = namebuf_create (wd[chdir_current].name);
+ namebuf_add_dir (nbuf, name);
+ return namebuf_finish (nbuf);
+ }
+ }
+ else
+ return xstrdup (name);
+}
+
/* Return the absolute path that represents the working
directory referenced by IDX.
@@ -1307,12 +1422,13 @@ tar_getcdpath (int idx)
int save_cwdi = chdir_current;
for (i = idx; i >= 0; i--)
- if (wd[i].abspath)
+ if (wd[i].abspath && !wd[i].one_top_level)
break;
while (++i <= idx)
{
- chdir_do (i);
+ if (!wd[i].one_top_level)
+ chdir_do (i, false);
if (i == 0)
{
if ((wd[i].abspath = xgetcwd ()) == NULL)
@@ -1325,13 +1441,18 @@ tar_getcdpath (int idx)
wd[i].abspath = xstrdup (wd[i].name);
else
{
- namebuf_t nbuf = namebuf_create (wd[i - 1].abspath);
+ int j = i - 1;
+ if (wd[j].one_top_level)
+ {
+ j--;
+ assert (! wd[j].one_top_level);
+ }
+ namebuf_t nbuf = namebuf_create (wd[j].abspath);
namebuf_add_dir (nbuf, wd[i].name);
wd[i].abspath = namebuf_finish (nbuf);
}
}
-
- chdir_do (save_cwdi);
+ chdir_do (save_cwdi, false);
}
return wd[idx].abspath;
diff --git a/src/names.c b/src/names.c
index 403192be..56cee63c 100644
--- a/src/names.c
+++ b/src/names.c
@@ -867,6 +867,7 @@ name_init (void)
{
name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
name_buffer_length = NAME_FIELD_SIZE;
+ chdir_do (chdir_arg (".", !!one_top_level_dir), false);
name_list_adjust ();
}
@@ -1119,7 +1120,8 @@ name_next_elt (int change_dirs)
case NELT_CHDIR:
if (change_dirs)
{
- chdir_do (chdir_arg (xstrdup (ep->v.name)));
+ chdir_do (chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir),
+ false);
name_list_advance ();
break;
}
@@ -1182,7 +1184,7 @@ name_gather (void)
static int change_dir;
while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
- change_dir = chdir_arg (xstrdup (ep->v.name));
+ change_dir = chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir);
if (ep)
{
@@ -1211,7 +1213,7 @@ name_gather (void)
{
int change_dir0 = change_dir;
while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
- change_dir = chdir_arg (xstrdup (ep->v.name));
+ change_dir = chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir);
if (ep)
addname (ep->v.name, change_dir, true, NULL);
@@ -1326,7 +1328,7 @@ name_match (const char *file_name)
if (cursor->name[0] == 0)
{
- chdir_do (cursor->change_dir);
+ chdir_do (cursor->change_dir, false);
namelist = NULL;
nametail = NULL;
return true;
@@ -1348,7 +1350,7 @@ name_match (const char *file_name)
if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
|| cursor->found_count == 0)
cursor->found_count++; /* remember it matched */
- chdir_do (cursor->change_dir);
+ chdir_do (cursor->change_dir, false);
/* We got a match. */
return ISFOUND (cursor);
}
@@ -1778,7 +1780,7 @@ collect_and_sort_names (void)
if (name->found_count || name->directory)
continue;
- chdir_do (name->change_dir);
+ chdir_do (name->change_dir, false);
if (name->name[0] == 0)
continue;
@@ -1924,7 +1926,7 @@ name_from_list (void)
{
if (!gnu_list_name->is_wildcard)
gnu_list_name->found_count++;
- chdir_do (gnu_list_name->change_dir);
+ chdir_do (gnu_list_name->change_dir, false);
return gnu_list_name;
}
return NULL;
diff --git a/src/tar.c b/src/tar.c
index 9a5fbd0b..56c9ed2a 100644
--- a/src/tar.c
+++ b/src/tar.c
@@ -1572,7 +1572,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
case ONE_TOP_LEVEL_OPTION:
optloc_save (OC_ONE_TOP_LEVEL, args->loc);
- one_top_level_option = true;
one_top_level_dir = arg;
break;
@@ -2576,9 +2575,11 @@ decode_options (int argc, char **argv)
same_order_option = false;
}
- if (one_top_level_option)
+ if (optloc_lookup (OC_ONE_TOP_LEVEL))
{
- char *base;
+ if (!IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ option_conflict_error ("--one-top-level",
+ subcommand_string (subcommand_option));
if (absolute_names_option)
{
@@ -2589,17 +2590,18 @@ decode_options (int argc, char **argv)
if (optloc_eq (one_top_level_loc, absolute_names_loc))
option_conflict_error ("--one-top-level", "--absolute-names");
- else if (one_top_level_loc->source == OPTS_COMMAND_LINE)
+ if (one_top_level_loc->source == OPTS_COMMAND_LINE)
absolute_names_option = false;
else
- one_top_level_option = false;
+ one_top_level_dir = NULL;
}
- if (!one_top_level_dir && one_top_level_option)
+ if (!absolute_names_option && !one_top_level_dir)
{
- /* If the user wants to guarantee that everything is under one
- directory, determine its name now and let it be created later. */
- base = base_name (archive_name_array[0]);
+ /* Determine name now; the directory (or directories, if -C
+ means there are multiple top-level directories) are
+ created later if needed. */
+ char *base = base_name (archive_name_array[0]);
one_top_level_dir = strip_compression_suffix (base);
free (base);
@@ -2609,9 +2611,7 @@ decode_options (int argc, char **argv)
"please set it explicitly with --one-top-level=DIR")));
}
- if (one_top_level_dir && !IS_RELATIVE_FILE_NAME (one_top_level_dir))
- USAGE_ERROR ((0, 0,
- _("--one-top-level=DIR must use a relative file name")));
+ normalize_filename_x (one_top_level_dir);
}
/* If ready to unlink hierarchies, so we are for simpler files. */
diff --git a/src/unlink.c b/src/unlink.c
index 33d2ccd1..e199bab3 100644
--- a/src/unlink.c
+++ b/src/unlink.c
@@ -103,7 +103,7 @@ flush_deferred_unlinks (bool force)
if (force
|| records_written > p->records_written + deferred_unlink_delay)
{
- chdir_do (p->dir_idx);
+ chdir_do (p->dir_idx, false);
if (p->is_dir)
{
const char *fname;
@@ -175,11 +175,11 @@ flush_deferred_unlinks (bool force)
struct deferred_unlink *next = p->next;
const char *fname;
- chdir_do (p->dir_idx);
+ chdir_do (p->dir_idx, false);
if (p->dir_idx && IS_CWD (p))
{
fname = tar_dirname ();
- chdir_do (p->dir_idx - 1);
+ chdir_do (p->dir_idx - 1, false);
}
else
fname = p->file_name;
@@ -195,9 +195,9 @@ flush_deferred_unlinks (bool force)
p = next;
}
dunlink_head = dunlink_tail = NULL;
- }
-
- chdir_do (saved_chdir);
+ }
+
+ chdir_do (saved_chdir, false);
}
void
diff --git a/src/update.c b/src/update.c
index 2bc38207..24167778 100644
--- a/src/update.c
+++ b/src/update.c
@@ -141,7 +141,7 @@ update_archive (void)
{
struct stat s;
- chdir_do (name->change_dir);
+ chdir_do (name->change_dir, false);
if (deref_stat (current_stat_info.file_name, &s) == 0)
{
if (S_ISDIR (s.st_mode))