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);
}
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)
{
&& 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);
}
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)
{
}
- 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)
{
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;
}
{
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,
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;
}
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
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))
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))
{
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)
{
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
{
}
else if (fd == 0)
{
- if (parentfd < 0 && ! top_level)
+ if (!top_level && parent->fd < 0)
{
- errno = - parentfd;
+ errno = - parent->fd;
ok = false;
}
}
}
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);
}
}
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)
< 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 */
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))
{
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
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.
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]);
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);
}
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;
*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)
{
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)
#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);
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;
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));
}
\f
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;
}
}
+ 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)
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);
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
open_error (file_name);
return false;
}
- }
+ }
if (fstat (fd, &st) < 0)
{
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;
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))
|| 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:
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:
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:
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);
/* 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
{
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;
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;
}
}
-/* 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;
}
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
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:
}
free (directory);
- return safer_rmdir (file_name) == 0;
+ return safer_rmdir (file_name, fdbase (file_name)) == 0;
}
}
break;
&& (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 ();
-
- if (renameat (chdir_fd, before_backup_name, chdir_fd, after_backup_name)
- == 0)
+ 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 (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),
{
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"),
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
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
}
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);
+}
+
\f
const char *
tar_dirname (void)
{
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)
}
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
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)
{
}
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);
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;
}
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)
{
/* 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'"),
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'"),
{
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)
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";
}