extern dev_t root_device;
void extr_init (void);
+bool create_dir (char const *file_name);
void extract_archive (void);
void extract_finish (void);
bool rename_directory (char *src, char *dst);
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;
/* intmax (N) is like ((intmax_t) (N)) except without a cast so
that it is an error if N is a pointer. */
enum { BADFD = AT_FDCWD == -1 ? -2 : -1 };
extern idx_t chdir_current;
-idx_t chdir_arg (char const *dir);
-void chdir_do (idx_t dir);
+idx_t chdir_arg (char const *dir, bool one_top_level);
+void chdir_do (idx_t dir, bool create);
struct chdir_id { int err; dev_t st_dev; ino_t st_ino; } chdir_id (void);
struct fdbase fdbase (char const *);
struct fdbase fdbase1 (char const *);
static struct delayed_link *delayed_link_head;
static struct delayed_link **delayed_link_tail = &delayed_link_head;
+static bool one_top_level_prepare = false;
+
struct string_list
{
struct string_list *next;
/* Create a struct delayed_set_stat even if
mode == desired_mode, because
repair_delayed_set_stat may need to update the struct. */
- delay_set_stat (file_name,
- NULL, mode & ~ current_umask, MODE_RWX,
- desired_mode, AT_SYMLINK_NOFOLLOW);
+ if (! one_top_level_prepare)
+ delay_set_stat (file_name,
+ NULL, mode & ~ current_umask, MODE_RWX,
+ desired_mode, AT_SYMLINK_NOFOLLOW);
if (interdir_made)
*interdir_made = true;
print_for_mkdir (file_name, desired_mode);
&& memeq (file_name, data->file_name, data->file_name_len)))
break;
- chdir_do (data->change_dir);
+ chdir_do (data->change_dir, false);
if (check_for_renamed_directories)
{
| (we_are_root ? 0 : MODE_WXUSR));
}
+/* Trimmed version of extract_dir, to create a dir that is not in the
+ archive, including parents. Should behave like extract_dir when
+ NO_OVERWRITE_DIR_OLD_FILES is set in order to avoid changing existing
+ paths if they are in the way.
+*/
+bool
+create_dir (char const *file_name)
+{
+ int status;
+ mode_t mode;
+ bool interdir_made = false;
+ /* exists only to avoid passing a const pointer to make_directories */
+ char *unconst_file_name;
+
+ mode = MODE_RWX & ~ newdir_umask;
+
+ for (;;)
+ {
+ struct fdbase f = fdbase (file_name);
+ status = f.fd == BADFD ? -1 : mkdirat (f.fd, f.base, mode);
+ if (status == 0)
+ {
+ return true;
+ }
+
+ if (errno == EEXIST)
+ {
+ struct stat st;
+ st.st_mode = 0;
+
+ if (is_directory_link (file_name, &st))
+ return true;
+
+ if ((st.st_mode != 0 && fstatat_flags == 0)
+ || deref_stat (file_name, &st) == 0)
+ {
+ if (S_ISDIR (st.st_mode))
+ {
+ return true;
+ }
+ }
+ errno = EEXIST;
+ break;
+ }
+ else if (errno != ENOENT || interdir_made)
+ {
+ /* The error is not due to missing parent, or we already
+ tried to make the parent directories and succeeded, so
+ there must be another problem. No point in retrying. */
+ break;
+ }
+ unconst_file_name = xstrdup (file_name);
+ if (make_directories (unconst_file_name, &interdir_made) == 0)
+ {
+ free (unconst_file_name);
+ continue;
+ }
+ else
+ {
+ free (unconst_file_name);
+ break;
+ }
+ }
+ mkdir_error (file_name);
+ return false;
+}
+
/* Extractor functions for various member types */
static bool
{
idx_t dir = chdir_current;
apply_nonancestor_delayed_set_stat (current_stat_info.file_name, false);
- chdir_do (dir);
+ chdir_do (dir, false);
}
/* Take a safety backup of a previously existing file. */
tar_extractor_t fun = prepare_to_extract (current_stat_info.file_name,
typeflag);
- bool ok = fun && fun (current_stat_info.file_name, typeflag);
+ bool ok = false;
+ if (fun)
+ {
+ /* Create one_top_level dir if it does not exist. */
+ one_top_level_prepare = true;
+ chdir_do (chdir_current, !!one_top_level_dir);
+ one_top_level_prepare = false;
+ if (fun (current_stat_info.file_name, typeflag))
+ ok = true;
+ }
skip_member ();
if (!ok && backup_option)
undo_last_backup ();
{
char const *valid_source = NULL;
- chdir_do (ds->change_dir);
+ chdir_do (ds->change_dir, false);
for (struct string_list *sources = ds->sources;
sources;
idx_t 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);
+ }
}
bool
}
if (one_top_level_dir)
- enforce_one_top_level (&stat_info->file_name);
+ {
+ 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);
+ }
return true;
}
simple_print_header (struct tar_stat_info *st, union block *blk,
off_t block_ordinal)
{
- char *temp_name
- = (show_transformed_names_option
- ? (st->file_name ? st->file_name : st->orig_file_name)
- : (st->orig_file_name ? st->orig_file_name : st->file_name));
+ char *temp_name =
+ (show_transformed_names_option
+ ? transform_top_level (st->file_name ? st->file_name : st->orig_file_name)
+ : xstrdup (st->orig_file_name ? st->orig_file_name : st->file_name));
if (block_number_option)
{
}
fflush (stdlis);
xattrs_print (st);
+ free (temp_name);
}
#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
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. */
idx_t
chdir_count (void)
{
- return wd_count - !!wd_count;
+ idx_t count = 0;
+ if (wd_count)
+ {
+ /* Do not count the initial CWD entry -> start at 1. */
+ for (idx_t i = 1; i < wd_count; i++)
+ {
+ if (! wd[i].one_top_level)
+ count++;
+ }
+ }
+ return count;
}
/* Grow the WD table by at least one entry. */
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. */
idx_t
-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 ();
{
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++;
}
working directory; otherwise, I must be a value returned by
chdir_arg. */
void
-chdir_do (idx_t i)
+chdir_do (idx_t 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;
+
+ /* 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 (! fd)
+ if (chdir_current != i || create)
+ {
+ if (! fd || create)
{
if (! IS_ABSOLUTE_FILE_NAME (curr->name))
- chdir_do (i - 1);
+ {
+ idx_t 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)
+ {
+ 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 created, 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 (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;
}
}
- 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. */
{
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);
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 (streq (name, "."))
+ {
+ /* 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.
if (!wd[idx].abspath)
{
idx_t save_cwdi = chdir_current, i = idx;
- while (0 < i && !wd[i - 1].abspath)
+ while (0 < i && (!wd[i - 1].abspath || wd[i - 1].one_top_level))
i--;
for (; i <= idx; i++)
{
- chdir_do (i);
+ if (!wd[i].one_top_level)
+ chdir_do (i, false);
if (i == 0)
{
if ((wd[i].abspath = xgetcwd ()) == NULL)
wd[i].abspath = xstrdup (wd[i].name);
else
{
- namebuf_t nbuf = namebuf_create (wd[i - 1].abspath);
+ idx_t 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;
void
name_init (void)
{
+ chdir_do (chdir_arg (".", !!one_top_level_dir), false);
name_list_adjust ();
}
\f
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;
}
static idx_t change_dir;
while ((ep = name_next_elt (false)) && 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)
{
{
idx_t change_dir0 = change_dir;
while ((ep = name_next_elt (false)) && 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);
if (cursor->name[0] == 0)
{
- chdir_do (cursor->change_dir);
+ chdir_do (cursor->change_dir, false);
namelist = NULL;
nametail = NULL;
return true;
return false;
/* We got a match. */
- chdir_do (found->change_dir);
+ chdir_do (found->change_dir, false);
return true;
}
/* NOTE: EXCLUDE_ANCHORED is not relevant here */
/* FIXME: just skip regexps for now */
continue;
- chdir_do (name->change_dir);
+ chdir_do (name->change_dir, false);
if (name->name[0] == 0)
continue;
{
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;
"please set it explicitly with --one-top-level=DIR"));
}
- if (one_top_level_dir
- && ! (*one_top_level_dir
- && IS_RELATIVE_FILE_NAME (one_top_level_dir)))
- paxusage(_("--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. */
if (force
|| p->records_written < records_written)
{
- chdir_do (p->dir_idx);
+ chdir_do (p->dir_idx, false);
if (p->is_dir)
{
const char *fname;
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;
dunlink_head = dunlink_tail = NULL;
}
- chdir_do (saved_chdir);
+ chdir_do (saved_chdir, false);
}
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))