From 4bde4f39d08f000f7e63a832b08a2525c1262f84 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sat, 18 Sep 2010 23:37:45 -0700 Subject: [PATCH] tar: prefer openat-style functions This change replaces traditional functions like 'open' with the POSIX.1-2008 functions like 'openat'. Mostly this is an internal refactoring change, in preparation for further changes to close some races. * gnulib.modules: Add faccessat, linkat, mkfifoat, renameat, symlinkat. Remove save-cwd. * src/Makefile.am (tar_LDADD): Add $(LIB_EACCESS). * tests/Makefile.am (LDADD): Likewise. * src/common.h (chdir_fd): New extern var. * src/compare.c (diff_file, diff_multivol): Use openat instead of open. * src/create.c (create_archive, restore_parent_fd): Likewise. * src/extract.c (create_placeholder_file): Likewise. * src/names.c (collect_and_sort_names): Likewise. * src/update.c (append_file): Likewise. * src/compare.c (diff_symlink): Use readlinkat instead of readlink. * src/compare.c (diff_file): Use chdir_fd instead of AT_FDCWD. * src/create.c (subfile_open, dump_file0): Likewise. * src/extract.c (fd_chmod, fd_chown, fd_stat, set_stat): (repair_delayed_set_stat, apply_nonancestor_delayed_set_stat): Likewise. * src/extract.c (mark_after_links, file_newer_p, extract_dir): (extract_link, apply_delayed_links): Use fstatat rather than stat or lstat. * src/misc.c (maybe_backup_file, deref_stat): Likewise. * src/extract.c (make_directories): Use mkdirat rather than mkdir. Use faccessat rather than access. This fixes a minor permissions bug when tar is running setuid (who would want to do that?!). (open_output_file): Use openat rather than open. In the process, this removes support for Masscomp's O_CTG files, which aren't compatible with openat's signature. Masscomp! Wow! That's a blast from the past. As far as I know, that operating system hasn't been supported for more than 20 years. (extract_link, apply_delayed_links): Use linkat rather than link. (extract_symlink, apply_delayed_links): Use symlinkat rather than symlink. (extract_node): Use mknodat rather than mknod. (extract_fifo): Use mkfifoat rather than mkfifo. (apply_delayed_links): Use unlinkat rather than unlink or rmdir. * src/misc.c (safer_rmdir, remove_any_file): Likewise. * src/unlink.c (flush_deferred_unlinks): Likewise. * src/extract.c (rename_directory): Use renameat rather than rename. * src/misc.c (maybe_backup_file, undo_last_backup): Likewise. * src/misc.c: Don't include ; no longer needed now that we're using openat etc. (struct wd): Add member fd. Remove members err and fd. All uses changed. (CHDIR_CACHE_SIZE): New constant. (wdcache, wdcache_count, chdir_fd): New vars. (chdir_do): Use openat rather than save_cwd. Keep the cache up to date. This code won't scale well, but is good enough for now. * src/update.c (update_archive): Use openat + fdopendir + streamsavedir rather than savedir. This file is a placeholder. It will be replaced with the actual ChangeLog by make dist. Run make ChangeLog if you wish to create it earlier. --- gnulib.modules | 6 ++- src/Makefile.am | 2 +- src/common.h | 1 + src/compare.c | 17 +++--- src/create.c | 12 +++-- src/extract.c | 74 ++++++++++++-------------- src/misc.c | 129 +++++++++++++++++++++++++++------------------- src/names.c | 3 +- src/unlink.c | 4 +- src/update.c | 16 ++++-- tests/Makefile.am | 2 +- 11 files changed, 150 insertions(+), 116 deletions(-) diff --git a/gnulib.modules b/gnulib.modules index 957d065..d33ee44 100644 --- a/gnulib.modules +++ b/gnulib.modules @@ -12,6 +12,7 @@ dirname error exclude exitfail +faccessat fdopendir fdutimensat fileblocks @@ -31,8 +32,10 @@ hash human inttypes lchown +linkat localcharset mkdtemp +mkfifoat modechange obstack openat @@ -41,9 +44,9 @@ progname quote quotearg readlinkat +renameat rpmatch safe-read -save-cwd savedir setenv snprintf @@ -55,6 +58,7 @@ strdup-posix strerror strtol strtoul +symlinkat timespec unlinkdir unlocked-io diff --git a/src/Makefile.am b/src/Makefile.am index fcbac33..de310f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,4 +48,4 @@ INCLUDES = -I$(top_srcdir)/gnu -I../ -I../gnu -I$(top_srcdir)/lib -I../lib LDADD = ../lib/libtar.a ../gnu/libgnu.a $(LIBINTL) $(LIBICONV) -tar_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +tar_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) diff --git a/src/common.h b/src/common.h index 2bc1d71..12a4889 100644 --- a/src/common.h +++ b/src/common.h @@ -614,6 +614,7 @@ void undo_last_backup (void); int deref_stat (bool deref, char const *name, struct stat *buf); extern int chdir_current; +extern int chdir_fd; int chdir_arg (char const *dir); void chdir_do (int dir); int chdir_count (void); diff --git a/src/compare.c b/src/compare.c index 204c5dc..1ee9bcb 100644 --- a/src/compare.c +++ b/src/compare.c @@ -222,9 +222,9 @@ diff_file (void) ? O_NOATIME : 0); - diff_handle = open (file_name, - (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY - | O_NONBLOCK | atime_flag)); + diff_handle = openat (chdir_fd, file_name, + (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY + | O_NONBLOCK | atime_flag)); if (diff_handle < 0) { @@ -244,7 +244,7 @@ diff_file (void) if (atime_preserve_option == replace_atime_preserve) { struct timespec atime = get_stat_atime (&stat_data); - if (set_file_atime (diff_handle, AT_FDCWD, file_name, + if (set_file_atime (diff_handle, chdir_fd, file_name, atime, 0) != 0) utime_error (file_name); @@ -279,7 +279,8 @@ diff_symlink (void) size_t len = strlen (current_stat_info.link_name); char *linkbuf = alloca (len + 1); - int status = readlink (current_stat_info.file_name, linkbuf, len + 1); + int status = readlinkat (chdir_fd, current_stat_info.file_name, + linkbuf, len + 1); if (status < 0) { @@ -428,9 +429,9 @@ diff_multivol (void) } - fd = open (current_stat_info.file_name, - (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK - | atime_flag)); + fd = openat (chdir_fd, current_stat_info.file_name, + (O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK + | atime_flag)); if (fd < 0) { diff --git a/src/create.c b/src/create.c index 0d22e96..26993c2 100644 --- a/src/create.c +++ b/src/create.c @@ -1345,7 +1345,8 @@ create_archive (void) { if (! st.orig_file_name) { - int fd = open (p->name, open_searchdir_flags); + int fd = openat (chdir_fd, p->name, + open_searchdir_flags); if (fd < 0) { open_diag (p->name); @@ -1549,7 +1550,7 @@ subfile_open (struct tar_stat_info const *dir, char const *file, int flags) gettext (""); } - while ((fd = openat (dir ? dir->fd : AT_FDCWD, file, flags)) < 0 + while ((fd = openat (dir ? dir->fd : chdir_fd, file, flags)) < 0 && open_failure_recover (dir)) continue; return fd; @@ -1580,7 +1581,8 @@ restore_parent_fd (struct tar_stat_info const *st) if (parentfd < 0) { - int origfd = open (parent->orig_file_name, open_searchdir_flags); + int origfd = openat (chdir_fd, parent->orig_file_name, + open_searchdir_flags); if (0 <= origfd) { if (fstat (parentfd, &parentstat) == 0 @@ -1615,7 +1617,7 @@ 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 ? AT_FDCWD : parent->fd; + int parentfd = top_level ? chdir_fd : parent->fd; void (*diag) (char const *) = 0; if (interactive_option && !confirm ("add", p)) @@ -1723,7 +1725,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) ok = dump_dir (st); fd = st->fd; - parentfd = top_level ? AT_FDCWD : parent->fd; + parentfd = top_level ? chdir_fd : parent->fd; } else { diff --git a/src/extract.c b/src/extract.c index 750ca26..2730a03 100644 --- a/src/extract.c +++ b/src/extract.c @@ -178,7 +178,7 @@ fd_chmod (int fd, char const *file, mode_t mode, int atflag) if (result == 0 || implemented (errno)) return result; } - return fchmodat (AT_FDCWD, file, mode, atflag); + return fchmodat (chdir_fd, file, mode, atflag); } /* Use fchown if possible, fchownat otherwise. */ @@ -191,7 +191,7 @@ fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag) if (result == 0 || implemented (errno)) return result; } - return fchownat (AT_FDCWD, file, uid, gid, atflag); + return fchownat (chdir_fd, file, uid, gid, atflag); } /* Use fstat if possible, fstatat otherwise. */ @@ -200,7 +200,7 @@ fd_stat (int fd, char const *file, struct stat *st, int atflag) { return (0 <= fd ? fstat (fd, st) - : fstatat (AT_FDCWD, file, st, atflag)); + : fstatat (chdir_fd, file, st, atflag)); } /* Set the mode for FILE_NAME to MODE. @@ -325,7 +325,7 @@ set_stat (char const *file_name, ts[0].tv_nsec = UTIME_OMIT; ts[1] = st->mtime; - if (fdutimensat (fd, AT_FDCWD, file_name, ts, atflag) == 0) + if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0) { if (incremental_option) check_time (file_name, ts[0]); @@ -375,7 +375,7 @@ mark_after_links (struct delayed_set_stat *head) struct stat st; h->after_links = 1; - if (stat (h->file_name, &st) != 0) + if (fstatat (chdir_fd, h->file_name, &st, 0) != 0) stat_error (h->file_name); else { @@ -449,7 +449,7 @@ repair_delayed_set_stat (char const *dir, for (data = delayed_set_stat_head; data; data = data->next) { struct stat st; - if (fstatat (AT_FDCWD, data->file_name, &st, data->atflag) != 0) + if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0) { stat_error (data->file_name); return; @@ -512,7 +512,7 @@ make_directories (char *file_name) *cursor = '\0'; /* truncate the name there */ desired_mode = MODE_RWX & ~ newdir_umask; mode = desired_mode | (we_are_root ? 0 : MODE_WXUSR); - status = mkdir (file_name, mode); + status = mkdirat (chdir_fd, file_name, mode); if (status == 0) { @@ -538,7 +538,7 @@ make_directories (char *file_name) this. Reported by Warren Hyde */ || ERRNO_IS_EACCES) /* Turbo C mkdir gives a funny errno. */ - && access (file_name, W_OK) == 0) + && faccessat (chdir_fd, file_name, W_OK, AT_EACCESS) == 0) continue; /* Some other error in the mkdir. We return to the caller. */ @@ -553,7 +553,7 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) { struct stat st; - if (stat (file_name, &st)) + if (fstatat (chdir_fd, file_name, &st, 0)) { if (errno != ENOENT) { @@ -671,7 +671,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) if (check_for_renamed_directories) { - if (fstatat (AT_FDCWD, data->file_name, &st, data->atflag) != 0) + if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0) { stat_error (data->file_name); skip_this_one = 1; @@ -727,7 +727,7 @@ extract_dir (char *file_name, int typeflag) { struct stat st; - if (stat (".", &st) != 0) + if (fstatat (chdir_fd, ".", &st, 0) != 0) stat_diag ("."); else root_device = st.st_dev; @@ -769,7 +769,7 @@ extract_dir (char *file_name, int typeflag) || old_files_option == OVERWRITE_OLD_FILES)) { struct stat st; - if (stat (file_name, &st) == 0) + if (fstatat (chdir_fd, file_name, &st, 0) == 0) { current_mode = st.st_mode; current_mode_mask = ALL_MODE_BITS; @@ -826,16 +826,6 @@ open_output_file (char const *file_name, int typeflag, mode_t mode, int openflag = (O_WRONLY | O_BINARY | O_CREAT | (overwriting_old_files ? O_TRUNC : O_EXCL)); -#if O_CTG - /* Contiguous files (on the Masscomp) have to specify the size in - the open call that creates them. */ - - if (typeflag == CONTTYPE) - fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size); - else - fd = open (file_name, openflag, mode); - -#else /* not O_CTG */ if (typeflag == CONTTYPE) { static int conttype_diagnosed; @@ -847,10 +837,8 @@ open_output_file (char const *file_name, int typeflag, mode_t mode, (0, 0, _("Extracting contiguous files as regular files"))); } } - fd = open (file_name, openflag, mode); - -#endif /* not O_CTG */ + fd = openat (chdir_fd, file_name, openflag, mode); if (0 <= fd) { if (overwriting_old_files) @@ -1004,7 +992,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) int fd; struct stat st; - while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) + while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) { switch (maybe_recoverable (file_name, interdir_made)) { @@ -1084,13 +1072,14 @@ extract_link (char *file_name, int typeflag) { struct stat st1, st2; int e; - int status = link (link_name, file_name); + int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0); e = errno; if (status == 0) { struct delayed_link *ds = delayed_link_head; - if (ds && lstat (link_name, &st1) == 0) + if (ds + && fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0) for (; ds; ds = ds->next) if (ds->change_dir == chdir_current && ds->dev == st1.st_dev @@ -1107,8 +1096,10 @@ extract_link (char *file_name, int typeflag) return 0; } else if ((e == EEXIST && strcmp (link_name, file_name) == 0) - || (lstat (link_name, &st1) == 0 - && lstat (file_name, &st2) == 0 + || ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) + == 0) + && (fstatat (chdir_fd, file_name, &st2, AT_SYMLINK_NOFOLLOW) + == 0) && st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)) return 0; @@ -1138,7 +1129,7 @@ extract_symlink (char *file_name, int typeflag) || contains_dot_dot (current_stat_info.link_name))) return create_placeholder_file (file_name, true, &interdir_made); - while (symlink (current_stat_info.link_name, file_name)) + while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) != 0) switch (maybe_recoverable (file_name, &interdir_made)) { case RECOVER_OK: @@ -1178,7 +1169,8 @@ extract_node (char *file_name, int typeflag) mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0)); - while (mknod (file_name, mode, current_stat_info.stat.st_rdev) != 0) + while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev) + != 0) switch (maybe_recoverable (file_name, &interdir_made)) { case RECOVER_OK: @@ -1207,7 +1199,7 @@ extract_fifo (char *file_name, int typeflag) mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0)); - while (mkfifo (file_name, mode) != 0) + while (mkfifoat (chdir_fd, file_name, mode) != 0) switch (maybe_recoverable (file_name, &interdir_made)) { case RECOVER_OK: @@ -1454,23 +1446,25 @@ apply_delayed_links (void) /* 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 (lstat (source, &st) == 0 + if (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0 && st.st_dev == ds->dev && st.st_ino == ds->ino && timespec_cmp (get_stat_ctime (&st), ds->ctime) == 0) { /* Unlink the placeholder, then create a hard link if possible, a symbolic link otherwise. */ - if (unlink (source) != 0) + if (unlinkat (chdir_fd, source, 0) != 0) unlink_error (source); - else if (valid_source && link (valid_source, source) == 0) + else if (valid_source + && (linkat (chdir_fd, valid_source, chdir_fd, source, 0) + == 0)) ; else if (!ds->is_symlink) { - if (link (ds->target, source) != 0) + if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) != 0) link_error (ds->target, source); } - else if (symlink (ds->target, source) != 0) + else if (symlinkat (ds->target, chdir_fd, source) != 0) symlink_error (ds->target, source); else { @@ -1523,7 +1517,7 @@ extract_finish (void) bool rename_directory (char *src, char *dst) { - if (rename (src, dst)) + if (renameat (chdir_fd, src, chdir_fd, dst) != 0) { int e = errno; @@ -1532,7 +1526,7 @@ rename_directory (char *src, char *dst) case ENOENT: if (make_directories (dst)) { - if (rename (src, dst) == 0) + if (renameat (chdir_fd, src, chdir_fd, dst) == 0) return true; e = errno; } diff --git a/src/misc.c b/src/misc.c index 15161ae..d94791f 100644 --- a/src/misc.c +++ b/src/misc.c @@ -21,7 +21,6 @@ #include #include "common.h" #include -#include #include #include #include @@ -432,7 +431,7 @@ safer_rmdir (const char *file_name) return -1; } - return rmdir (file_name); + return unlinkat (chdir_fd, file_name, AT_REMOVEDIR); } /* Remove FILE_NAME, returning 1 on success. If FILE_NAME is a directory, @@ -452,7 +451,7 @@ remove_any_file (const char *file_name, enum remove_option option) if (try_unlink_first) { - if (unlink (file_name) == 0) + if (unlinkat (chdir_fd, file_name, 0) == 0) return 1; /* POSIX 1003.1-2001 requires EPERM when attempting to unlink a @@ -468,7 +467,7 @@ remove_any_file (const char *file_name, enum remove_option option) switch (errno) { case ENOTDIR: - return !try_unlink_first && unlink (file_name) == 0; + return !try_unlink_first && unlinkat (chdir_fd, file_name, 0) == 0; case 0: case EEXIST: @@ -545,7 +544,7 @@ maybe_backup_file (const char *file_name, bool this_is_the_archive) if (this_is_the_archive && _remdev (file_name)) return true; - if (stat (file_name, &file_stat)) + if (fstatat (chdir_fd, file_name, &file_stat, 0)) { if (errno == ENOENT) return true; @@ -565,7 +564,8 @@ maybe_backup_file (const char *file_name, bool this_is_the_archive) if (! after_backup_name) xalloc_die (); - if (rename (before_backup_name, after_backup_name) == 0) + if (renameat (chdir_fd, before_backup_name, chdir_fd, after_backup_name) + == 0) { if (verbose_option) fprintf (stdlis, _("Renaming %s to %s\n"), @@ -592,7 +592,8 @@ undo_last_backup (void) { if (after_backup_name) { - if (rename (after_backup_name, before_backup_name) != 0) + if (renameat (chdir_fd, after_backup_name, chdir_fd, before_backup_name) + != 0) { int e = errno; ERROR ((0, e, _("%s: Cannot rename to %s"), @@ -611,7 +612,7 @@ undo_last_backup (void) int deref_stat (bool deref, char const *name, struct stat *buf) { - return deref ? stat (name, buf) : lstat (name, buf); + return fstatat (chdir_fd, name, buf, deref ? 0 : AT_SYMLINK_NOFOLLOW); } /* Set FD's (i.e., assuming the working directory is PARENTFD, FILE's) @@ -633,13 +634,10 @@ struct wd /* The directory's name. */ char const *name; - /* A negative value if no attempt has been made to save the - directory, 0 if it was saved successfully, and a positive errno - value if it was not saved successfully. */ - int err; - - /* The saved version of the directory, if ERR == 0. */ - struct saved_cwd saved_cwd; + /* If nonzero, the file descriptor of the directory, or AT_FDCWD if + the working directory. If zero, the directory needs to be opened + to be used. */ + int fd; }; /* A vector of chdir targets. wd[0] is the initial working directory. */ @@ -651,6 +649,19 @@ static size_t wd_count; /* The allocated size of the vector. */ static size_t wd_alloc; +/* The maximum number of chdir targets with open directories. + Don't make it too large, as many operating systems have a small + limit on the number of open file descriptors. Also, the current + implementation does not scale well. */ +enum { CHDIR_CACHE_SIZE = 16 }; + +/* Indexes into WD of chdir targets with open file descriptors, sorted + most-recently used first. Zero indexes are unused. */ +static int wdcache[CHDIR_CACHE_SIZE]; + +/* Number of nonzero entries in WDCACHE. */ +static size_t wdcache_count; + int chdir_count () { @@ -677,7 +688,7 @@ chdir_arg (char const *dir) if (! wd_count) { wd[wd_count].name = "."; - wd[wd_count].err = -1; + wd[wd_count].fd = AT_FDCWD; wd_count++; } } @@ -694,66 +705,76 @@ chdir_arg (char const *dir) } wd[wd_count].name = dir; - wd[wd_count].err = -1; + wd[wd_count].fd = 0; return wd_count++; } /* Index of current directory. */ int chdir_current; -/* Change to directory I. If I is 0, change to the initial working - directory; otherwise, I must be a value returned by chdir_arg. */ +/* Value suitable for use as the first argument to openat, and in + 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; + +/* 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 + first argument for openat, etc. If I is 0, change to the initial + working directory; otherwise, I must be a value returned by + chdir_arg. */ void chdir_do (int i) { if (chdir_current != i) { - struct wd *prev = &wd[chdir_current]; + static size_t counter; struct wd *curr = &wd[i]; + int fd = curr->fd; - if (prev->err < 0) + if (! fd) { - prev->err = 0; - if (save_cwd (&prev->saved_cwd) != 0) - prev->err = errno; - else if (0 <= prev->saved_cwd.desc) + if (! IS_ABSOLUTE_FILE_NAME (curr->name)) + chdir_do (i - 1); + fd = openat (chdir_fd, curr->name, open_searchdir_flags); + if (fd < 0) + open_fatal (curr->name); + + curr->fd = fd; + + /* Add I to the cache, tossing out the lowest-ranking entry if the + cache is full. */ + if (wdcache_count < CHDIR_CACHE_SIZE) + wdcache[wdcache_count++] = i; + else { - /* Make sure we still have at least one descriptor available. */ - int fd1 = prev->saved_cwd.desc; - int fd2 = dup (fd1); - if (0 <= fd2) - close (fd2); - else if (errno == EMFILE) - { - /* Force restore_cwd to use chdir_long. */ - close (fd1); - prev->saved_cwd.desc = -1; - prev->saved_cwd.name = xgetcwd (); - if (! prev->saved_cwd.name) - prev->err = errno; - } - else - prev->err = errno; + struct wd *stale = &wd[wdcache[CHDIR_CACHE_SIZE - 1]]; + if (close (stale->fd) != 0) + close_diag (stale->name); + stale->fd = 0; + wdcache[CHDIR_CACHE_SIZE - 1] = i; } } - if (0 <= curr->err) + if (0 < fd) { - int err = curr->err; - if (err == 0 && restore_cwd (&curr->saved_cwd) != 0) - err = errno; - if (err) - FATAL_ERROR ((0, err, _("Cannot restore working directory"))); - } - else - { - if (i && ! ISSLASH (curr->name[0])) - chdir_do (i - 1); - if (chdir (curr->name) != 0) - chdir_fatal (curr->name); + /* Move the i value to the front of the cache. This is + O(CHDIR_CACHE_SIZE), but the cache is small. */ + size_t ci; + int prev = wdcache[0]; + for (ci = 1; prev != i; ci++) + { + int curr = wdcache[ci]; + wdcache[ci] = prev; + if (curr == i) + break; + prev = curr; + } + wdcache[0] = i; } chdir_current = i; + chdir_fd = fd; } } diff --git a/src/names.c b/src/names.c index c38ccb6..88d8be2 100644 --- a/src/names.c +++ b/src/names.c @@ -988,7 +988,8 @@ collect_and_sort_names (void) } if (S_ISDIR (st.stat.st_mode)) { - int dir_fd = open (name->name, open_read_flags | O_DIRECTORY); + int dir_fd = openat (chdir_fd, name->name, + 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 817ab4a..b281636 100644 --- a/src/unlink.c +++ b/src/unlink.c @@ -77,7 +77,7 @@ flush_deferred_unlinks (bool force) { if (p->is_dir) { - if (rmdir (p->file_name) != 0) + if (unlinkat (chdir_fd, p->file_name, AT_REMOVEDIR) != 0) { switch (errno) { @@ -101,7 +101,7 @@ flush_deferred_unlinks (bool force) } else { - if (unlink (p->file_name) != 0 && errno != ENOENT) + if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT) unlink_error (p->file_name); } dunlink_reclaim (p); diff --git a/src/update.c b/src/update.c index 53ce553..de2786d 100644 --- a/src/update.c +++ b/src/update.c @@ -47,7 +47,7 @@ char *output_start; static void append_file (char *file_name) { - int handle = open (file_name, O_RDONLY | O_BINARY); + int handle = openat (chdir_fd, file_name, O_RDONLY | O_BINARY); struct stat stat_data; if (handle < 0) @@ -144,8 +144,13 @@ update_archive (void) if (S_ISDIR (s.st_mode)) { char *p, *dirp; - dirp = savedir (name->name); - if (!dirp) + DIR *stream; + int fd = openat (chdir_fd, name->name, + open_read_flags | O_DIRECTORY); + if (fd < 0) + open_error (name->name); + else if (! ((stream = fdopendir (fd)) + && (dirp = streamsavedir (stream)))) savedir_error (name->name); else { @@ -160,6 +165,11 @@ update_archive (void) remname (name); } + + if (stream + ? closedir (stream) != 0 + : 0 <= fd && close (fd) != 0) + savedir_error (name->name); } else if (tar_timespec_cmp (get_stat_mtime (&s), current_stat_info.mtime) diff --git a/tests/Makefile.am b/tests/Makefile.am index 5cfd847..d378a37 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -198,4 +198,4 @@ genfile_SOURCES = genfile.c argcv.c argcv.h localedir = $(datadir)/locale INCLUDES = -I$(top_srcdir)/gnu -I../gnu -I$(top_srcdir)/gnu -I$(top_srcdir)/lib AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" -LDADD = ../gnu/libgnu.a $(LIBINTL) $(LIB_CLOCK_GETTIME) +LDADD = ../gnu/libgnu.a $(LIBINTL) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) -- 2.45.2