Introduced with RHEL-121245 Also, fix CVE-2026-18508. Resolves: RHEL-143906 Resolves: RHEL-223959
794 lines
25 KiB
Diff
794 lines
25 KiB
Diff
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 (¤t_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 (¤t_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))
|