Browse Source

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 <save-cwd.h>; 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.
Paul Eggert 14 years ago
parent
commit
4bde4f39d0
11 changed files with 150 additions and 116 deletions
  1. 5 1
      gnulib.modules
  2. 1 1
      src/Makefile.am
  3. 1 0
      src/common.h
  4. 9 8
      src/compare.c
  5. 7 5
      src/create.c
  6. 34 40
      src/extract.c
  7. 75 54
      src/misc.c
  8. 2 1
      src/names.c
  9. 2 2
      src/unlink.c
  10. 13 3
      src/update.c
  11. 1 1
      tests/Makefile.am

+ 5 - 1
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

+ 1 - 1
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)

+ 1 - 0
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);

+ 9 - 8
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)
     {

+ 7 - 5
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
 	{

+ 34 - 40
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
 				   <[email protected]> */
 	       || 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;
 	    }

+ 75 - 54
src/misc.c

@@ -21,7 +21,6 @@
 #include <rmt.h>
 #include "common.h"
 #include <quotearg.h>
-#include <save-cwd.h>
 #include <xgetcwd.h>
 #include <unlinkdir.h>
 #include <utimens.h>
@@ -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;
     }
 }
 

+ 2 - 1
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

+ 2 - 2
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);

+ 13 - 3
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)

+ 1 - 1
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)