bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

fcntl for mingw


From: Eric Blake
Subject: fcntl for mingw
Date: Thu, 10 Dec 2009 23:24:04 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

Here's what I'm currently playing with.  I'll wait another day or two for any 
comments, first; but it passes for me on Linux, cygwin 1.5, cygwin 1.7, and 
mingw.

Eric Blake (4):
      [1/4] fcntl: support F_DUPFD_CLOEXEC on systems with fcntl
Provide POSIX semantics (minus the atomicity factor) in most systems.

      [2/4] fcntl: work around cygwin bug in F_DUPFD
Work around a cygwin 1.5 bug uncovered in the previous patch.

      [3/4] fcntl: port portions of fcntl to mingw
Port portions of fcntl to mingw.

      [4/4] dup3, fchdir: rely more on fcntl
Directly use fcntl where it makes more sense.


>From 1823f51e958ee55c0d10ea21b973249ef533d02e Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 7 Dec 2009 11:50:59 -0700
Subject: [PATCH 1/4] fcntl: support F_DUPFD_CLOEXEC on systems with fcntl

Implement F_DUPFD_CLOEXEC.  The unit test still fails on systems
with other fcntl bugs (such as cygwin 1.5 mishandling F_DUPFD,
or mingw lacking fcntl altogether).  Passes on Linux, both with
and without kernel support, and on cygwin 1.7.

* modules/fcntl (Files): List new files.
(Depends-on): Add cloexec.
(configure.ac): Run a test.
* m4/fcntl.m4 (gl_FUNC_FCNTL): New file.
* lib/fcntl.c (rpl_fcntl): Likewise.
* m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Add witness defaults.
(gl_FCNTL_H): Always replace fcntl.h.
* modules/fcntl-h (Makefile.am): Substitute witnesses.
* lib/fcntl.in.h (fcntl): Declare replacement.
(openat) [GNULIB_POSIXCHECK]: Avoid problematic macro.
* doc/posix-functions/fcntl.texi (fcntl): Document this.
* doc/posix-headers/fcntl.texi (fcntl.h): Likewise.
* tests/test-fcntl.c: New file.
* modules/fcntl-tests: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                      |   16 ++
 doc/posix-functions/fcntl.texi |    8 +-
 doc/posix-headers/fcntl.texi   |    4 +
 lib/fcntl.c                    |  110 ++++++++++++++
 lib/fcntl.in.h                 |   28 +++-
 m4/fcntl.m4                    |   43 ++++++
 m4/fcntl_h.m4                  |    6 +-
 modules/fcntl                  |    7 +-
 modules/fcntl-h                |    6 +-
 modules/fcntl-tests            |   12 ++
 tests/test-fcntl.c             |  328 ++++++++++++++++++++++++++++++++++++++++
 11 files changed, 555 insertions(+), 13 deletions(-)
 create mode 100644 lib/fcntl.c
 create mode 100644 m4/fcntl.m4
 create mode 100644 modules/fcntl-tests
 create mode 100644 tests/test-fcntl.c

diff --git a/ChangeLog b/ChangeLog
index fec55df..af24729 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,21 @@
 2009-12-10  Eric Blake  <address@hidden>

+       fcntl: support F_DUPFD_CLOEXEC on systems with fcntl
+       * modules/fcntl (Files): List new files.
+       (Depends-on): Add cloexec.
+       (configure.ac): Run a test.
+       * m4/fcntl.m4 (gl_FUNC_FCNTL): New file.
+       * lib/fcntl.c (rpl_fcntl): Likewise.
+       * m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Add witness defaults.
+       (gl_FCNTL_H): Always replace fcntl.h.
+       * modules/fcntl-h (Makefile.am): Substitute witnesses.
+       * lib/fcntl.in.h (fcntl): Declare replacement.
+       (openat) [GNULIB_POSIXCHECK]: Avoid problematic macro.
+       * doc/posix-functions/fcntl.texi (fcntl): Document this.
+       * doc/posix-headers/fcntl.texi (fcntl.h): Likewise.
+       * tests/test-fcntl.c: New file.
+       * modules/fcntl-tests: Likewise.
+
        setenv: relax requirement in light of POSIX ruling
        * m4/setenv.m4 (gl_FUNC_SETENV_SEPARATE): Test handling of "" but
        not NULL.
diff --git a/doc/posix-functions/fcntl.texi b/doc/posix-functions/fcntl.texi
index 143ac63..31dca10 100644
--- a/doc/posix-functions/fcntl.texi
+++ b/doc/posix-functions/fcntl.texi
@@ -4,10 +4,16 @@ fcntl

 POSIX specification: @url
{http://www.opengroup.org/onlinepubs/9699919799/functions/fcntl.html}

-Gnulib module: ---
+Gnulib module: fcntl

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+This function does not support @code{F_DUPFD_CLOEXEC} on some
+platforms, although the replacement is not atomic:
+MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11,
+IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.7.1, mingw, Interix 3.5,
+BeOS.
 @end itemize

 Portability problems not fixed by Gnulib:
diff --git a/doc/posix-headers/fcntl.texi b/doc/posix-headers/fcntl.texi
index 340cf28..2475c9b 100644
--- a/doc/posix-headers/fcntl.texi
+++ b/doc/posix-headers/fcntl.texi
@@ -27,6 +27,10 @@ fcntl.h
 mingw.

 @item
address@hidden is not defined on some platforms (but for now,
+the replacement is only defined on platforms where @code{fcntl} exists).
+
address@hidden
 @samp{AT_FDCWD}, @samp{AT_EACCESS}, @samp{AT_SYMLINK_NOFOLLOW},
 @samp{AT_SYMLINK_FOLLOW}, and @samp{AT_REMOVEDIR}
 are not defined on many platforms:
diff --git a/lib/fcntl.c b/lib/fcntl.c
new file mode 100644
index 0000000..d8442cc
--- /dev/null
+++ b/lib/fcntl.c
@@ -0,0 +1,110 @@
+/* Provide file descriptor control.
+
+   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 <address@hidden>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <fcntl.h>
+
+#include <errno.h>
+#include <stdarg.h>
+
+#include "cloexec.h"
+
+#if !HAVE_FCNTL
+# error not ported to mingw yet
+#endif
+#undef fcntl
+
+/* 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.
+
+   F_DUPFD - duplicate FD, with ACTION being the minimum target fd.
+   If successful, return the duplicate, which will be inheritable;
+   otherwise return -1 and set errno.
+
+   F_DUPFD_CLOEXEC - duplicate FD, with ACTION being the minimum
+   target fd.  If successful, return the duplicate, which will not be
+   inheritable; otherwise return -1 and set errno.
+*/
+
+int
+rpl_fcntl (int fd, int action, ...)
+{
+  va_list arg;
+  int result = -1;
+  va_start (arg, action);
+  switch (action)
+    {
+    case F_DUPFD_CLOEXEC:
+      {
+        int target = va_arg (arg, int);
+
+        /* Try the system call first, if it exists (that is, if
+           gl_F_DUPFD_CLOEXEC is 0 but F_DUPFD_CLOEXEC is defined).
+           (We may be running with a glibc that has the macro but with
+           an older kernel that does not support it.)  Cache the
+           information on whether the system call really works, but
+           don't cache failure if F_DUPFD gets the same EINVAL
+           failure.  0 = unknown, 1 = yes, -1 = no.  */
+        static int have_dupfd_cloexec = -gl_F_DUPFD_CLOEXEC;
+        if (0 <= have_dupfd_cloexec)
+          {
+            result = fcntl (fd, action, target);
+            if (0 <= result || errno != EINVAL)
+              have_dupfd_cloexec = 1;
+            else
+              {
+                result = fcntl (fd, F_DUPFD, target);
+                if (result < 0)
+                  break;
+                have_dupfd_cloexec = -1;
+              }
+          }
+        else
+          result = fcntl (fd, F_DUPFD, target);
+        if (0 <= result && have_dupfd_cloexec == -1)
+          {
+            if (set_cloexec_flag (result, true) < 0)
+              {
+                int saved_errno = errno;
+                close (result);
+                errno = saved_errno;
+                result = -1;
+              }
+          }
+#if REPLACE_FCHDIR
+        if (0 <= result)
+          result = _gl_register_dup (fd, result);
+#endif
+        break;
+      }
+
+    default:
+      {
+        void *p = va_arg (arg, void *);
+        result = fcntl (fd, action, p);
+        break;
+      }
+    }
+  va_end (arg);
+  return result;
+}
diff --git a/lib/fcntl.in.h b/lib/fcntl.in.h
index 0ae8213..11c64e3 100644
--- a/lib/fcntl.in.h
+++ b/lib/fcntl.in.h
@@ -53,12 +53,32 @@
 extern "C" {
 #endif

+#if @GNULIB_FCNTL@
+# if @REPLACE_FCNTL@
+#  undef fcntl
+#  define fcntl rpl_fcntl
+extern int fcntl (int fd, int action, ...);
+#  ifndef F_DUPFD_CLOEXEC
+#   define F_DUPFD_CLOEXEC 0x40000000
+#   define gl_F_DUPFD_CLOEXEC 1
+#  endif
+# endif
+/* Witness variable: 1 if gnulib defined F_DUPFD_CLOEXEC, 0 otherwise.  */
+# ifndef gl_F_DUPFD_CLOEXEC
+#  define gl_F_DUPFD_CLOEXEC 0
+# endif
+#elif defined GNULIB_POSIXCHECK
+/* Can't provide link warning without support for C99 variadic macros.  */
+#endif
+
 #if @GNULIB_OPEN@
 # if @REPLACE_OPEN@
 #  undef open
 #  define open rpl_open
 extern int open (const char *filename, int flags, ...);
 # endif
+#elif defined GNULIB_POSIXCHECK
+/* Can't provide link warning without support for C99 variadic macros.  */
 #endif

 #if @GNULIB_OPENAT@
@@ -67,14 +87,10 @@ extern int open (const char *filename, int flags, ...);
 #  define openat rpl_openat
 # endif
 # if address@hidden@ || @REPLACE_OPENAT@
-int openat (int fd, char const *file, int flags, /* mode_t mode */ ...);
+extern int openat (int fd, char const *file, int flags, /* mode_t mode */ ...);
 # endif
 #elif defined GNULIB_POSIXCHECK
-# undef openat
-# define openat(f,u,g) \
-    (GL_LINK_WARNING ("openat is not portable - " \
-                      "use gnulib module openat for portability"), \
-     openat)
+/* Can't provide link warning without support for C99 variadic macros.  */
 #endif

 #ifdef __cplusplus
diff --git a/m4/fcntl.m4 b/m4/fcntl.m4
new file mode 100644
index 0000000..1c33540
--- /dev/null
+++ b/m4/fcntl.m4
@@ -0,0 +1,43 @@
+# fcntl.m4 serial 1
+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,
+dnl with or without modifications, as long as this notice is preserved.
+
+# For now, this module ensures that fcntl()
+# - supports or emulates F_DUPFD_CLOEXEC
+# Still to be ported to various platforms:
+# - supports F_DUPFD correctly
+# Still to be ported to mingw:
+# - F_GETFD, F_SETFD, F_DUPFD
+# - F_DUPFD_CLOEXEC
+# - F_GETFL, F_SETFL
+# - F_GETOWN, F_SETOWN
+# - F_GETLK, F_SETLK, F_SETLKW
+AC_DEFUN([gl_FUNC_FCNTL],
+[
+  dnl Persuade glibc to expose F_DUPFD_CLOEXEC.
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+  AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
+  AC_CHECK_FUNCS_ONCE([fcntl])
+  if test $ac_cv_func_fcntl = no; then
+    HAVE_FCNTL=0
+  else
+    AC_CACHE_CHECK([whether fcntl understands F_DUPFD_CLOEXEC],
+      [gl_cv_func_fcntl_f_dupfd_cloexec],
+      [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>
+#if defined __linux__ || !defined F_DUPFD_CLOEXEC
+/* The Linux kernel added F_DUPFD_CLOEXEC in 2.6.24, but we always replace
+   it to support the semantics on older kernels that failed with EINVAL.  */
+choke me
+#endif
+         ]])],
+         [gl_cv_func_fcntl_f_dupfd_cloexec=yes],
+         [gl_cv_func_fcntl_f_dupfd_cloexec="needs runtime check"])])
+    if test "$gl_cv_func_fcntl_f_dupfd_cloexec" != yes; then
+      REPLACE_FCNTL=1
+      AC_LIBOBJ([fcntl])
+    fi
+  fi
+])
diff --git a/m4/fcntl_h.m4 b/m4/fcntl_h.m4
index 40a1803..3825adf 100644
--- a/m4/fcntl_h.m4
+++ b/m4/fcntl_h.m4
@@ -1,4 +1,4 @@
-# serial 6
+# serial 7
 # Configure fcntl.h.
 dnl Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
@@ -12,8 +12,6 @@ AC_DEFUN([gl_FCNTL_H],
   AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
   AC_REQUIRE([gl_FCNTL_O_FLAGS])
   gl_CHECK_NEXT_HEADERS([fcntl.h])
-  FCNTL_H='fcntl.h'
-  AC_SUBST([FCNTL_H])
 ])

 # Test whether the flags O_NOATIME and O_NOFOLLOW actually work.
@@ -99,10 +97,12 @@ AC_DEFUN([gl_FCNTL_MODULE_INDICATOR],

 AC_DEFUN([gl_FCNTL_H_DEFAULTS],
 [
+  GNULIB_FCNTL=0;   AC_SUBST([GNULIB_FCNTL])
   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_OPENAT=1;    AC_SUBST([HAVE_OPENAT])
+  REPLACE_FCNTL=0;  AC_SUBST([REPLACE_FCNTL])
   REPLACE_OPEN=0;   AC_SUBST([REPLACE_OPEN])
   REPLACE_OPENAT=0; AC_SUBST([REPLACE_OPENAT])
 ])
diff --git a/modules/fcntl b/modules/fcntl
index 1107cac..961026e 100644
--- a/modules/fcntl
+++ b/modules/fcntl
@@ -1,12 +1,17 @@
 Description:
-Placeholder for eventual fcntl() replacement.
+Support for fcntl() action F_DUPFD_CLOEXEC.

 Files:
+m4/fcntl.m4
+lib/fcntl.c

 Depends-on:
+cloexec
 fcntl-h

 configure.ac:
+gl_FUNC_FCNTL
+gl_FCNTL_MODULE_INDICATOR([fcntl])

 Makefile.am:

diff --git a/modules/fcntl-h b/modules/fcntl-h
index eb5cce1..60e078e 100644
--- a/modules/fcntl-h
+++ b/modules/fcntl-h
@@ -15,7 +15,7 @@ configure.ac:
 gl_FCNTL_H

 Makefile.am:
-BUILT_SOURCES += $(FCNTL_H)
+BUILT_SOURCES += fcntl.h

 # We need the following in order to create <fcntl.h> when the system
 # doesn't have one that works with the given compiler.
@@ -25,11 +25,13 @@ fcntl.h: fcntl.in.h $(LINK_WARNING_H)
          sed -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \
              -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \
              -e 's|@''NEXT_FCNTL_H''@|$(NEXT_FCNTL_H)|g' \
+             -e 's|@''GNULIB_FCNTL''@|$(GNULIB_FCNTL)|g' \
              -e 's|@''GNULIB_OPEN''@|$(GNULIB_OPEN)|g' \
              -e 's|@''GNULIB_OPENAT''@|$(GNULIB_OPENAT)|g' \
+             -e 's|@''HAVE_OPENAT''@|$(HAVE_OPENAT)|g' \
+             -e 's|@''REPLACE_FCNTL''@|$(REPLACE_FCNTL)|g' \
              -e 's|@''REPLACE_OPEN''@|$(REPLACE_OPEN)|g' \
              -e 's|@''REPLACE_OPENAT''@|$(REPLACE_OPENAT)|g' \
-             -e 's|@''HAVE_OPENAT''@|$(HAVE_OPENAT)|g' \
              -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
              < $(srcdir)/fcntl.in.h; \
        } > address@hidden && \
diff --git a/modules/fcntl-tests b/modules/fcntl-tests
new file mode 100644
index 0000000..4f9e75d
--- /dev/null
+++ b/modules/fcntl-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-fcntl.c
+
+Depends-on:
+getdtablesize
+stdbool
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-fcntl
+check_PROGRAMS += test-fcntl
diff --git a/tests/test-fcntl.c b/tests/test-fcntl.c
new file mode 100644
index 0000000..4dd1995
--- /dev/null
+++ b/tests/test-fcntl.c
@@ -0,0 +1,328 @@
+/* Test of fcntl(2).
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <fcntl.h>
+
+/* Helpers.  */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+/* Get declarations of the Win32 API functions.  */
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+
+#include "binary-io.h"
+
+/* Use O_CLOEXEC if available, but test works without it.  */
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
+
+#if !O_BINARY
+# define setmode(f,m) zero ()
+static int zero (void) { return 0; }
+#endif
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+/* Return true if FD is open.  */
+static bool
+is_open (int fd)
+{
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  /* On Win32, the initial state of unassigned standard file
+     descriptors is that they are open but point to an
+     INVALID_HANDLE_VALUE, and there is no fcntl.  */
+  return (HANDLE) _get_osfhandle (fd) != INVALID_HANDLE_VALUE;
+#else
+# ifndef F_GETFL
+#  error Please port fcntl to your platform
+# endif
+  return 0 <= fcntl (fd, F_GETFL);
+#endif
+}
+
+/* Return true if FD is open and inheritable across exec/spawn.  */
+static bool
+is_inheritable (int fd)
+{
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  /* On Win32, the initial state of unassigned standard file
+     descriptors is that they are open but point to an
+     INVALID_HANDLE_VALUE, and there is no fcntl.  */
+  HANDLE h = (HANDLE) _get_osfhandle (fd);
+  DWORD flags;
+  if (h == INVALID_HANDLE_VALUE || GetHandleInformation (h, &flags) == 0)
+    return false;
+  return (flags & HANDLE_FLAG_INHERIT) != 0;
+#else
+# ifndef F_GETFD
+#  error Please port fcntl to your platform
+# endif
+  int i = fcntl (fd, F_GETFD);
+  return 0 <= i && (i & FD_CLOEXEC) == 0;
+#endif
+}
+
+/* Return non-zero if FD is open in the given MODE, which is either
+   O_TEXT or O_BINARY.  */
+static bool
+is_mode (int fd, int mode)
+{
+  int value = setmode (fd, O_BINARY);
+  setmode (fd, value);
+  return mode == value;
+}
+
+/* Since native fcntl can have more supported operations than our
+   replacement is aware of, and since various operations assign
+   different types to the vararg argument, a wrapper around fcntl must
+   be able to pass a vararg of unknown type on through to the original
+   fcntl.  Make sure that this works properly: func1 behaves like the
+   original fcntl interpreting the vararg as an int, and func2 behaves
+   like rpl_fcntl that doesn't know what type to forward.  */
+static int
+func1 (int a, ...)
+{
+  va_list arg;
+  int i;
+  va_start (arg, a);
+  i = va_arg (arg, int);
+  va_end (arg);
+  return i;
+}
+static int
+func2 (int a, ...)
+{
+  va_list arg;
+  void *p;
+  va_start (arg, a);
+  p = va_arg (arg, void *);
+  va_end (arg);
+  return func1 (a, p);
+}
+
+/* Ensure that all supported fcntl actions are distinct, and
+   usable in preprocessor expressions.  */
+static void
+check_flags (void)
+{
+  switch (0)
+    {
+#ifdef F_DUPFD
+    case F_DUPFD:
+# if F_DUPFD
+# endif
+#endif
+
+#ifdef F_DUPFD_CLOEXEC
+    case F_DUPFD_CLOEXEC:
+# if F_DUPFD_CLOEXEC
+# endif
+#endif
+
+#ifdef F_GETFD
+    case F_GETFD:
+# if F_GETFD
+# endif
+#endif
+
+#ifdef F_SETFD
+    case F_SETFD:
+# if F_SETFD
+# endif
+#endif
+
+#ifdef F_GETFL
+    case F_GETFL:
+# if F_GETFL
+# endif
+#endif
+
+#ifdef F_SETFL
+    case F_SETFL:
+# if F_SETFL
+# endif
+#endif
+
+#ifdef F_GETOWN
+    case F_GETOWN:
+# if F_GETOWN
+# endif
+#endif
+
+#ifdef F_SETOWN
+    case F_SETOWN:
+# if F_SETOWN
+# endif
+#endif
+
+#ifdef F_GETLK
+    case F_GETLK:
+# if F_GETLK
+# endif
+#endif
+
+#ifdef F_SETLK
+    case F_SETLK:
+# if F_SETLK
+# endif
+#endif
+
+#ifdef F_SETLKW
+    case F_SETLKW:
+# if F_SETLKW
+# endif
+#endif
+
+      ;
+    }
+}
+
+int
+main (int argc, char **argv)
+{
+  const char *file = "test-fcntl.tmp";
+  int fd;
+
+  ASSERT (func2 (1, 2) == 2);
+  ASSERT (func2 (2, -2) == -2);
+  ASSERT (func2 (3, 0x80000000) == 0x80000000);
+  check_flags ();
+
+#if HAVE_FCNTL
+
+  /* Assume std descriptors were provided by invoker, and ignore fds
+     that might have been inherited.  */
+  fd = creat (file, 0600);
+  ASSERT (STDERR_FILENO < fd);
+  close (fd + 1);
+  close (fd + 2);
+
+  /* For F_DUPFD*, the source must be valid.  */
+  errno = 0;
+  ASSERT (fcntl (-1, F_DUPFD, 0) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (fd + 1, F_DUPFD, 0) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (10000000, F_DUPFD, 0) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (-1, F_DUPFD_CLOEXEC, 0) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (fd + 1, F_DUPFD_CLOEXEC, 0) == -1);
+  ASSERT (errno == EBADF);
+  errno = 0;
+  ASSERT (fcntl (10000000, F_DUPFD_CLOEXEC, 0) == -1);
+  ASSERT (errno == EBADF);
+
+  /* For F_DUPFD*, the destination must be valid.  */
+  ASSERT (getdtablesize () < 10000000);
+  errno = 0;
+  ASSERT (fcntl (fd, F_DUPFD, -1) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (fcntl (fd, F_DUPFD, 10000000) == -1);
+  ASSERT (errno == EINVAL);
+  ASSERT (getdtablesize () < 10000000);
+  errno = 0;
+  ASSERT (fcntl (fd, F_DUPFD_CLOEXEC, -1) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (fcntl (fd, F_DUPFD_CLOEXEC, 10000000) == -1);
+  ASSERT (errno == EINVAL);
+
+  /* For F_DUPFD*, check for correct inheritance, as well as
+     preservation of text vs. binary.  */
+  setmode (fd, O_BINARY);
+  ASSERT (is_open (fd));
+  ASSERT (!is_open (fd + 1));
+  ASSERT (!is_open (fd + 2));
+  ASSERT (is_inheritable (fd));
+  ASSERT (is_mode (fd, O_BINARY));
+
+  ASSERT (fcntl (fd, F_DUPFD, fd) == fd + 1);
+  ASSERT (is_open (fd));
+  ASSERT (is_open (fd + 1));
+  ASSERT (!is_open (fd + 2));
+  ASSERT (is_inheritable (fd + 1));
+  ASSERT (is_mode (fd, O_BINARY));
+  ASSERT (is_mode (fd + 1, O_BINARY));
+  ASSERT (close (fd + 1) == 0);
+
+  ASSERT (fcntl (fd, F_DUPFD_CLOEXEC, fd + 2) == fd + 2);
+  ASSERT (is_open (fd));
+  ASSERT (!is_open (fd + 1));
+  ASSERT (is_open (fd + 2));
+  ASSERT (is_inheritable (fd));
+  ASSERT (!is_inheritable (fd + 2));
+  ASSERT (is_mode (fd, O_BINARY));
+  ASSERT (is_mode (fd + 2, O_BINARY));
+  ASSERT (close (fd) == 0);
+
+  setmode (fd + 2, O_TEXT);
+  ASSERT (fcntl (fd + 2, F_DUPFD, fd + 1) == fd + 1);
+  ASSERT (!is_open (fd));
+  ASSERT (is_open (fd + 1));
+  ASSERT (is_open (fd + 2));
+  ASSERT (is_inheritable (fd + 1));
+  ASSERT (!is_inheritable (fd + 2));
+  ASSERT (is_mode (fd + 1, O_TEXT));
+  ASSERT (is_mode (fd + 2, O_TEXT));
+  ASSERT (close (fd + 1) == 0);
+
+  ASSERT (fcntl (fd + 2, F_DUPFD_CLOEXEC, 0) == fd);
+  ASSERT (is_open (fd));
+  ASSERT (!is_open (fd + 1));
+  ASSERT (is_open (fd + 2));
+  ASSERT (!is_inheritable (fd));
+  ASSERT (!is_inheritable (fd + 2));
+  ASSERT (is_mode (fd, O_TEXT));
+  ASSERT (is_mode (fd + 2, O_TEXT));
+  ASSERT (close (fd + 2) == 0);
+
+  /* Cleanup.  */
+  ASSERT (close (fd) == 0);
+  ASSERT (unlink (file) == 0);
+
+#endif /* HAVE_FCNTL */
+
+  return 0;
+}
-- 
1.6.4.2


>From 7cbb7482507b7f8581bd48c6e38105f213b39c78 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 8 Dec 2009 12:10:52 -0700
Subject: [PATCH 2/4] fcntl: work around cygwin bug in F_DUPFD

fcntl(0,F_DUPFD,10000000) mistakenly failed with EMFILE
instead of EINVAL, and fcntl(0,F_DUPFD,-1) mistakenly passed.

* m4/fcntl.m4 (gl_REPLACE_FCNTL): New macro.
(gl_FUNC_FCNTL): Use it.  Test for F_DUPFD bug.
* lib/fcntl.c (rpl_fcntl) <F_DUPFD>: Work around it.
* doc/posix-functions/fcntl.texi (fcntl): Document it.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                      |    6 ++++++
 doc/posix-functions/fcntl.texi |    4 ++++
 lib/fcntl.c                    |   24 ++++++++++++++++++++++--
 m4/fcntl.m4                    |   36 +++++++++++++++++++++++++++++++-----
 4 files changed, 63 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index af24729..635a19b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2009-12-10  Eric Blake  <address@hidden>

+       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.
+       * lib/fcntl.c (rpl_fcntl) <F_DUPFD>: Work around it.
+       * doc/posix-functions/fcntl.texi (fcntl): Document it.
+
        fcntl: support F_DUPFD_CLOEXEC on systems with fcntl
        * modules/fcntl (Files): List new files.
        (Depends-on): Add cloexec.
diff --git a/doc/posix-functions/fcntl.texi b/doc/posix-functions/fcntl.texi
index 31dca10..c59f603 100644
--- a/doc/posix-functions/fcntl.texi
+++ b/doc/posix-functions/fcntl.texi
@@ -14,6 +14,10 @@ fcntl
 MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11,
 IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.7.1, mingw, Interix 3.5,
 BeOS.
address@hidden
+The @code{F_DUPFD} action of this function does not reject
+out-of-range targets properly on some platforms:
+Cygwin 1.5.x.
 @end itemize

 Portability problems not fixed by Gnulib:
diff --git a/lib/fcntl.c b/lib/fcntl.c
index d8442cc..eb3736f 100644
--- a/lib/fcntl.c
+++ b/lib/fcntl.c
@@ -54,6 +54,26 @@ rpl_fcntl (int fd, int action, ...)
   va_start (arg, action);
   switch (action)
     {
+
+#if FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
+    case F_DUPFD:
+      {
+        int target = va_arg (arg, int);
+        /* Detect invalid target; needed for cygwin 1.5.x.  */
+        if (target < 0 || getdtablesize () <= target)
+          errno = EINVAL;
+        else
+          {
+            result = fcntl (fd, action, target);
+# if REPLACE_FCHDIR
+            if (0 <= result)
+              result = _gl_register_dup (fd, result);
+# endif
+          }
+        break;
+      } /* F_DUPFD */
+#endif /* FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR */
+
     case F_DUPFD_CLOEXEC:
       {
         int target = va_arg (arg, int);
@@ -80,7 +100,7 @@ rpl_fcntl (int fd, int action, ...)
               }
           }
         else
-          result = fcntl (fd, F_DUPFD, target);
+          result = rpl_fcntl (fd, F_DUPFD, target);
         if (0 <= result && have_dupfd_cloexec == -1)
           {
             if (set_cloexec_flag (result, true) < 0)
@@ -96,7 +116,7 @@ rpl_fcntl (int fd, int action, ...)
           result = _gl_register_dup (fd, result);
 #endif
         break;
-      }
+      } /* F_DUPFD_CLOEXEC */

     default:
       {
diff --git a/m4/fcntl.m4 b/m4/fcntl.m4
index 1c33540..40f369e 100644
--- a/m4/fcntl.m4
+++ b/m4/fcntl.m4
@@ -1,13 +1,12 @@
-# fcntl.m4 serial 1
+# fcntl.m4 serial 2
 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,
 dnl with or without modifications, as long as this notice is preserved.

 # For now, this module ensures that fcntl()
-# - supports or emulates F_DUPFD_CLOEXEC
-# Still to be ported to various platforms:
 # - supports F_DUPFD correctly
+# - supports or emulates F_DUPFD_CLOEXEC
 # Still to be ported to mingw:
 # - F_GETFD, F_SETFD, F_DUPFD
 # - F_DUPFD_CLOEXEC
@@ -23,6 +22,23 @@ AC_DEFUN([gl_FUNC_FCNTL],
   if test $ac_cv_func_fcntl = no; then
     HAVE_FCNTL=0
   else
+    dnl cygwin 1.5.x F_DUPFD has wrong errno, and allows negative target
+    AC_CACHE_CHECK([whether fcntl handles F_DUPFD correctly],
+      [gl_cv_func_fcntl_f_dupfd_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>
+]], [[return fcntl (0, F_DUPFD, -1) != -1;
+         ]])],
+         [gl_cv_func_fcntl_f_dupfd_works=yes],
+         [gl_cv_func_fcntl_f_dupfd_works=no],
+         [gl_cv_func_fcntl_f_dupfd_works="guessing no"])])
+    if test "$gl_cv_func_fcntl_f_dupfd_works" != yes; then
+      gl_REPLACE_FCNTL
+      AC_DEFINE([FCNTL_DUPFD_BUGGY], [1], [Define this to 1 if F_DUPFD
+        behavior does not match POSIX])
+    fi
+
+    dnl Many systems lack F_DUPFD_CLOEXEC
     AC_CACHE_CHECK([whether fcntl understands F_DUPFD_CLOEXEC],
       [gl_cv_func_fcntl_f_dupfd_cloexec],
       [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
@@ -36,8 +52,18 @@ choke me
          [gl_cv_func_fcntl_f_dupfd_cloexec=yes],
          [gl_cv_func_fcntl_f_dupfd_cloexec="needs runtime check"])])
     if test "$gl_cv_func_fcntl_f_dupfd_cloexec" != yes; then
-      REPLACE_FCNTL=1
-      AC_LIBOBJ([fcntl])
+      gl_REPLACE_FCNTL
+      dnl No witness macro needed for this bug.
     fi
   fi
 ])
+
+AC_DEFUN([gl_REPLACE_FCNTL],
+[
+  AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
+  AC_CHECK_FUNCS_ONCE([fcntl])
+  if test $ac_cv_func_fcntl = yes; then
+    REPLACE_FCNTL=1
+    AC_LIBOBJ([fcntl])
+  fi
+])
-- 
1.6.4.2


>From 8a90eba1e82d51894b30a0e75a2d0428aaf2bae8 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 21 Aug 2009 15:19:25 -0600
Subject: [PATCH 3/4] 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.
* 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 <address@hidden>
---
 ChangeLog                      |   14 ++++
 doc/posix-functions/fcntl.texi |   10 ++-
 doc/posix-headers/fcntl.texi   |   18 ++---
 lib/fcntl.c                    |  170 ++++++++++++++++++++++++++++++++++++++-
 lib/fcntl.in.h                 |   17 ++++-
 m4/fcntl.m4                    |   14 ++--
 m4/fcntl_h.m4                  |    3 +-
 modules/fcntl                  |    2 +-
 modules/fcntl-h                |    1 +
 tests/test-fcntl.c             |   37 ++++++---
 10 files changed, 246 insertions(+), 40 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 635a19b..fd96734 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2009-12-10  Eric Blake  <address@hidden>

+       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.
+       * 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.
diff --git a/doc/posix-functions/fcntl.texi b/doc/posix-functions/fcntl.texi
index c59f603..2280460 100644
--- a/doc/posix-functions/fcntl.texi
+++ b/doc/posix-functions/fcntl.texi
@@ -18,11 +18,17 @@ fcntl
 The @code{F_DUPFD} action of this function does not reject
 out-of-range targets properly on some platforms:
 Cygwin 1.5.x.
address@hidden
+This function is missing on some platforms, although the replacement
+fails with @code{EINVAL} for unimplemented actions:
+mingw.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-mingw.
+This function does not support @code{F_SETFD}, @code{F_GETFL},
address@hidden, @code{F_GETOWN}, @code{F_SETOWN}, @code{F_GETLK},
address@hidden, and @code{F_SETLKW} on some platforms:
+mingw
 @end itemize
diff --git a/doc/posix-headers/fcntl.texi b/doc/posix-headers/fcntl.texi
index 2475c9b..3237e5f 100644
--- a/doc/posix-headers/fcntl.texi
+++ b/doc/posix-headers/fcntl.texi
@@ -23,12 +23,12 @@ fcntl.h
 on some platforms but not on others.

 @item
address@hidden is not defined on some platforms:
address@hidden, @samp{F_DUPFD}, and @samp{F_GETFD} are not defined
+on some platforms:
 mingw.

 @item
address@hidden is not defined on some platforms (but for now,
-the replacement is only defined on platforms where @code{fcntl} exists).
address@hidden is not defined on many platforms.

 @item
 @samp{AT_FDCWD}, @samp{AT_EACCESS}, @samp{AT_SYMLINK_NOFOLLOW},
@@ -54,14 +54,10 @@ fcntl.h
 on some platforms.

 @item
address@hidden, @samp{F_DUPFD_CLOEXEC}, @samp{F_GETFD}, and
address@hidden are not defined on some platforms:
-mingw
-
address@hidden
address@hidden, @samp{F_SETFL}, @samp{F_GETLK}, @samp{F_SETLK},
address@hidden, @samp{F_GETOWN}, and @samp{F_SETOWN} are not defined
-on some platforms.
address@hidden, @samp{F_GETFL}, @samp{F_SETFL}, @samp{F_GETLK},
address@hidden, @samp{F_SETLOKW}, @samp{F_GETOWN}, and @samp{F_SETOWN}
+are not defined on some platforms:
+mingw.

 @item
 @samp{POSIX_FADV_DONTNEED}, @samp{POSIX_FADV_NOREUSE},
diff --git a/lib/fcntl.c b/lib/fcntl.c
index eb3736f..4f0b6cb 100644
--- a/lib/fcntl.c
+++ b/lib/fcntl.c
@@ -23,15 +23,127 @@
 #include <fcntl.h>

 #include <errno.h>
+#include <limits.h>
 #include <stdarg.h>

 #include "cloexec.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 (index >= fds_to_close_bound)
+        {
+          if (index >= sizeof (fds_to_close))
+            /* 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
@@ -44,7 +156,10 @@
    F_DUPFD_CLOEXEC - duplicate FD, with ACTION being the minimum
    target fd.  If successful, return the duplicate, which will not be
    inheritable; otherwise return -1 and set errno.
-*/
+
+   F_GETFD - 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, ...)
@@ -55,7 +170,14 @@ rpl_fcntl (int fd, int action, ...)
   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);
@@ -78,6 +200,10 @@ rpl_fcntl (int fd, int action, ...)
       {
         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 it exists (that is, if
            gl_F_DUPFD_CLOEXEC is 0 but F_DUPFD_CLOEXEC is defined).
            (We may be running with a glibc that has the macro but with
@@ -111,17 +237,51 @@ rpl_fcntl (int fd, int action, ...)
                 result = -1;
               }
           }
-#if REPLACE_FCHDIR
+# if REPLACE_FCHDIR
         if (0 <= result)
           result = _gl_register_dup (fd, result);
-#endif
+# endif
         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;
       }
     }
diff --git a/lib/fcntl.in.h b/lib/fcntl.in.h
index 11c64e3..e4ef3b5 100644
--- a/lib/fcntl.in.h
+++ b/lib/fcntl.in.h
@@ -57,12 +57,27 @@ extern "C" {
 # if @REPLACE_FCNTL@
 #  undef fcntl
 #  define fcntl rpl_fcntl
+# endif
+# if address@hidden@ || @REPLACE_FCNTL@
 extern int fcntl (int fd, int action, ...);
+
+/* Provide supported F_* macros.  Macros not listed here, and not
+   provided by the implementation, are intentionally left
+   undefined.  */
 #  ifndef F_DUPFD_CLOEXEC
 #   define F_DUPFD_CLOEXEC 0x40000000
 #   define gl_F_DUPFD_CLOEXEC 1
 #  endif
+
+#  ifndef F_DUPFD
+#   define F_DUPFD 1
+#  endif
+
+#  ifndef F_GETFD
+#   define F_GETFD 2
+#  endif
 # endif
+
 /* Witness variable: 1 if gnulib defined F_DUPFD_CLOEXEC, 0 otherwise.  */
 # ifndef gl_F_DUPFD_CLOEXEC
 #  define gl_F_DUPFD_CLOEXEC 0
@@ -97,7 +112,7 @@ extern int openat (int fd, char const *file, int flags, /* 
mode_t mode */ ...);
 }
 #endif

-/* Fix up the FD_* macros.  */
+/* Fix up the FD_* macros, missing only on mingw.  */

 #ifndef FD_CLOEXEC
 # define FD_CLOEXEC 1
diff --git a/m4/fcntl.m4 b/m4/fcntl.m4
index 40f369e..eb43317 100644
--- 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 @@ dnl with or without modifications, as long as this notice is 
preserved.
 # 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
@@ -20,7 +20,7 @@ AC_DEFUN([gl_FUNC_FCNTL],
   AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
   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],
@@ -62,8 +62,10 @@ AC_DEFUN([gl_REPLACE_FCNTL],
 [
   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])
 ])
diff --git a/m4/fcntl_h.m4 b/m4/fcntl_h.m4
index 3825adf..12e435e 100644
--- 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 @@ AC_DEFUN([gl_FCNTL_H_DEFAULTS],
   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])
diff --git a/modules/fcntl b/modules/fcntl
index 961026e..4182747 100644
--- a/modules/fcntl
+++ b/modules/fcntl
@@ -1,5 +1,5 @@
 Description:
-Support for fcntl() action F_DUPFD_CLOEXEC.
+Support for fcntl() action F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD.

 Files:
 m4/fcntl.m4
diff --git a/modules/fcntl-h b/modules/fcntl-h
index 60e078e..6df77dc 100644
--- a/modules/fcntl-h
+++ b/modules/fcntl-h
@@ -28,6 +28,7 @@ fcntl.h: fcntl.in.h $(LINK_WARNING_H)
              -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' \
diff --git a/tests/test-fcntl.c b/tests/test-fcntl.c
index 4dd1995..a7be425 100644
--- a/tests/test-fcntl.c
+++ b/tests/test-fcntl.c
@@ -143,22 +143,16 @@ check_flags (void)
 {
   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
@@ -224,8 +218,6 @@ main (int argc, char **argv)
   ASSERT (func2 (3, 0x80000000) == 0x80000000);
   check_flags ();

-#if HAVE_FCNTL
-
   /* Assume std descriptors were provided by invoker, and ignore fds
      that might have been inherited.  */
   fd = creat (file, 0600);
@@ -318,11 +310,30 @@ main (int argc, char **argv)
   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;
 }
-- 
1.6.4.2


>From 35dc344339de7a6e3ba64a2f0d644bd9200d409b Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 10 Dec 2009 15:42:56 -0700
Subject: [PATCH 4/4] dup3, fchdir: rely more on fcntl

Only call _gl_register_dup at the lowest layers, by making
an fchdir replacement require an fcntl replacement.  Let
fcntl do the work, instead of copying code in dup3.

* modules/fchdir (Depends-on): Add fcntl.
* modules/accept4 (Depends-on): Use fcntl-h, not fcntl.
* modules/dup3 (configure.ac): Set module indicator.
* m4/fchdir.m4 (gl_FUNC_FCHDIR): Replace fcntl if fchdir is
missing.
* lib/fchdir.c (_gl_register_dup): Fix comment.
* lib/cloexec.c (dup_cloexec): Simplify, since REPLACE_FCHDIR
implies that fcntl will do the registration.
* lib/dup3.c (dup3): Likewise.  Let fcntl do more work.
* tests/test-fchdir.c (main): Enhance test.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog           |   12 +++++
 lib/cloexec.c       |    5 --
 lib/dup3.c          |  128 ++++-----------------------------------------------
 lib/fchdir.c        |    5 +--
 m4/fchdir.m4        |    3 +-
 modules/accept4     |    2 +-
 modules/dup3        |    1 +
 modules/fchdir      |    1 +
 tests/test-fchdir.c |    9 ++++
 9 files changed, 36 insertions(+), 130 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fd96734..ee17e1a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-12-10  Eric Blake  <address@hidden>

+       dup3, fchdir: rely more on fcntl
+       * modules/fchdir (Depends-on): Add fcntl.
+       * modules/accept4 (Depends-on): Use fcntl-h, not fcntl.
+       * modules/dup3 (configure.ac): Set module indicator.
+       * m4/fchdir.m4 (gl_FUNC_FCHDIR): Replace fcntl if fchdir is
+       missing.
+       * lib/fchdir.c (_gl_register_dup): Fix comment.
+       * lib/cloexec.c (dup_cloexec): Simplify, since REPLACE_FCHDIR
+       implies that fcntl will do the registration.
+       * lib/dup3.c (dup3): Likewise.  Let fcntl do more work.
+       * tests/test-fchdir.c (main): Enhance test.
+
        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
diff --git a/lib/cloexec.c b/lib/cloexec.c
index 69b45b4..96ade8f 100644
--- a/lib/cloexec.c
+++ b/lib/cloexec.c
@@ -142,11 +142,6 @@ dup_cloexec (int fd)

 # ifdef F_DUPFD_CLOEXEC
   nfd = fcntl (fd, F_DUPFD_CLOEXEC, 0);
-#  if REPLACE_FCHDIR
-  if (0 <= nfd)
-    nfd = _gl_register_dup (fd, nfd);
-#  endif
-
 # else /* !F_DUPFD_CLOEXEC */
   nfd = dup (fd);
   if (0 <= nfd && set_cloexec_flag (nfd, true) < 0)
diff --git a/lib/dup3.c b/lib/dup3.c
index c61b9ba..85412c7 100644
--- a/lib/dup3.c
+++ b/lib/dup3.c
@@ -74,7 +74,7 @@ dup3 (int oldfd, int newfd, int flags)
   }
 #endif

-  if (oldfd < 0 || newfd < 0 || newfd >= getdtablesize ())
+  if (newfd < 0 || newfd >= getdtablesize () || fcntl (oldfd, F_GETFD) == -1)
     {
       errno = EBADF;
       return -1;
@@ -95,130 +95,23 @@ dup3 (int oldfd, int newfd, int flags)
       return -1;
     }

-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-/* Native Woe32 API.  */
-
   if (flags & O_CLOEXEC)
     {
-      /* Neither dup() nor dup2() can create a file descriptor with
-         O_CLOEXEC = O_NOINHERIT set.  We need to use the low-level function
-         _open_osfhandle for this.  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;
-
-      if (old_handle == INVALID_HANDLE_VALUE)
-        {
-          /* oldfd is not open, or is an unassigned standard file
-             descriptor.  */
-          errno = EBADF;
-          return -1;
-        }
-
       close (newfd);
-
-      for (;;)
+      result = fcntl (oldfd, F_DUPFD_CLOEXEC, newfd);
+      if (newfd < result)
         {
-          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 */
-                                FALSE,                /* InheritHandle */
-                                DUPLICATE_SAME_ACCESS)) /* Options */
-            {
-              errno = EBADF; /* arbitrary */
-              result = -1;
-              break;
-            }
-          duplicated_fd = _open_osfhandle ((long) new_handle, flags);
-          if (duplicated_fd < 0)
-            {
-              CloseHandle (new_handle);
-              result = -1;
-              break;
-            }
-          if (duplicated_fd > newfd)
-            /* Shouldn't happen, since newfd is still closed.  */
-            abort ();
-          if (duplicated_fd == newfd)
-            {
-              result = newfd;
-              break;
-            }
-
-          /* Set the bit duplicated_fd in fds_to_close[].  */
-          index = (unsigned int) duplicated_fd / CHAR_BIT;
-          if (index >= fds_to_close_bound)
-            {
-              if (index >= sizeof (fds_to_close))
-                /* 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 (result);
+          errno = EIO;
+          result = -1;
         }
-
-      /* 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 (result == newfd)
-        result = _gl_register_dup (oldfd, newfd);
-#endif
-      return result;
+      if (result < 0)
+        return -1;
     }
-
-  if (dup2 (oldfd, newfd) < 0)
-    return -1;
-
-#else
-/* Unix API.  */
-
-  if (dup2 (oldfd, newfd) < 0)
+  else if (dup2 (oldfd, newfd) < 0)
     return -1;

-  /* POSIX <http://www.opengroup.org/onlinepubs/9699919799/functions/dup.html>
-     says that initially, the FD_CLOEXEC flag is cleared on newfd.  */
-
-  if (flags & O_CLOEXEC)
-    {
-      int fcntl_flags;
-
-      if ((fcntl_flags = fcntl (newfd, F_GETFD, 0)) < 0
-          || fcntl (newfd, F_SETFD, fcntl_flags | FD_CLOEXEC) == -1)
-        {
-          int saved_errno = errno;
-          close (newfd);
-          errno = saved_errno;
-          return -1;
-        }
-    }
-
-#endif
-
 #if O_BINARY
   if (flags & O_BINARY)
     setmode (newfd, O_BINARY);
@@ -226,8 +119,5 @@ dup3 (int oldfd, int newfd, int flags)
     setmode (newfd, O_TEXT);
 #endif

-#if REPLACE_FCHDIR
-  newfd = _gl_register_dup (oldfd, newfd);
-#endif
   return newfd;
 }
diff --git a/lib/fchdir.c b/lib/fchdir.c
index 5930940..8c0ff13 100644
--- a/lib/fchdir.c
+++ b/lib/fchdir.c
@@ -168,10 +168,7 @@ _gl_register_fd (int fd, const char *filename)
    and fcntl.  Both arguments must be valid and distinct file
    descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
    directory, but there is insufficient memory to track the same
-   directory in NEWFD; otherwise return NEWFD.
-
-   FIXME: Need to implement rpl_fcntl in gnulib, and have it call
-   this.  */
+   directory in NEWFD; otherwise return NEWFD.  */
 int
 _gl_register_dup (int oldfd, int newfd)
 {
diff --git a/m4/fchdir.m4 b/m4/fchdir.m4
index e0240a1..fcdf62e 100644
--- a/m4/fchdir.m4
+++ b/m4/fchdir.m4
@@ -1,4 +1,4 @@
-# fchdir.m4 serial 12
+# fchdir.m4 serial 13
 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,
@@ -26,6 +26,7 @@ AC_DEFUN([gl_FUNC_FCHDIR],
     gl_REPLACE_CLOSE
     gl_REPLACE_DUP2
     dnl dup3 is already unconditionally replaced
+    gl_REPLACE_FCNTL
     gl_REPLACE_DIRENT_H
     AC_CACHE_CHECK([whether open can visit directories],
       [gl_cv_func_open_directory_works],
diff --git a/modules/accept4 b/modules/accept4
index d13127e..6f26071 100644
--- a/modules/accept4
+++ b/modules/accept4
@@ -9,7 +9,7 @@ m4/accept4.m4
 Depends-on:
 sys_socket
 accept
-fcntl
+fcntl-h
 binary-io

 configure.ac:
diff --git a/modules/dup3 b/modules/dup3
index 4665044..07228a3 100644
--- a/modules/dup3
+++ b/modules/dup3
@@ -13,6 +13,7 @@ getdtablesize

 configure.ac:
 gl_FUNC_DUP3
+gl_MODULE_INDICATOR([dup3])
 gl_UNISTD_MODULE_INDICATOR([dup3])

 Makefile.am:
diff --git a/modules/fchdir b/modules/fchdir
index 5bae7d6..46b481f 100644
--- a/modules/fchdir
+++ b/modules/fchdir
@@ -10,6 +10,7 @@ close
 dirent
 dirfd
 dup2
+fcntl
 fcntl-h
 include_next
 malloc-posix
diff --git a/tests/test-fchdir.c b/tests/test-fchdir.c
index 1906559..75819eb 100644
--- a/tests/test-fchdir.c
+++ b/tests/test-fchdir.c
@@ -89,6 +89,15 @@ main (void)
           ASSERT (dup_cloexec (fd) == new_fd);
           ASSERT (dup2 (new_fd, fd) == fd);
           ASSERT (close (new_fd) == 0);
+          ASSERT (fcntl (fd, F_DUPFD_CLOEXEC, new_fd) == new_fd);
+          ASSERT (close (fd) == 0);
+          ASSERT (fcntl (new_fd, F_DUPFD, fd) == fd);
+          ASSERT (close (new_fd) == 0);
+#if GNULIB_DUP3
+          ASSERT (dup3 (fd, new_fd, 0) == new_fd);
+          ASSERT (dup3 (new_fd, fd, 0) == fd);
+          ASSERT (close (new_fd) == 0);
+#endif
         }
     }

-- 
1.6.4.2







reply via email to

[Prev in Thread] Current Thread [Next in Thread]