Cache parent directories

Although this might help (or hurt) performance, the main
motivation is to make it easier in future commits
to prevent tarballs from escaping the extraction directory.
* src/common.h: (BADFD): New constant.
(struct fdbase): New type.
* src/create.c (dump_file0): Use parent->fd instead of caching
it into a local, as the latter approach is now awkward.
* src/extract.c (extract_link): Don’t save errno unless needed.
* src/misc.c (safer_rmdir): New arg F.  All callers changed.
(maybe_backup_file): Construct full after_backup_name, now
that find_backup_file_name no longer does that for us.
(chdir_fd): Now static not extern, as other modules now use fdbase.
(fdbase_cache): New static var.
(fdbase_clear): New function.  Call it whenever removing
or renaming directories or symlinks to directories.
(fdbase_opendir): New static function.
(fdbase, fdbase1): New functions.  Call them whenever the
code formerly passed chdir_fd to a syscall.
This commit is contained in:
Paul Eggert
2025-11-12 17:33:11 -08:00
parent 382a47f2fd
commit bdd773d028
9 changed files with 372 additions and 130 deletions

View File

@@ -752,11 +752,17 @@ int deref_stat (char const *name, struct stat *buf);
idx_t blocking_read (int fd, void *buf, idx_t count);
idx_t blocking_write (int fd, void const *buf, idx_t count);
/* Not valid as the first argument to openat.
It is a negative integer not equal to AT_FDCWD. */
enum { BADFD = AT_FDCWD == -1 ? -2 : -1 };
extern idx_t chdir_current;
extern int chdir_fd;
idx_t chdir_arg (char const *dir);
void chdir_do (idx_t dir);
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 fdbase1 (char const *);
void fdbase_clear (void);
idx_t chdir_count (void);
void close_diag (char const *name);

View File

@@ -213,7 +213,9 @@ diff_file (void)
}
else
{
diff_handle = openat (chdir_fd, file_name, open_read_flags);
struct fdbase f = fdbase (file_name);
diff_handle = (f.fd == BADFD ? -1
: openat (f.fd, f.base, open_read_flags));
if (diff_handle < 0)
{
@@ -232,8 +234,7 @@ diff_file (void)
&& stat_data.st_size != 0)
{
struct timespec atime = get_stat_atime (&stat_data);
if (set_file_atime (diff_handle, chdir_fd, file_name, atime)
< 0)
if (set_file_atime (diff_handle, f.fd, f.base, atime) < 0)
utime_error (file_name);
}
@@ -265,9 +266,9 @@ diff_symlink (void)
char buf[1024];
idx_t len = strlen (current_stat_info.link_name);
char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
linkbuf, len + 1);
struct fdbase f = fdbase (current_stat_info.file_name);
ssize_t status = (f.fd == BADFD ? -1
: readlinkat (f.fd, f.base, linkbuf, len + 1));
if (status < 0)
{
@@ -426,7 +427,8 @@ diff_multivol (void)
}
int fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
struct fdbase f = fdbase (current_stat_info.file_name);
int fd = f.fd == BADFD ? -1 : openat (f.fd, f.base, open_read_flags);
if (fd < 0)
{

View File

@@ -815,12 +815,15 @@ start_header (struct tar_stat_info *st)
break;
case COMMAND_MTIME:
if (!sys_exec_setmtime_script (set_mtime_command,
chdir_fd,
st->orig_file_name,
set_mtime_format,
&mtime))
mtime = st->mtime;
{
struct fdbase f = fdbase (st->orig_file_name);
if (f.fd == BADFD
|| !sys_exec_setmtime_script (set_mtime_command,
f.fd, f.base,
set_mtime_format,
&mtime))
mtime = st->mtime;
}
break;
}
@@ -1338,8 +1341,10 @@ create_archive (void)
{
if (! st.orig_file_name)
{
int fd = openat (chdir_fd, p->name,
open_searchdir_flags);
struct fdbase f = fdbase (p->name);
int fd = (f.fd == BADFD ? -1
: openat (f.fd, f.base,
open_searchdir_flags));
if (fd < 0)
{
file_removed_diag (p->name, !p->parent,
@@ -1539,9 +1544,18 @@ subfile_open (struct tar_stat_info const *dir, char const *file, int flags)
gettext ("");
}
while ((fd = openat (dir ? dir->fd : chdir_fd, file, flags)) < 0
&& open_failure_recover (dir))
continue;
do
{
if (dir)
fd = openat (dir->fd, file, flags);
else
{
struct fdbase f = fdbase (file);
fd = f.fd == BADFD ? -1 : openat (f.fd, f.base, flags);
}
}
while (fd < 0 && open_failure_recover (dir));
return fd;
}
@@ -1569,8 +1583,9 @@ restore_parent_fd (struct tar_stat_info const *st)
if (parentfd < 0)
{
int origfd = openat (chdir_fd, parent->orig_file_name,
open_searchdir_flags);
struct fdbase f = fdbase (parent->orig_file_name);
int origfd = (f.fd == BADFD ? -1
: openat (f.fd, f.base, open_searchdir_flags));
if (0 <= origfd)
{
if (fstat (parentfd, &parentstat) < 0
@@ -1605,7 +1620,6 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
bool is_dir;
struct tar_stat_info const *parent = st->parent;
bool top_level = ! parent;
int parentfd = top_level ? chdir_fd : parent->fd;
void (*diag) (char const *) = 0;
if (interactive_option && !confirm ("add", p))
@@ -1618,12 +1632,15 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
if (!transform_name (&st->file_name, XFORM_REGFILE))
return NULL;
if (parentfd < 0 && ! top_level)
struct fdbase f = (top_level ? fdbase (name)
: (struct fdbase) { .fd = parent->fd, .base = name });
if (!top_level && parent->fd < 0)
{
errno = - parentfd;
errno = - parent->fd;
diag = open_diag;
}
else if (fstatat (parentfd, name, &st->stat, fstatat_flags) < 0)
else if (f.fd == BADFD
|| fstatat (f.fd, f.base, &st->stat, fstatat_flags) < 0)
diag = stat_diag;
else if (file_dumpable_p (&st->stat))
{
@@ -1695,9 +1712,9 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
bool ok;
struct stat st2;
xattrs_acls_get (parentfd, name, st, !is_dir);
xattrs_selinux_get (parentfd, name, st, fd);
xattrs_xattrs_get (parentfd, name, st, fd);
xattrs_acls_get (f.fd, f.base, st, !is_dir);
xattrs_selinux_get (f.fd, f.base, st, fd);
xattrs_xattrs_get (f.fd, f.base, st, fd);
if (is_dir)
{
@@ -1715,7 +1732,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
ok = dump_dir (st);
fd = st->fd;
parentfd = top_level ? chdir_fd : parent->fd;
f = (top_level ? fdbase (name)
: (struct fdbase) { .fd = parent->fd, .base = name });
}
else
{
@@ -1756,9 +1774,9 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
}
else if (fd == 0)
{
if (parentfd < 0 && ! top_level)
if (!top_level && parent->fd < 0)
{
errno = - parentfd;
errno = - parent->fd;
ok = false;
}
}
@@ -1809,7 +1827,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
}
else if (atime_preserve_option == replace_atime_preserve
&& timespec_cmp (st->atime, get_stat_atime (&st2)) != 0
&& set_file_atime (fd, parentfd, name, st->atime) < 0)
&& set_file_atime (fd, f.fd, f.base, st->atime) < 0)
utime_error (p);
}
@@ -1821,7 +1839,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
}
else if (S_ISLNK (st->stat.st_mode))
{
st->link_name = areadlinkat_with_size (parentfd, name, st->stat.st_size);
st->link_name = areadlinkat_with_size (f.fd, f.base, st->stat.st_size);
if (!st->link_name)
{
if (errno == ENOMEM)
@@ -1835,8 +1853,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
< strlen (st->link_name))
write_long_link (st);
xattrs_selinux_get (parentfd, name, st, 0);
xattrs_xattrs_get (parentfd, name, st, 0);
xattrs_selinux_get (f.fd, f.base, st, 0);
xattrs_xattrs_get (f.fd, f.base, st, 0);
block_ordinal = current_block_ordinal ();
st->stat.st_size = 0; /* force 0 size on symlink */
@@ -1857,23 +1875,23 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
else if (S_ISCHR (st->stat.st_mode))
{
type = CHRTYPE;
xattrs_acls_get (parentfd, name, st, true);
xattrs_selinux_get (parentfd, name, st, 0);
xattrs_xattrs_get (parentfd, name, st, 0);
xattrs_acls_get (f.fd, f.base, st, true);
xattrs_selinux_get (f.fd, f.base, st, 0);
xattrs_xattrs_get (f.fd, f.base, st, 0);
}
else if (S_ISBLK (st->stat.st_mode))
{
type = BLKTYPE;
xattrs_acls_get (parentfd, name, st, true);
xattrs_selinux_get (parentfd, name, st, 0);
xattrs_xattrs_get (parentfd, name, st, 0);
xattrs_acls_get (f.fd, f.base, st, true);
xattrs_selinux_get (f.fd, f.base, st, 0);
xattrs_xattrs_get (f.fd, f.base, st, 0);
}
else if (S_ISFIFO (st->stat.st_mode))
{
type = FIFOTYPE;
xattrs_acls_get (parentfd, name, st, true);
xattrs_selinux_get (parentfd, name, st, 0);
xattrs_xattrs_get (parentfd, name, st, 0);
xattrs_acls_get (f.fd, f.base, st, true);
xattrs_selinux_get (f.fd, f.base, st, 0);
xattrs_xattrs_get (f.fd, f.base, st, 0);
}
else if (S_ISSOCK (st->stat.st_mode))
{

View File

@@ -266,7 +266,8 @@ fd_i_chmod (int fd, char const *file, mode_t mode, int atflag)
if (result == 0 || implemented (errno))
return result;
}
return fchmodat (chdir_fd, file, mode, atflag);
struct fdbase f = fdbase (file);
return f.fd == BADFD ? -1 : fchmodat (f.fd, f.base, mode, atflag);
}
/* A version of fd_i_chmod which gracefully handles several common error
@@ -315,16 +316,18 @@ fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag)
if (result == 0 || implemented (errno))
return result;
}
return fchownat (chdir_fd, file, uid, gid, atflag);
struct fdbase f = fdbase (file);
return f.fd == BADFD ? -1 : fchownat (f.fd, f.base, uid, gid, atflag);
}
/* Use fstat if possible, fstatat otherwise. */
static int
fd_stat (int fd, char const *file, struct stat *st, int atflag)
{
return (0 <= fd
? fstat (fd, st)
: fstatat (chdir_fd, file, st, atflag));
if (0 <= fd)
return fstat (fd, st);
struct fdbase f = fdbase (file);
return f.fd == BADFD ? -1 : fstatat (f.fd, f.base, st, atflag);
}
/* Set the mode for FILE_NAME to MODE.
@@ -421,7 +424,15 @@ set_stat (char const *file_name,
ts[0].tv_nsec = UTIME_OMIT;
ts[1] = st->mtime;
if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0)
int r;
if (0 <= fd)
r = futimens (fd, ts);
if (fd < 0 || (r < 0 && errno == ENOSYS))
{
struct fdbase f = fdbase (file_name);
r = f.fd == BADFD ? -1 : utimensat (f.fd, f.base, ts, atflag);
}
if (r == 0)
{
if (incremental_option)
check_time (file_name, ts[0]);
@@ -546,9 +557,9 @@ delay_set_stat (char const *file_name, struct tar_stat_info const *st,
if (data->interdir)
{
struct stat real_st;
if (fstatat (chdir_fd, data->file_name,
&real_st, data->atflag)
< 0)
struct fdbase f = fdbase (data->file_name);
if (f.fd == BADFD
|| fstatat (f.fd, f.base, &real_st, data->atflag) < 0)
{
stat_error (data->file_name);
}
@@ -660,7 +671,8 @@ repair_delayed_set_stat (char const *dir,
for (data = delayed_set_stat_head; data; data = data->next)
{
struct stat st;
if (fstatat (chdir_fd, data->file_name, &st, data->atflag) < 0)
struct fdbase f = fdbase (data->file_name);
if (f.fd == BADFD || fstatat (f.fd, f.base, &st, data->atflag) < 0)
{
stat_error (data->file_name);
return;
@@ -774,7 +786,8 @@ make_directories (char *file_name, bool *interdir_made)
*cursor = '\0'; /* truncate the name there */
desired_mode = MODE_RWX & ~ newdir_umask;
mode = desired_mode | (we_are_root ? 0 : MODE_WXUSR);
status = mkdirat (chdir_fd, file_name, mode);
struct fdbase f = fdbase (file_name);
status = f.fd == BADFD ? -1 : mkdirat (f.fd, f.base, mode);
if (status == 0)
{
@@ -817,7 +830,8 @@ make_directories (char *file_name, bool *interdir_made)
process may have created it, so check whether it exists now. */
*parent_end = '\0';
struct stat st;
int stat_status = fstatat (chdir_fd, file_name, &st, 0);
struct fdbase f = fdbase (file_name);
int stat_status = f.fd == BADFD ? -1 : fstatat (f.fd, f.base, &st, 0);
if (! (stat_status < 0 || S_ISDIR (st.st_mode)))
stat_status = -1;
if (stat_status < 0)
@@ -973,7 +987,8 @@ set_xattr (MAYBE_UNUSED char const *file_name,
#ifdef HAVE_XATTRS
if (xattrs_option && st->xattr_map.xm_size)
{
int r = mknodat (chdir_fd, file_name, mode | S_IFREG, 0);
struct fdbase f = fdbase (file_name);
int r = f.fd == BADFD ? -1 : mknodat (f.fd, f.base, mode | S_IFREG, 0);
if (r < 0)
return r;
xattrs_xattrs_set (st, file_name, typeflag, false);
@@ -1017,7 +1032,8 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
if (check_for_renamed_directories)
{
if (fstatat (chdir_fd, data->file_name, &st, data->atflag) < 0)
struct fdbase f = fdbase (data->file_name);
if (f.fd == BADFD || fstatat (f.fd, f.base, &st, data->atflag) < 0)
{
stat_error (data->file_name);
skip_this_one = 1;
@@ -1066,8 +1082,9 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
static bool
is_directory_link (char const *file_name, struct stat *st)
{
return (issymlinkat (chdir_fd, file_name)
&& fstatat (chdir_fd, file_name, st, 0) == 0
struct fdbase f = fdbase (file_name);
return (f.fd != BADFD && issymlinkat (f.fd, f.base)
&& fstatat (f.fd, f.base, st, 0) == 0
&& S_ISDIR (st->st_mode));
}
@@ -1126,7 +1143,8 @@ extract_dir (char *file_name, char typeflag)
for (;;)
{
status = mkdirat (chdir_fd, file_name, mode);
struct fdbase f = fdbase (file_name);
status = f.fd == BADFD ? -1 : mkdirat (f.fd, f.base, mode);
if (status == 0)
{
current_mode = mode & ~ current_umask;
@@ -1235,18 +1253,20 @@ open_output_file (char const *file_name, char typeflag, mode_t mode,
}
}
struct fdbase f = fdbase (file_name);
/* If O_NOFOLLOW is needed but does not work, check for a symlink
separately. There's a race condition, but that cannot be avoided
on hosts lacking O_NOFOLLOW. */
if (! HAVE_WORKING_O_NOFOLLOW
&& overwriting_old_files && ! dereference_option
&& issymlinkat (chdir_fd, file_name))
&& f.fd != BADFD && issymlinkat (f.fd, f.base))
{
errno = ELOOP;
return -1;
}
fd = openat (chdir_fd, file_name, openflag, mode);
fd = f.fd == BADFD ? -1 : openat (f.fd, f.base, openflag, mode);
if (0 <= fd)
{
if (openflag & O_EXCL)
@@ -1410,7 +1430,8 @@ find_delayed_link_source (char const *name)
if (!delayed_link_table)
return false;
if (fstatat (chdir_fd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
struct fdbase f = fdbase (name);
if (f.fd == BADFD || fstatat (f.fd, f.base, &st, AT_SYMLINK_NOFOLLOW) < 0)
{
if (errno != ENOENT)
stat_error (name);
@@ -1436,8 +1457,16 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
int fd;
struct stat st;
while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
for (;;)
{
struct fdbase f = fdbase (file_name);
if (f.fd != BADFD)
{
fd = openat (f.fd, f.base, O_WRONLY | O_CREAT | O_EXCL, 0);
if (0 <= fd)
break;
}
if (errno == EEXIST && find_delayed_link_source (file_name))
{
/* The placeholder file has already been created. This means
@@ -1459,7 +1488,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
open_error (file_name);
return false;
}
}
}
if (fstat (fd, &st) < 0)
{
@@ -1536,15 +1565,23 @@ extract_link (char *file_name, MAYBE_UNUSED char typeflag)
do
{
struct stat st1, st2;
int e;
int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0);
e = errno;
struct stat st, st1;
int status;
struct fdbase f = fdbase (file_name), f1;
if (f.fd == BADFD)
status = -1;
else
{
f1 = fdbase1 (link_name);
status = (f1.fd == BADFD ? -1
: linkat (f1.fd, f1.base, f.fd, f.base, 0));
}
if (status == 0)
{
if (delayed_link_table
&& fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0)
&& fstatat (f1.fd, f1.base, &st1, AT_SYMLINK_NOFOLLOW) == 0)
{
struct delayed_link dl1;
dl1.st_ino = st1.st_ino;
@@ -1564,14 +1601,14 @@ extract_link (char *file_name, MAYBE_UNUSED char typeflag)
return true;
}
else if ((e == EEXIST && streq (link_name, file_name))
|| ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW)
== 0)
&& (fstatat (chdir_fd, file_name, &st2, AT_SYMLINK_NOFOLLOW)
== 0)
&& psame_inode (&st1, &st2)))
return true;
int e = errno;
if ((e == EEXIST && streq (link_name, file_name))
|| (f.fd != BADFD && f1.fd != BADFD
&& fstatat (f1.fd, f1.base, &st1, AT_SYMLINK_NOFOLLOW) == 0
&& fstatat (f.fd, f.base, &st, AT_SYMLINK_NOFOLLOW) == 0
&& psame_inode (&st1, &st)))
return true;
errno = e;
}
while ((rc = maybe_recoverable (file_name, false, &interdir_made))
@@ -1597,7 +1634,10 @@ extract_symlink (char *file_name, MAYBE_UNUSED char typeflag)
|| contains_dot_dot (current_stat_info.link_name)))
return create_placeholder_file (file_name, true, &interdir_made);
while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) < 0)
for (struct fdbase f;
((f = fdbase (file_name)).fd == BADFD
|| symlinkat (current_stat_info.link_name, f.fd, f.base) < 0);
)
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
@@ -1636,8 +1676,10 @@ extract_node (char *file_name, char typeflag)
mode_t mode = (current_stat_info.stat.st_mode & (MODE_RWX | S_IFBLK | S_IFCHR)
& ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev)
< 0)
for (struct fdbase f;
((f = fdbase (file_name)).fd == BADFD
|| mknodat (f.fd, f.base, mode, current_stat_info.stat.st_rdev) < 0);
)
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
@@ -1666,7 +1708,10 @@ extract_fifo (char *file_name, char typeflag)
mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
& ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
while (mkfifoat (chdir_fd, file_name, mode) < 0)
for (struct fdbase f;
((f = fdbase (file_name)).fd == BADFD
|| mkfifoat (f.fd, f.base, mode) < 0);
)
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
@@ -1882,7 +1927,7 @@ static void
apply_delayed_link (struct delayed_link *ds)
{
struct string_list *sources = ds->sources;
char const *valid_source = 0;
char const *valid_source = NULL;
chdir_do (ds->change_dir);
@@ -1894,24 +1939,29 @@ apply_delayed_link (struct delayed_link *ds)
/* Make sure the placeholder file is still there. If not,
don't create a link, as the placeholder was probably
removed by a later extraction. */
if (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0
struct fdbase f = fdbase (source);
if (f.fd != BADFD && fstatat (f.fd, f.base, &st, AT_SYMLINK_NOFOLLOW) == 0
&& SAME_INODE (st, *ds)
&& BIRTHTIME_EQ (get_stat_birthtime (&st), ds->birthtime))
{
/* Unlink the placeholder, then create a hard link if possible,
a symbolic link otherwise. */
if (unlinkat (chdir_fd, source, 0) < 0)
struct fdbase f1;
if (unlinkat (f.fd, f.base, 0) < 0)
unlink_error (source);
else if (valid_source
&& (linkat (chdir_fd, valid_source, chdir_fd, source, 0)
== 0))
&& ((f1 = f.fd == BADFD ? f : fdbase1 (valid_source)).fd
!= BADFD)
&& linkat (f1.fd, f1.base, f.fd, f.base, 0) == 0)
;
else if (!ds->is_symlink)
{
if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) < 0)
f1 = f.fd == BADFD ? f : fdbase1 (ds->target);
if (f1.fd == BADFD
|| linkat (f1.fd, f1.base, f.fd, f.base, 0) < 0)
link_error (ds->target, source);
}
else if (symlinkat (ds->target, chdir_fd, source) < 0)
else if (symlinkat (ds->target, f.fd, f.base) < 0)
symlink_error (ds->target, source);
else
{
@@ -1996,9 +2046,14 @@ extract_finish (void)
bool
rename_directory (char *src, char *dst)
{
if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
fixup_delayed_set_stat (src, dst);
else
struct fdbase f1 = fdbase1 (src);
struct fdbase f = f1.fd == BADFD ? f1 : fdbase (dst);
if (f.fd != BADFD && renameat (f1.fd, f1.base, f.fd, f.base) == 0)
{
fdbase_clear ();
fixup_delayed_set_stat (src, dst);
}
else if (f1.fd != BADFD)
{
int e = errno;
@@ -2007,8 +2062,13 @@ rename_directory (char *src, char *dst)
case ENOENT:
if (make_directories (dst, NULL) == 0)
{
if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
return true;
f = fdbase (dst);
if (f.fd != BADFD
&& renameat (f1.fd, f1.base, f.fd, f.base) == 0)
{
fdbase_clear ();
return true;
}
e = errno;
}
break;

View File

@@ -656,21 +656,25 @@ must_be_dot_or_slash (char const *file_name)
}
}
/* Act like rmdir (FILE_NAME) relative to CHDIR_FD.
/* Act like rmdir (FILENAME) relative to chdir_fd, i.e., like rmdir (F).
However, reject attempts to remove a root directory
even on systems that allow such a thing.
Also, do not try to change the removed directory's status later. */
static int
safer_rmdir (const char *file_name)
safer_rmdir (const char *file_name, struct fdbase f)
{
if (!file_name[slashlen (file_name)])
if (f.fd == BADFD)
return -1; /* Preserve errno. */
if (IS_ABSOLUTE_FILE_NAME (f.base))
{
errno = file_name[0] ? EBUSY : ENOENT;
errno = EBUSY;
return -1;
}
if (unlinkat (chdir_fd, file_name, AT_REMOVEDIR) == 0)
if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
{
fdbase_clear ();
remove_delayed_set_stat (file_name);
return 0;
}
@@ -692,10 +696,15 @@ remove_any_file (const char *file_name, enum remove_option option)
non-directory. */
bool try_unlink_first = cannot_unlink_dir ();
struct fdbase f = fdbase (file_name);
if (try_unlink_first)
{
if (unlinkat (chdir_fd, file_name, 0) == 0)
return 1;
if (f.fd != BADFD && unlinkat (f.fd, f.base, 0) == 0)
{
fdbase_clear ();
return 1;
}
/* POSIX 1003.1-2001 requires EPERM when attempting to unlink a
directory without appropriate privileges, but many Linux
@@ -704,13 +713,16 @@ remove_any_file (const char *file_name, enum remove_option option)
return 0;
}
if (safer_rmdir (file_name) == 0)
if (safer_rmdir (file_name, f) == 0)
return 1;
switch (errno)
{
case ENOTDIR:
return !try_unlink_first && unlinkat (chdir_fd, file_name, 0) == 0;
if (try_unlink_first || f.fd == BADFD || unlinkat (f.fd, f.base, 0) < 0)
return 0;
fdbase_clear ();
return 1;
case 0:
case EEXIST:
@@ -751,7 +763,7 @@ remove_any_file (const char *file_name, enum remove_option option)
}
free (directory);
return safer_rmdir (file_name) == 0;
return safer_rmdir (file_name, fdbase (file_name)) == 0;
}
}
break;
@@ -801,13 +813,27 @@ maybe_backup_file (const char *file_name, bool this_is_the_archive)
&& (S_ISBLK (file_stat.st_mode) || S_ISCHR (file_stat.st_mode)))
return true;
after_backup_name = find_backup_file_name (chdir_fd, file_name, backup_type);
struct fdbase f = fdbase (file_name);
if (f.fd == BADFD)
{
open_error (file_name);
return false;
}
idx_t subdirlen = f.base - file_name;
after_backup_name = find_backup_file_name (f.fd, f.base, backup_type);
if (! after_backup_name)
xalloc_die ();
idx_t after_backup_namelen = strlen (after_backup_name);
after_backup_name = xrealloc (after_backup_name,
subdirlen + after_backup_namelen + 1);
memmove (after_backup_name + subdirlen, after_backup_name,
after_backup_namelen + 1);
memcpy (after_backup_name, file_name, subdirlen);
if (renameat (chdir_fd, before_backup_name, chdir_fd, after_backup_name)
== 0)
if (renameat (f.fd, f.base, f.fd, &after_backup_name[subdirlen]) == 0)
{
if (S_ISLNK (file_stat.st_mode))
fdbase_clear ();
if (verbose_option)
fprintf (stdlis, _("Renaming %s to %s\n"),
quote_n (0, before_backup_name),
@@ -833,8 +859,11 @@ undo_last_backup (void)
{
if (after_backup_name)
{
if (renameat (chdir_fd, after_backup_name, chdir_fd, before_backup_name)
< 0)
struct fdbase f = fdbase (before_backup_name);
if (f.fd == BADFD
|| (renameat (f.fd, &after_backup_name[f.base - before_backup_name],
f.fd, f.base)
< 0))
{
int e = errno;
paxerror (e, _("%s: Cannot rename to %s"),
@@ -855,7 +884,8 @@ undo_last_backup (void)
int
deref_stat (char const *name, struct stat *buf)
{
return fstatat (chdir_fd, name, buf, fstatat_flags);
struct fdbase f = fdbase (name);
return f.fd == BADFD ? -1 : fstatat (f.fd, f.base, buf, fstatat_flags);
}
/* Read from FD into the buffer BUF with COUNT bytes. Attempt to fill
@@ -1020,7 +1050,7 @@ idx_t chdir_current;
similar locations for fstatat, etc. This is an open file
descriptor, or AT_FDCWD if the working directory is current. It is
valid until the next invocation of chdir_do. */
int chdir_fd = AT_FDCWD;
static int chdir_fd = AT_FDCWD;
/* Change to directory I, in a virtual way. This does not actually
invoke chdir; it merely sets chdir_fd to an int suitable as the
@@ -1100,6 +1130,113 @@ chdir_id (void)
}
return curr->id;
}
/* Caches of recent calls to fdbase and fdbase1. */
static struct fdbase_cache
{
/* Length of subdirectory name. If zero, no subdir is cached here:
SUBDIR (if nonnull) is merely a buffer available for use later,
and CHDIR_CURRENT and FD are irrelevant. */
idx_t subdirlen;
/* Index of ancestor of this subdirectory. */
idx_t chdir_current;
/* Buffer containing name of subdirectory relative to the ancestor. */
char *subdir;
/* Number of bytes allocated for SUBDIR. */
idx_t subdiralloc;
/* FD of subdirectory. */
int fd;
} fdbase_cache[2];
/* Clear the fdbase cache. Call this after any action that might
invalidate the cache. Such actions include removing or renaming
directories or symlinks to directories. Call this if in doubt,
e.g., if it is not known whether a removed directory entry is a
symlink to a directory. */
void
fdbase_clear (void)
{
for (int i = 0; i < 2; i++)
{
struct fdbase_cache *c = &fdbase_cache[i];
if (c->subdirlen)
{
if (0 <= c->fd)
close (c->fd);
c->subdirlen = 0;
}
}
}
/* Return an fd open to FILE_NAME's parent directory,
along with the base name of FILE_NAME.
Use the alternate cache if ALTERNATE, the main cache otherwise.
If FILE_NAME is relative, it is relative to chdir_fd.
Return AT_FDCWD if FILE_NAME is relative to the working directory.
Return BADFD (setting errno) on failure. */
static struct fdbase
fdbase_opendir (char const *file_name, bool alternate)
{
char const *name = file_name;
/* Skip past leading "./"s,
but not past the last "./" if that ends the name. */
idx_t dslen = dotslashlen (name);
if (dslen)
{
name += dslen;
if (!*name)
for (name--; *--name != '.'; )
continue;
}
/* For files immediately under CHDIR_FD, and for root directories,
just use CHDIR_FD and NAME. */
char const *base = last_component (name);
idx_t subdirlen = base - name;
if (!subdirlen | !*base)
return (struct fdbase) { .fd = chdir_fd, .base = name };
struct fdbase_cache *c = &fdbase_cache[alternate];
if (! (c->chdir_current == chdir_current
&& c->subdirlen == subdirlen
&& memeq (c->subdir, name, subdirlen)))
{
if (c->subdirlen && 0 <= c->fd)
close (c->fd);
c->chdir_current = chdir_current;
if (c->subdiralloc <= subdirlen)
c->subdir = xpalloc (c->subdir, &c->subdiralloc,
subdirlen - c->subdiralloc + 1, -1, 1);
char *p = mempcpy (c->subdir, name, subdirlen);
*p = '\0';
c->fd = openat (chdir_fd, c->subdir, open_searchdir_flags);
c->subdirlen = c->fd < 0 ? 0 : subdirlen;
if (BADFD != -1 && c->fd < 0)
c->fd = BADFD;
}
return (struct fdbase) { .fd = c->fd, .base = base };
}
struct fdbase
fdbase (char const *name)
{
return fdbase_opendir (name, false);
}
struct fdbase
fdbase1 (char const *name)
{
return fdbase_opendir (name, true);
}
const char *
tar_dirname (void)
@@ -1353,7 +1490,9 @@ tar_savedir (const char *name, bool must_exist)
{
char *ret = NULL;
DIR *dir = NULL;
int fd = openat (chdir_fd, name, open_read_flags | O_DIRECTORY);
struct fdbase f = fdbase (name);
int fd = (f.fd == BADFD ? -1
: openat (f.fd, f.base, open_read_flags | O_DIRECTORY));
if (fd < 0)
{
if (!must_exist && errno == ENOENT)

View File

@@ -1799,8 +1799,9 @@ collect_and_sort_names (void)
}
if (S_ISDIR (st.stat.st_mode))
{
int dir_fd = openat (chdir_fd, name->name,
open_read_flags | O_DIRECTORY);
struct fdbase f = fdbase (name->name);
int dir_fd = (f.fd == BADFD ? -1
: openat (f.fd, f.base, open_read_flags | O_DIRECTORY));
if (dir_fd < 0)
open_diag (name->name);
else

View File

@@ -106,7 +106,10 @@ flush_deferred_unlinks (bool force)
else
fname = p->file_name;
if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) < 0)
struct fdbase f = fdbase (fname);
if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
fdbase_clear ();
else
{
switch (errno)
{
@@ -132,7 +135,10 @@ flush_deferred_unlinks (bool force)
}
else
{
if (unlinkat (chdir_fd, p->file_name, 0) < 0 && errno != ENOENT)
struct fdbase f = fdbase (p->file_name);
if (f.fd != BADFD && unlinkat (f.fd, f.base, 0) == 0)
fdbase_clear ();
else if (errno != ENOENT)
unlink_error (p->file_name);
}
dunlink_reclaim (p);
@@ -166,11 +172,12 @@ flush_deferred_unlinks (bool force)
else
fname = p->file_name;
if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) < 0)
{
if (errno != ENOENT)
rmdir_error (fname);
}
struct fdbase f = fdbase (fname);
if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
fdbase_clear ();
else if (errno != ENOENT)
rmdir_error (fname);
dunlink_reclaim (p);
p = next;
}

View File

@@ -45,7 +45,8 @@ static bool acting_as_filter;
static void
append_file (char *file_name)
{
int handle = openat (chdir_fd, file_name, O_RDONLY | O_BINARY);
struct fdbase f = fdbase (file_name);
int handle = f.fd == BADFD ? -1 : openat (f.fd, f.base, O_RDONLY | O_BINARY);
if (handle < 0)
{

View File

@@ -293,7 +293,8 @@ xattrs__acls_set (struct tar_stat_info const *st,
/* No "default" IEEE 1003.1e ACL set for directory. At this moment,
FILE_NAME may already have inherited default acls from parent
directory; clean them up. */
if (acl_delete_def_file_at (chdir_fd, file_name) < 0)
struct fdbase f1 = fdbase (file_name);
if (f1.fd == BADFD || acl_delete_def_file_at (f1.fd, f1.base) < 0)
warnopt (WARN_XATTR_WRITE, errno,
_("acl_delete_def_file_at: Cannot drop default POSIX ACLs "
"for file '%s'"),
@@ -309,7 +310,8 @@ xattrs__acls_set (struct tar_stat_info const *st,
return;
}
if (acl_set_file_at (chdir_fd, file_name, type, acl) < 0)
struct fdbase f = fdbase (file_name);
if (f.fd == BADFD || acl_set_file_at (f.fd, f.base, type, acl) < 0)
/* warn even if filesystem does not support acls */
warnopt (WARN_XATTR_WRITE, errno,
_ ("acl_set_file_at: Cannot set POSIX ACLs for file '%s'"),
@@ -599,13 +601,16 @@ xattrs__fd_set (char const *file_name, char typeflag,
{
const char *sysname = "setxattrat";
int ret;
struct fdbase f = fdbase (file_name);
if (typeflag != SYMTYPE)
ret = setxattrat (chdir_fd, file_name, attr, ptr, len, 0);
if (f.fd == BADFD)
ret = -1;
else if (typeflag != SYMTYPE)
ret = setxattrat (f.fd, f.base, attr, ptr, len, 0);
else
{
sysname = "lsetxattr";
ret = lsetxattrat (chdir_fd, file_name, attr, ptr, len, 0);
ret = lsetxattrat (f.fd, f.base, attr, ptr, len, 0);
}
if (ret < 0)
@@ -662,14 +667,17 @@ xattrs_selinux_set (MAYBE_UNUSED struct tar_stat_info const *st,
if (!st->cntx_name)
return;
if (typeflag != SYMTYPE)
struct fdbase f = fdbase (file_name);
if (f.fd == BADFD)
ret = -1;
else if (typeflag != SYMTYPE)
{
ret = setfileconat (chdir_fd, file_name, st->cntx_name);
ret = setfileconat (f.fd, f.base, st->cntx_name);
sysname = "setfileconat";
}
else
{
ret = lsetfileconat (chdir_fd, file_name, st->cntx_name);
ret = lsetfileconat (f.fd, f.base, st->cntx_name);
sysname = "lsetfileconat";
}