fchdir: port to mingw
authorEric Blake <ebb9@byu.net>
Mon, 31 Aug 2009 12:09:08 +0000 (06:09 -0600)
committerEric Blake <ebb9@byu.net>
Tue, 1 Sep 2009 01:19:55 +0000 (19:19 -0600)
* 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>
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 [new file with mode: 0644]
tests/test-fchdir.c [new file with mode: 0644]

index f8745fe..8bf2621 100644 (file)
--- 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.
index 27990a0..6a919d8 100644 (file)
@@ -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 @@ On Windows platforms (excluding Cygwin), this function does usually not
 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
index d51d963..cedbcde 100644 (file)
 
 #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 @@ ensure_dirs_slot (size_t fd)
 /* 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 @@ _gl_unregister_fd (int fd)
     }
 }
 
+/* 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 @@ _gl_register_fd (int fd, const char *filename)
 
   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 @@ rpl_dup2_fchdir (int oldfd, int newfd)
 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;
 }
index 326e6d1..5cfef1f 100644 (file)
@@ -39,6 +39,10 @@ orig_open (const char *filename, int flags, mode_t mode)
 #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 @@ open (const char *filename, int flags, ...)
 
   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 @@ open (const char *filename, int flags, ...)
 #endif
 
 #ifdef FCHDIR_REPLACEMENT
-  if (fd >= 0)
+  if (!REPLACE_OPEN_DIRECTORY && 0 <= fd)
     _gl_register_fd (fd, filename);
 #endif
 
index da965d0..9d511da 100644 (file)
@@ -302,6 +302,10 @@ extern int rpl_lstat (const char *name, struct stat *buf);
    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
index d5dd3e2..49e6634 100644 (file)
@@ -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 @@ AC_DEFUN([gl_FUNC_FCHDIR],
     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
 ])
 
index 17e842c..d3fe0e5 100644 (file)
@@ -15,6 +15,7 @@ fcntl-h
 include_next
 open
 strdup
+sys_stat
 unistd
 
 configure.ac:
diff --git a/modules/fchdir-tests b/modules/fchdir-tests
new file mode 100644 (file)
index 0000000..f3bb8b2
--- /dev/null
@@ -0,0 +1,11 @@
+Files:
+tests/test-fchdir.c
+
+Depends-on:
+getcwd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-fchdir
+check_PROGRAMS += test-fchdir
diff --git a/tests/test-fchdir.c b/tests/test-fchdir.c
new file mode 100644 (file)
index 0000000..b361d0d
--- /dev/null
@@ -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;
+}