From: Jim Meyering <meyering@redhat.com>
Date: Fri, 8 Oct 2010 16:42:59 +0000 (+0200)
Subject: fdopendir: fix a bug on systems lacking openat and /proc support
X-Git-Tag: v0.1~3703
X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=a1885e2dc2a8f1c79ead4e6ceca1a53827b89048;p=gnulib.git

fdopendir: fix a bug on systems lacking openat and /proc support

OpenBSD 4.7 is one such system.  The most noticeable effect was
failure of any application making nontrivial use of fts: rm, du,
chown, chmod etc.  E.g., "mkdir -p a/b; ./rm -rf a" would fail with
  ./rm: traversal failed: `a': Bad file descriptor
Debugging that, you see that even though FD 6 was closed just
prior to the opendir call in fd_clone_opendir, its resulting
dir->dd_fd was 8, rather than the expected value of 6:

Breakpoint 3, fdopendir_with_dup (fd=6, older_dupfd=-1) at fdopendir.c:93
93                close (fd);
(gdb) n
94                dir = fd_clone_opendir (dupfd);
(gdb) n
95                saved_errno = errno;
(gdb) p dir->dd_fd
$11 = 8

Notice how it closes FD 6, then gets a DIR* pointer using FD 8.
The problem is that on OpenBSD, fd_clone_opendir has to resort
to using the old-style save/restore CWD mechanism, due to its
lack of openat/proc support, and *that* would steal the FD (6)
that opendir was supposed to use.

The fix is to squirrel away the desired FD so that save_cwd uses a
different one, and then free the dest FD right before calling opendir.
That guarantees opendir will use the required file descriptor.

* lib/fdopendir.c (fd_clone_opendir): Handle the above.
---

diff --git a/ChangeLog b/ChangeLog
index adbcd1bf72..472c671006 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2010-10-08  Jim Meyering  <meyering@redhat.com>
+
+	fdopendir: fix a bug on systems lacking openat and /proc support
+	OpenBSD 4.7 is one such system.  The most noticeable effect was
+	failure of any application making nontrivial use of fts: rm, du,
+	chown, chmod etc.  E.g., "mkdir -p a/b; ./rm -rf a" would fail with
+	  ./rm: traversal failed: `a': Bad file descriptor
+	Debugging that, you see that even though FD 6 was closed just
+	prior to the opendir call in fd_clone_opendir, its resulting
+	dir->dd_fd was 8, rather than the expected value of 6:
+
+	Breakpoint 3, fdopendir_with_dup (fd=6, older_dupfd=-1) at fdopendir.c:93
+	93                close (fd);
+	(gdb) n
+	94                dir = fd_clone_opendir (dupfd);
+	(gdb) n
+	95                saved_errno = errno;
+	(gdb) p dir->dd_fd
+	$11 = 8
+
+	Notice how it closes FD 6, then gets a DIR* pointer using FD 8.
+	The problem is that on OpenBSD, fd_clone_opendir has to resort
+	to using the old-style save/restore CWD mechanism, due to its
+	lack of openat/proc support, and *that* would steal the FD (6)
+	that opendir was supposed to use.
+
+	The fix is to squirrel away the desired FD so that save_cwd uses a
+	different one, and then free the dest FD right before calling opendir.
+	That guarantees opendir will use the required file descriptor.
+
+	* lib/fdopendir.c (fd_clone_opendir): Handle the above.
+
 2010-10-08  Bruno Haible  <bruno@clisp.org>
 
 	sys_select: Avoid warning due to undeclared memset() on OpenBSD 4.5.
diff --git a/lib/fdopendir.c b/lib/fdopendir.c
index 4adef63791..59826ae4aa 100644
--- a/lib/fdopendir.c
+++ b/lib/fdopendir.c
@@ -140,10 +140,23 @@ fd_clone_opendir (int fd)
         dir = opendir (name);
       saved_errno = errno;
 # else /* !REPLACE_FCHDIR */
+
+      /* Occupy the destination FD slot, so that save_cwd cannot hijack it.  */
+      int fd_reserve = dup (fd);
+      if (fd_reserve < 0)
+        {
+          saved_errno = errno;
+          dir = NULL;
+          goto fail;
+        }
+
       struct saved_cwd saved_cwd;
       if (save_cwd (&saved_cwd) != 0)
         openat_save_fail (errno);
 
+      /* Liberate the target file descriptor, so that opendir uses it.  */
+      close (fd_reserve);
+
       if (fchdir (fd) != 0)
         {
           dir = NULL;
@@ -162,6 +175,7 @@ fd_clone_opendir (int fd)
 # endif /* !REPLACE_FCHDIR */
     }
 
+ fail:
   if (proc_file != buf)
     free (proc_file);
   errno = saved_errno;