changeset 12456:f3aceada3c52

fcntl: port portions of fcntl to mingw Borrow ideas from dup_cloexec and dup3 to implement F_DUPFD and F_DUPFD_CLOEXEC. Support querying the inheritance status via F_GETFD, but for now, no support for changing with F_SETFD. The remaining portions of fcntl fail with EINVAL. * m4/fcntl.m4 (gl_FUNC_FCNTL): Also build fcntl.c on mingw. * lib/fcntl.c (fcntl) <F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD>: Provide replacement for mingw. * modules/fcntl (Description): Update. (Depends-on): Add dup2. * m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Add witness. * modules/fcntl-h (Makefile.am): Substitute it. * lib/fcntl.in.h (fcntl): Update declaration. (F_DUPFD, F_GETFD): New macros, when needed. * doc/posix-headers/fcntl.texi (fcntl.h): Update documentation. * doc/posix-functions/fcntl.texi (fcntl): Likewise. * tests/test-fcntl.c (check_flags, main): Enhance test for items we now guarantee. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Wed, 16 Dec 2009 09:11:32 -0700
parents dbc90a3fc4ed
children 2a3833485e1c
files ChangeLog doc/posix-functions/fcntl.texi doc/posix-headers/fcntl.texi lib/fcntl.c lib/fcntl.in.h m4/fcntl.m4 m4/fcntl_h.m4 modules/fcntl modules/fcntl-h tests/test-fcntl.c
diffstat 10 files changed, 243 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,20 @@
 2009-12-16  Eric Blake  <ebb9@byu.net>
 
+	fcntl: port portions of fcntl to mingw
+	* m4/fcntl.m4 (gl_FUNC_FCNTL): Also build fcntl.c on mingw.
+	* lib/fcntl.c (fcntl) <F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD>: Provide
+	replacement for mingw.
+	* modules/fcntl (Description): Update.
+	(Depends-on): Add dup2.
+	* m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Add witness.
+	* modules/fcntl-h (Makefile.am): Substitute it.
+	* lib/fcntl.in.h (fcntl): Update declaration.
+	(F_DUPFD, F_GETFD): New macros, when needed.
+	* doc/posix-headers/fcntl.texi (fcntl.h): Update documentation.
+	* doc/posix-functions/fcntl.texi (fcntl): Likewise.
+	* tests/test-fcntl.c (check_flags, main): Enhance test for items
+	we now guarantee.
+
 	fcntl: work around cygwin bug in F_DUPFD
 	* m4/fcntl.m4 (gl_REPLACE_FCNTL): New macro.
 	(gl_FUNC_FCNTL): Use it.  Test for F_DUPFD bug.
--- a/doc/posix-functions/fcntl.texi
+++ b/doc/posix-functions/fcntl.texi
@@ -20,11 +20,17 @@
 The @code{F_DUPFD} action of this function does not reject
 out-of-range targets properly on some platforms:
 Cygwin 1.5.x.
+
+@item
+This function is missing on some platforms:
+mingw.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-mingw.
+The replacement function does not support @code{F_SETFD},
+@code{F_GETFL}, @code{F_SETFL}, @code{F_GETOWN}, @code{F_SETOWN},
+@code{F_GETLK}, @code{F_SETLK}, and @code{F_SETLKW} on some platforms:
+mingw
 @end itemize
--- a/doc/posix-headers/fcntl.texi
+++ b/doc/posix-headers/fcntl.texi
@@ -23,7 +23,8 @@
 on some platforms but not on others.
 
 @item
-@samp{FD_CLOEXEC} is not defined on some platforms:
+@samp{FD_CLOEXEC}, @samp{F_DUPFD}, and @samp{F_GETFD} are not defined
+on some platforms:
 mingw.
 
 @item
@@ -56,10 +57,6 @@
 on some platforms.
 
 @item
-@samp{F_DUPFD} and @samp{F_GETFD} are not defined on some platforms:
-mingw.
-
-@item
 @samp{F_SETFD}, @samp{F_GETFL}, @samp{F_SETFL}, @samp{F_GETLK},
 @samp{F_SETLK}, @samp{F_SETLOKW}, @samp{F_GETOWN}, and @samp{F_SETOWN}
 are not defined on some platforms:
--- a/lib/fcntl.c
+++ b/lib/fcntl.c
@@ -23,17 +23,130 @@
 #include <fcntl.h>
 
 #include <errno.h>
+#include <limits.h>
 #include <stdarg.h>
 
 #if !HAVE_FCNTL
-# error not ported to mingw yet
+# define rpl_fcntl fcntl
 #endif
 #undef fcntl
 
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+/* Get declarations of the Win32 API functions.  */
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+/* Upper bound on getdtablesize().  See lib/getdtablesize.c.  */
+# define OPEN_MAX_MAX 0x10000
+
+/* Duplicate OLDFD into the first available slot of at least NEWFD,
+   which must be positive, with FLAGS determining whether the duplicate
+   will be inheritable.  */
+static int
+dupfd (int oldfd, int newfd, int flags)
+{
+  /* Mingw has no way to create an arbitrary fd.  Iterate until all
+     file descriptors less than newfd are filled up.  */
+  HANDLE curr_process = GetCurrentProcess ();
+  HANDLE old_handle = (HANDLE) _get_osfhandle (oldfd);
+  unsigned char fds_to_close[OPEN_MAX_MAX / CHAR_BIT];
+  unsigned int fds_to_close_bound = 0;
+  int result;
+  BOOL inherit = flags & O_CLOEXEC ? FALSE : TRUE;
+  int mode;
+
+  if (newfd < 0 || getdtablesize () <= newfd)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  if (old_handle == INVALID_HANDLE_VALUE
+      || (mode = setmode (oldfd, O_BINARY)) == -1)
+    {
+      /* oldfd is not open, or is an unassigned standard file
+         descriptor.  */
+      errno = EBADF;
+      return -1;
+    }
+  setmode (oldfd, mode);
+  flags |= mode;
+
+  for (;;)
+    {
+      HANDLE new_handle;
+      int duplicated_fd;
+      unsigned int index;
+
+      if (!DuplicateHandle (curr_process,           /* SourceProcessHandle */
+                            old_handle,             /* SourceHandle */
+                            curr_process,           /* TargetProcessHandle */
+                            (PHANDLE) &new_handle,  /* TargetHandle */
+                            (DWORD) 0,              /* DesiredAccess */
+                            inherit,                /* InheritHandle */
+                            DUPLICATE_SAME_ACCESS)) /* Options */
+        {
+          /* TODO: Translate GetLastError () into errno.  */
+          errno = EMFILE;
+          result = -1;
+          break;
+        }
+      duplicated_fd = _open_osfhandle ((long) new_handle, flags);
+      if (duplicated_fd < 0)
+        {
+          CloseHandle (new_handle);
+          errno = EMFILE;
+          result = -1;
+          break;
+        }
+      if (newfd <= duplicated_fd)
+        {
+          result = duplicated_fd;
+          break;
+        }
+
+      /* Set the bit duplicated_fd in fds_to_close[].  */
+      index = (unsigned int) duplicated_fd / CHAR_BIT;
+      if (fds_to_close_bound <= index)
+        {
+          if (sizeof fds_to_close <= index)
+            /* Need to increase OPEN_MAX_MAX.  */
+            abort ();
+          memset (fds_to_close + fds_to_close_bound, '\0',
+                  index + 1 - fds_to_close_bound);
+          fds_to_close_bound = index + 1;
+        }
+      fds_to_close[index] |= 1 << ((unsigned int) duplicated_fd % CHAR_BIT);
+    }
+
+  /* Close the previous fds that turned out to be too small.  */
+  {
+    int saved_errno = errno;
+    unsigned int duplicated_fd;
+
+    for (duplicated_fd = 0;
+         duplicated_fd < fds_to_close_bound * CHAR_BIT;
+         duplicated_fd++)
+      if ((fds_to_close[duplicated_fd / CHAR_BIT]
+           >> (duplicated_fd % CHAR_BIT))
+          & 1)
+        close (duplicated_fd);
+
+    errno = saved_errno;
+  }
+
+# if REPLACE_FCHDIR
+  if (0 <= result)
+    result = _gl_register_dup (oldfd, result);
+# endif
+  return result;
+}
+#endif /* W32 */
+
 /* Perform the specified ACTION on the file descriptor FD, possibly
    using the argument ARG further described below.  This replacement
    handles the following actions, and forwards all others on to the
-   native fcntl.
+   native fcntl.  An unrecognized ACTION returns -1 with errno set to
+   EINVAL.
 
    F_DUPFD - duplicate FD, with int ARG being the minimum target fd.
    If successful, return the duplicate, which will be inheritable;
@@ -41,7 +154,12 @@
 
    F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum
    target fd.  If successful, return the duplicate, which will not be
-   inheritable; otherwise return -1 and set errno.  */
+   inheritable; otherwise return -1 and set errno.
+
+   F_GETFD - ARG need not be present.  If successful, return a
+   non-negative value containing the descriptor flags of FD (only
+   FD_CLOEXEC is portable, but other flags may be present); otherwise
+   return -1 and set errno.  */
 
 int
 rpl_fcntl (int fd, int action, /* arg */...)
@@ -52,7 +170,14 @@
   switch (action)
     {
 
-#if FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
+#if !HAVE_FCNTL
+    case F_DUPFD:
+      {
+        int target = va_arg (arg, int);
+        result = dupfd (fd, target, 0);
+        break;
+      }
+#elif FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
     case F_DUPFD:
       {
         int target = va_arg (arg, int);
@@ -75,6 +200,10 @@
       {
         int target = va_arg (arg, int);
 
+#if !HAVE_FCNTL
+        result = dupfd (fd, target, O_CLOEXEC);
+        break;
+#else /* HAVE_FCNTL */
         /* Try the system call first, if the headers claim it exists
            (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we
            may be running with a glibc that has the macro but with an
@@ -89,10 +218,10 @@
             if (0 <= result || errno != EINVAL)
               {
                 have_dupfd_cloexec = 1;
-#if REPLACE_FCHDIR
+# if REPLACE_FCHDIR
                 if (0 <= result)
                   result = _gl_register_dup (fd, result);
-#endif
+# endif
               }
             else
               {
@@ -116,12 +245,46 @@
               }
           }
         break;
+#endif /* HAVE_FCNTL */
       } /* F_DUPFD_CLOEXEC */
 
+#if !HAVE_FCNTL
+    case F_GETFD:
+      {
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+        HANDLE handle = (HANDLE) _get_osfhandle (fd);
+        DWORD flags;
+        if (handle == INVALID_HANDLE_VALUE
+            || GetHandleInformation (handle, &flags) == 0)
+          errno = EBADF;
+        else
+          result = (flags & HANDLE_FLAG_INHERIT) ? 0 : FD_CLOEXEC;
+# else /* !W32 */
+        /* Use dup2 to reject invalid file descriptors.  No way to
+           access this information, so punt.  */
+        if (0 <= dup2 (fd, fd))
+          result = 0;
+# endif /* !W32 */
+        break;
+      } /* F_GETFD */
+#endif /* !HAVE_FCNTL */
+
+      /* Implementing F_SETFD on mingw is not trivial - there is no
+         API for changing the O_NOINHERIT bit on an fd, and merely
+         changing the HANDLE_FLAG_INHERIT bit on the underlying handle
+         can lead to odd state.  It may be possible by duplicating the
+         handle, using _open_osfhandle with the right flags, then
+         using dup2 to move the duplicate onto the original, but that
+         is not supported for now.  */
+
     default:
       {
+#if HAVE_FCNTL
         void *p = va_arg (arg, void *);
         result = fcntl (fd, action, p);
+#else
+        errno = EINVAL;
+#endif
         break;
       }
     }
--- a/lib/fcntl.in.h
+++ b/lib/fcntl.in.h
@@ -59,6 +59,8 @@
 # if @REPLACE_FCNTL@
 #  undef fcntl
 #  define fcntl rpl_fcntl
+# endif
+# if !@HAVE_FCNTL@ || @REPLACE_FCNTL@
 extern int fcntl (int fd, int action, ...);
 # endif
 #elif defined GNULIB_POSIXCHECK
@@ -104,14 +106,14 @@
 }
 #endif
 
-/* Fix up the FD_* macros.  */
+/* Fix up the FD_* macros, only known to be missing on mingw.  */
 
 #ifndef FD_CLOEXEC
 # define FD_CLOEXEC 1
 #endif
 
 /* Fix up the supported F_* macros.  Intentionally leave other F_*
-   macros undefined.  */
+   macros undefined.  Only known to be missing on mingw.  */
 
 #ifndef F_DUPFD_CLOEXEC
 # define F_DUPFD_CLOEXEC 0x40000000
@@ -121,6 +123,14 @@
 # define GNULIB_defined_F_DUPFD_CLOEXEC 0
 #endif
 
+#ifndef F_DUPFD
+# define F_DUPFD 1
+#endif
+
+#ifndef F_GETFD
+# define F_GETFD 2
+#endif
+
 /* Fix up the O_* macros.  */
 
 #if !defined O_DIRECT && defined O_DIRECTIO
--- a/m4/fcntl.m4
+++ b/m4/fcntl.m4
@@ -1,4 +1,4 @@
-# fcntl.m4 serial 2
+# fcntl.m4 serial 3
 dnl Copyright (C) 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,
@@ -7,9 +7,9 @@
 # For now, this module ensures that fcntl()
 # - supports F_DUPFD correctly
 # - supports or emulates F_DUPFD_CLOEXEC
+# - supports F_GETFD
 # Still to be ported to mingw:
-# - F_GETFD, F_SETFD, F_DUPFD
-# - F_DUPFD_CLOEXEC
+# - F_SETFD
 # - F_GETFL, F_SETFL
 # - F_GETOWN, F_SETOWN
 # - F_GETLK, F_SETLK, F_SETLKW
@@ -21,7 +21,7 @@
   AC_REQUIRE([AC_CANONICAL_HOST])
   AC_CHECK_FUNCS_ONCE([fcntl])
   if test $ac_cv_func_fcntl = no; then
-    HAVE_FCNTL=0
+    gl_REPLACE_FCNTL
   else
     dnl cygwin 1.5.x F_DUPFD has wrong errno, and allows negative target
     AC_CACHE_CHECK([whether fcntl handles F_DUPFD correctly],
@@ -74,8 +74,10 @@
 [
   AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
   AC_CHECK_FUNCS_ONCE([fcntl])
-  if test $ac_cv_func_fcntl = yes; then
+  if test $ac_cv_func_fcntl = no; then
+    HAVE_FCNTL=0
+  else
     REPLACE_FCNTL=1
-    AC_LIBOBJ([fcntl])
   fi
+  AC_LIBOBJ([fcntl])
 ])
--- a/m4/fcntl_h.m4
+++ b/m4/fcntl_h.m4
@@ -1,4 +1,4 @@
-# serial 7
+# serial 8
 # Configure fcntl.h.
 dnl Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
@@ -101,6 +101,7 @@
   GNULIB_OPEN=0;    AC_SUBST([GNULIB_OPEN])
   GNULIB_OPENAT=0;  AC_SUBST([GNULIB_OPENAT])
   dnl Assume proper GNU behavior unless another module says otherwise.
+  HAVE_FCNTL=1;     AC_SUBST([HAVE_FCNTL])
   HAVE_OPENAT=1;    AC_SUBST([HAVE_OPENAT])
   REPLACE_FCNTL=0;  AC_SUBST([REPLACE_FCNTL])
   REPLACE_OPEN=0;   AC_SUBST([REPLACE_OPEN])
--- a/modules/fcntl
+++ b/modules/fcntl
@@ -1,11 +1,12 @@
 Description:
-Support for fcntl() action F_DUPFD_CLOEXEC.
+Support for fcntl() action F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD.
 
 Files:
 m4/fcntl.m4
 lib/fcntl.c
 
 Depends-on:
+dup2
 fcntl-h
 
 configure.ac:
--- a/modules/fcntl-h
+++ b/modules/fcntl-h
@@ -29,6 +29,7 @@
 	      -e 's|@''GNULIB_FCNTL''@|$(GNULIB_FCNTL)|g' \
 	      -e 's|@''GNULIB_OPEN''@|$(GNULIB_OPEN)|g' \
 	      -e 's|@''GNULIB_OPENAT''@|$(GNULIB_OPENAT)|g' \
+	      -e 's|@''HAVE_FCNTL''@|$(HAVE_FCNTL)|g' \
 	      -e 's|@''HAVE_OPENAT''@|$(HAVE_OPENAT)|g' \
 	      -e 's|@''REPLACE_FCNTL''@|$(REPLACE_FCNTL)|g' \
 	      -e 's|@''REPLACE_OPEN''@|$(REPLACE_OPEN)|g' \
--- a/tests/test-fcntl.c
+++ b/tests/test-fcntl.c
@@ -155,22 +155,16 @@
 {
   switch (0)
     {
-#ifdef F_DUPFD
     case F_DUPFD:
-# if F_DUPFD
-# endif
+#if F_DUPFD
 #endif
 
-#ifdef F_DUPFD_CLOEXEC
     case F_DUPFD_CLOEXEC:
-# if F_DUPFD_CLOEXEC
-# endif
+#if F_DUPFD_CLOEXEC
 #endif
 
-#ifdef F_GETFD
     case F_GETFD:
-# if F_GETFD
-# endif
+#if F_GETFD
 #endif
 
 #ifdef F_SETFD
@@ -241,8 +235,6 @@
   }
   check_flags ();
 
-#if HAVE_FCNTL
-
   /* Assume std descriptors were provided by invoker, and ignore fds
      that might have been inherited.  */
   fd = creat (file, 0600);
@@ -335,11 +327,30 @@
   ASSERT (is_mode (fd + 2, O_TEXT));
   ASSERT (close (fd + 2) == 0);
 
+  /* Test F_GETFD.  */
+  errno = 0;
+  ASSERT (fcntl (-1, F_GETFD) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (fd + 1, F_GETFD) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (10000000, F_GETFD) == -1);
+  ASSERT (errno == EBADF);
+  {
+    int result = fcntl (fd, F_GETFD);
+    ASSERT (0 <= result);
+    ASSERT ((result & FD_CLOEXEC) == FD_CLOEXEC);
+    ASSERT (dup (fd) == fd + 1);
+    result = fcntl (fd + 1, F_GETFD);
+    ASSERT (0 <= result);
+    ASSERT ((result & FD_CLOEXEC) == 0);
+    ASSERT (close (fd + 1) == 0);
+  }
+
   /* Cleanup.  */
   ASSERT (close (fd) == 0);
   ASSERT (unlink (file) == 0);
 
-#endif /* HAVE_FCNTL */
-
   return 0;
 }