From: Paul Eggert Date: Thu, 13 Nov 2025 01:33:11 +0000 (-0800) Subject: Cache parent directories X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bdd773d028cd21f9b76b8cc306c57e0db3607e82;p=thirdparty%2Ftar.git 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. --- diff --git a/src/common.h b/src/common.h index 6296eacd..92032d22 100644 --- a/src/common.h +++ b/src/common.h @@ -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); diff --git a/src/compare.c b/src/compare.c index 71d51cc8..3135b92e 100644 --- a/src/compare.c +++ b/src/compare.c @@ -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) { diff --git a/src/create.c b/src/create.c index 274d760a..aa5acb63 100644 --- a/src/create.c +++ b/src/create.c @@ -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)) { diff --git a/src/extract.c b/src/extract.c index 179a2d7b..7b11abbd 100644 --- a/src/extract.c +++ b/src/extract.c @@ -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; diff --git a/src/misc.c b/src/misc.c index 77981412..7939ce93 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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 (); - - 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), @@ -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) diff --git a/src/names.c b/src/names.c index c29d3aa3..4d8b6ee7 100644 --- a/src/names.c +++ b/src/names.c @@ -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 diff --git a/src/unlink.c b/src/unlink.c index a1f754a4..d042d2ed 100644 --- a/src/unlink.c +++ b/src/unlink.c @@ -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; } diff --git a/src/update.c b/src/update.c index b693b838..fbae4f55 100644 --- a/src/update.c +++ b/src/update.c @@ -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) { diff --git a/src/xattrs.c b/src/xattrs.c index 399eb7fb..26e23ef7 100644 --- a/src/xattrs.c +++ b/src/xattrs.c @@ -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"; }