changeset 11922:9750527ffbab

fchdir: port to mingw * m4/fchdir.m4 (gl_FUNC_FCHDIR): Check for mingw bug. * lib/open.c (open) [FCHDIR_REPLACEMENT]: If directories can't be opened, then use a substitute. * lib/sys_stat.in.h (fstat) [REPLACE_OPEN_DIRECTORY]: Declare replacement. * lib/fchdir.c (fstat) [REPLACE_OPEN_DIRECTORY]: Implement it. (_gl_register_fd): No need to check stat if open already filters all directories. (fchdir): Fix error condition to match POSIX. * modules/fchdir (Depends-on): Add sys_stat. * doc/posix-functions/open.texi (open): Document the limitation. * modules/fchdir-tests: New file. * tests/test-fchdir.c: Likewise. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Mon, 31 Aug 2009 06:09:08 -0600
parents c455cd9841a8
children 61821cbbceb2
files ChangeLog doc/posix-functions/open.texi lib/fchdir.c lib/open.c lib/sys_stat.in.h m4/fchdir.m4 modules/fchdir modules/fchdir-tests tests/test-fchdir.c
diffstat 9 files changed, 211 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,20 @@
 2009-08-31  Eric Blake  <ebb9@byu.net>
 
+	fchdir: port to mingw
+	* m4/fchdir.m4 (gl_FUNC_FCHDIR): Check for mingw bug.
+	* lib/open.c (open) [FCHDIR_REPLACEMENT]: If directories can't be
+	opened, then use a substitute.
+	* lib/sys_stat.in.h (fstat) [REPLACE_OPEN_DIRECTORY]: Declare
+	replacement.
+	* lib/fchdir.c (fstat) [REPLACE_OPEN_DIRECTORY]: Implement it.
+	(_gl_register_fd): No need to check stat if open already filters
+	all directories.
+	(fchdir): Fix error condition to match POSIX.
+	* modules/fchdir (Depends-on): Add sys_stat.
+	* doc/posix-functions/open.texi (open): Document the limitation.
+	* modules/fchdir-tests: New file.
+	* tests/test-fchdir.c: Likewise.
+
 	canonicalize: allow cross-testing from cygwin to mingw
 	* modules/canonicalize-tests (configure.ac): Define HAVE_SYMLINK.
 	(Makefile.am): Pass it through TESTS_ENVIRONMENT.
--- a/doc/posix-functions/open.texi
+++ b/doc/posix-functions/open.texi
@@ -4,9 +4,9 @@
 
 POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/open.html}
 
-Gnulib module: open
+Gnulib module: open, fchdir
 
-Portability problems fixed by Gnulib:
+Portability problems fixed by the Gnulib module open:
 @itemize
 @item
 This function does not fail when the file name argument ends in a slash
@@ -18,6 +18,13 @@
 recognize the @file{/dev/null} filename.
 @end itemize
 
+Portability problems fixed by the Gnulib module fchdir:
+@itemize
+@item
+On Windows platforms (excluding Cygwin), this function fails to open a
+read-only descriptor for directories.
+@end itemize
+
 Portability problems not fixed by Gnulib:
 @itemize
 @item
--- a/lib/fchdir.c
+++ b/lib/fchdir.c
@@ -30,8 +30,17 @@
 
 #include "canonicalize.h"
 
+#ifndef REPLACE_OPEN_DIRECTORY
+# define REPLACE_OPEN_DIRECTORY 0
+#endif
+
 /* This replacement assumes that a directory is not renamed while opened
-   through a file descriptor.  */
+   through a file descriptor.
+
+   FIXME: On mingw, this would be possible to enforce if we were to
+   also open a HANDLE to each directory currently visited by a file
+   descriptor, since mingw refuses to rename any in-use file system
+   object.  */
 
 /* Array of file descriptors opened.  If it points to a directory, it stores
    info about this directory; otherwise it stores an errno value of ENOTDIR.  */
@@ -77,6 +86,8 @@
 /* Hook into the gnulib replacements for open() and close() to keep track
    of the open file descriptors.  */
 
+/* Close FD, cleaning up any fd to name mapping if fd was visiting a
+   directory.  */
 void
 _gl_unregister_fd (int fd)
 {
@@ -89,6 +100,9 @@
     }
 }
 
+/* Mark FD as visiting FILENAME.  FD must be positive, and refer to an
+   open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero, this
+   should only be called if FD is visiting a directory.  */
 void
 _gl_register_fd (int fd, const char *filename)
 {
@@ -96,14 +110,28 @@
 
   ensure_dirs_slot (fd);
   if (fd < dirs_allocated
-      && fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
+      && (REPLACE_OPEN_DIRECTORY
+          || (fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))))
     {
       dirs[fd].name = canonicalize_file_name (filename);
       if (dirs[fd].name == NULL)
-	dirs[fd].saved_errno = errno;
+        dirs[fd].saved_errno = errno;
     }
 }
 
+/* Return stat information about FD in STATBUF.  Needed when
+   rpl_open() used a dummy file to work around an open() that can't
+   normally visit directories.  */
+#if REPLACE_OPEN_DIRECTORY
+int
+rpl_fstat (int fd, struct stat *statbuf)
+{
+  if (0 <= fd && fd <= dirs_allocated && dirs[fd].name != NULL)
+    return stat (dirs[fd].name, statbuf);
+  return fstat (fd, statbuf);
+}
+#endif
+
 /* Override opendir() and closedir(), to keep track of the open file
    descriptors.  Needed because there is a function dirfd().  */
 
@@ -218,27 +246,16 @@
 int
 fchdir (int fd)
 {
-  if (fd >= 0)
+  if (fd >= 0 && fd < dirs_allocated && dirs[fd].name != NULL)
+    return chdir (dirs[fd].name);
+  /* At this point, fd is either invalid, or open but not a directory.
+     If dup2 fails, errno is correctly EBADF.  */
+  if (0 <= fd)
     {
-      if (fd < dirs_allocated)
-	{
-	  if (dirs[fd].name != NULL)
-	    return chdir (dirs[fd].name);
-	  else
-	    {
-	      errno = dirs[fd].saved_errno;
-	      return -1;
-	    }
-	}
-      else
-	{
-	  errno = ENOMEM;
-	  return -1;
-	}
+      if (dup2 (fd, fd) == fd)
+        errno = ENOTDIR;
     }
   else
-    {
-      errno = EBADF;
-      return -1;
-    }
+    errno = EBADF;
+  return -1;
 }
--- a/lib/open.c
+++ b/lib/open.c
@@ -39,6 +39,10 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#ifndef REPLACE_OPEN_DIRECTORY
+# define REPLACE_OPEN_DIRECTORY 0
+#endif
+
 int
 open (const char *filename, int flags, ...)
 {
@@ -98,6 +102,29 @@
 
   fd = orig_open (filename, flags, mode);
 
+#ifdef FCHDIR_REPLACEMENT
+  /* Implementing fchdir and fdopendir requires the ability to open a
+     directory file descriptor.  If open doesn't support that (as on
+     mingw), we use a dummy file that behaves the same as directories
+     on Linux (ie. always reports EOF on attempts to read()), and
+     override fstat() in fchdir.c to hide the fact that we have a
+     dummy.  */
+  if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES
+      && (mode & O_ACCMODE) == O_RDONLY)
+    {
+      struct stat statbuf;
+      if (stat (filename, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))
+        {
+          /* Maximum recursion depth of 1.  */
+          fd = open ("/dev/null", flags, mode);
+          if (0 <= fd)
+            _gl_register_fd (fd, filename);
+        }
+      else
+        errno = EACCES;
+    }
+#endif
+
 #if OPEN_TRAILING_SLASH_BUG
   /* If the filename ends in a slash and fd does not refer to a directory,
      then fail.
@@ -129,7 +156,7 @@
 #endif
 
 #ifdef FCHDIR_REPLACEMENT
-  if (fd >= 0)
+  if (!REPLACE_OPEN_DIRECTORY && 0 <= fd)
     _gl_register_fd (fd, filename);
 #endif
 
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -302,6 +302,10 @@
    lstat (p, b))
 #endif
 
+#if defined FCHDIR_REPLACEMENT && REPLACE_OPEN_DIRECTORY
+# define fstat rpl_fstat
+extern int fstat (int fd, struct stat *buf);
+#endif
 
 #if @REPLACE_MKDIR@
 # undef mkdir
--- a/m4/fchdir.m4
+++ b/m4/fchdir.m4
@@ -1,4 +1,4 @@
-# fchdir.m4 serial 7
+# fchdir.m4 serial 8
 dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -18,6 +18,17 @@
     gl_REPLACE_OPEN
     gl_REPLACE_CLOSE
     gl_REPLACE_DIRENT_H
+    AC_CACHE_CHECK([whether open can visit directories],
+      [gl_cv_func_open_directory_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <fcntl.h>
+]], [return open(".", O_RDONLY);])],
+        [gl_cv_func_open_directory_works=yes],
+        [gl_cv_func_open_directory_works=no],
+        [gl_cv_func_open_directory_works="guessing no"])])
+    if test "$gl_cv_func_open_directory_works" != yes; then
+      AC_DEFINE([REPLACE_OPEN_DIRECTORY], [1], [Define to 1 if open() should
+work around the inability to open a directory.])
+    fi
   fi
 ])
 
--- a/modules/fchdir
+++ b/modules/fchdir
@@ -15,6 +15,7 @@
 include_next
 open
 strdup
+sys_stat
 unistd
 
 configure.ac:
new file mode 100644
--- /dev/null
+++ b/modules/fchdir-tests
@@ -0,0 +1,11 @@
+Files:
+tests/test-fchdir.c
+
+Depends-on:
+getcwd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-fchdir
+check_PROGRAMS += test-fchdir
new file mode 100644
--- /dev/null
+++ b/tests/test-fchdir.c
@@ -0,0 +1,91 @@
+/* Test changing to a directory named by a file descriptor.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ASSERT(expr) \
+  do									     \
+    {									     \
+      if (!(expr))							     \
+        {								     \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+          fflush (stderr);						     \
+          abort ();							     \
+        }								     \
+    }									     \
+  while (0)
+
+int
+main ()
+{
+  char *cwd = getcwd (NULL, 0);
+  int fd = open (".", O_RDONLY);
+  int i;
+
+  ASSERT (cwd);
+  ASSERT (0 <= fd);
+
+  /* Check for failure cases.  */
+  {
+    int bad_fd = open ("/dev/null", O_RDONLY);
+    ASSERT (0 <= bad_fd);
+    errno = 0;
+    ASSERT (fchdir (bad_fd) == -1);
+    ASSERT (errno == ENOTDIR);
+    ASSERT (close (bad_fd) == 0);
+    errno = 0;
+    ASSERT (fchdir (-1) == -1);
+    ASSERT (errno == EBADF);
+  }
+
+  /* Repeat test twice, once in '.' and once in '..'.  */
+  for (i = 0; i < 2; i++)
+    {
+      ASSERT (chdir (".." + 1 - i) == 0);
+      ASSERT (fchdir (fd) == 0);
+      {
+	size_t len = strlen (cwd) + 1;
+	char *new_dir = malloc (len);
+	ASSERT (new_dir);
+	ASSERT (getcwd (new_dir, len) == new_dir);
+	ASSERT (strcmp (cwd, new_dir) == 0);
+	free (new_dir);
+      }
+
+      /* For second iteration, use a cloned fd, to ensure that dup
+	 remembers whether an fd was associated with a directory.  */
+      if (!i)
+	{
+	  int new_fd = dup (fd);
+	  ASSERT (0 <= new_fd);
+	  ASSERT (close (fd) == 0);
+	  fd = new_fd;
+	}
+    }
+
+  free (cwd);
+  return 0;
+}