fcntl: support F_DUPFD_CLOEXEC on systems with fcntl
authorEric Blake <ebb9@byu.net>
Mon, 7 Dec 2009 18:50:59 +0000 (11:50 -0700)
committerEric Blake <ebb9@byu.net>
Thu, 17 Dec 2009 02:25:30 +0000 (19:25 -0700)
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.
(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.
(F_DUPFD_CLOEXEC, GNULIB_defined_F_DUPFD_CLOEXEC): New macro when
needed, plus a witness.
* 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 <ebb9@byu.net>
ChangeLog
doc/posix-functions/fcntl.texi
doc/posix-headers/fcntl.texi
lib/fcntl.c [new file with mode: 0644]
lib/fcntl.in.h
m4/fcntl.m4 [new file with mode: 0644]
m4/fcntl_h.m4
modules/fcntl
modules/fcntl-h
modules/fcntl-tests [new file with mode: 0644]
tests/test-fcntl.c [new file with mode: 0644]

index 013360d..94e8228 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,21 @@
 2009-12-16  Eric Blake  <ebb9@byu.net>
 
+       fcntl: support F_DUPFD_CLOEXEC on systems with fcntl
+       * modules/fcntl (Files): List new files.
+       (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.
+       (F_DUPFD_CLOEXEC, GNULIB_defined_F_DUPFD_CLOEXEC): New macro when
+       needed, plus a witness.
+       * 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.
+
        binary-io: avoid potential compilation warning
        * lib/binary-io.h [__DJGPP__]: Avoid null preprocessor
        directives.
index 143ac63..5e1aea5 100644 (file)
@@ -4,10 +4,17 @@
 
 POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/fcntl.html}
 
-Gnulib module: ---
+Gnulib module: fcntl
 
 Portability problems fixed by Gnulib:
 @itemize
+@item
+This function does not support @code{F_DUPFD_CLOEXEC} on some
+platforms:
+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.
+Note that the gnulib replacement code is functional but not atomic.
 @end itemize
 
 Portability problems not fixed by Gnulib:
index 340cf28..299f1b4 100644 (file)
@@ -27,6 +27,12 @@ on some platforms but not on others.
 mingw.
 
 @item
+@samp{F_DUPFD_CLOEXEC} is not defined on some platforms:
+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.
+
+@item
 @samp{AT_FDCWD}, @samp{AT_EACCESS}, @samp{AT_SYMLINK_NOFOLLOW},
 @samp{AT_SYMLINK_FOLLOW}, and @samp{AT_REMOVEDIR}
 are not defined on many platforms:
@@ -50,14 +56,14 @@ replacement is not atomic on these platforms.
 on some platforms.
 
 @item
-@samp{F_DUPFD}, @samp{F_DUPFD_CLOEXEC}, @samp{F_GETFD}, and
-@samp{F_SETFD} are not defined on some platforms:
-mingw
+@samp{F_DUPFD} and @samp{F_GETFD} are not defined on some platforms:
+mingw.
 
 @item
-@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.
+@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:
+mingw.
 
 @item
 @samp{POSIX_FADV_DONTNEED}, @samp{POSIX_FADV_NOREUSE},
diff --git a/lib/fcntl.c b/lib/fcntl.c
new file mode 100644 (file)
index 0000000..3c05d31
--- /dev/null
@@ -0,0 +1,104 @@
+/* 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 <ebb9@byu.net>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <fcntl.h>
+
+#include <errno.h>
+#include <stdarg.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_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.  */
+
+int
+rpl_fcntl (int fd, int action, /* arg */...)
+{
+  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 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
+           older kernel that does not support it.  Cache the
+           information on whether the system call really works, but
+           avoid caching failure if the corresponding F_DUPFD fails
+           for any reason.  0 = unknown, 1 = yes, -1 = no.  */
+        static int have_dupfd_cloexec = GNULIB_defined_F_DUPFD_CLOEXEC ? -1 : 0;
+        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)
+          {
+            int flags = fcntl (result, F_GETFD);
+            if (flags < 0 || fcntl (result, F_SETFD, flags | FD_CLOEXEC) == -1)
+              {
+                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;
+      } /* F_DUPFD_CLOEXEC */
+
+    default:
+      {
+        void *p = va_arg (arg, void *);
+        result = fcntl (fd, action, p);
+        break;
+      }
+    }
+  va_end (arg);
+  return result;
+}
index 953e065..34ea1cf 100644 (file)
 extern "C" {
 #endif
 
+#if @GNULIB_FCNTL@
+# if @REPLACE_FCNTL@
+#  undef fcntl
+#  define fcntl rpl_fcntl
+extern int fcntl (int fd, int action, ...);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef fcntl
+# define fcntl \
+    (GL_LINK_WARNING ("fcntl is not always POSIX compliant - " \
+                      "use gnulib module fcntl for portability"), \
+     fcntl)
+#endif
+
 #if @GNULIB_OPEN@
 # if @REPLACE_OPEN@
 #  undef open
@@ -96,6 +110,17 @@ extern int openat (int fd, char const *file, int flags, /* mode_t mode */ ...)
 # define FD_CLOEXEC 1
 #endif
 
+/* Fix up the supported F_* macros.  Intentionally leave other F_*
+   macros undefined.  */
+
+#ifndef F_DUPFD_CLOEXEC
+# define F_DUPFD_CLOEXEC 0x40000000
+/* Witness variable: 1 if gnulib defined F_DUPFD_CLOEXEC, 0 otherwise.  */
+# define GNULIB_defined_F_DUPFD_CLOEXEC 1
+#else
+# define GNULIB_defined_F_DUPFD_CLOEXEC 0
+#endif
+
 /* Fix up the O_* macros.  */
 
 #if !defined O_DIRECT && defined O_DIRECTIO
diff --git a/m4/fcntl.m4 b/m4/fcntl.m4
new file mode 100644 (file)
index 0000000..f361b7d
--- /dev/null
@@ -0,0 +1,49 @@
+# 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>
+#ifndef F_DUPFD_CLOEXEC
+choke me
+#endif
+         ]])],
+         [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#ifdef __linux__
+/* The Linux kernel only added F_DUPFD_CLOEXEC in 2.6.24, so 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"])],
+         [gl_cv_func_fcntl_f_dupfd_cloexec=no])])
+    if test "$gl_cv_func_fcntl_f_dupfd_cloexec" != yes; then
+      REPLACE_FCNTL=1
+      AC_LIBOBJ([fcntl])
+    fi
+  fi
+])
index 40a1803..3825adf 100644 (file)
@@ -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])
 ])
index 1107cac..b4b2ae4 100644 (file)
@@ -1,12 +1,16 @@
 Description:
-Placeholder for eventual fcntl() replacement.
+Support for fcntl() action F_DUPFD_CLOEXEC.
 
 Files:
+m4/fcntl.m4
+lib/fcntl.c
 
 Depends-on:
 fcntl-h
 
 configure.ac:
+gl_FUNC_FCNTL
+gl_FCNTL_MODULE_INDICATOR([fcntl])
 
 Makefile.am:
 
index 6eaec52..322920a 100644 (file)
@@ -16,7 +16,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.
@@ -26,11 +26,13 @@ fcntl.h: fcntl.in.h $(LINK_WARNING_H) $(ARG_NONNULL_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)' \
              -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
              < $(srcdir)/fcntl.in.h; \
diff --git a/modules/fcntl-tests b/modules/fcntl-tests
new file mode 100644 (file)
index 0000000..e81a5e9
--- /dev/null
@@ -0,0 +1,13 @@
+Files:
+tests/test-fcntl.c
+
+Depends-on:
+binary-io
+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 (file)
index 0000000..c5a931c
--- /dev/null
@@ -0,0 +1,345 @@
+/* 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 <ebb9@byu.net>, 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 or a pointer to a
+   struct, and func2 behaves like rpl_fcntl that doesn't know what
+   type to forward.  */
+struct dummy_struct
+{
+  long filler;
+  int value;
+};
+static int
+func1 (int a, ...)
+{
+  va_list arg;
+  int i;
+  va_start (arg, a);
+  if (a < 4)
+    i = va_arg (arg, int);
+  else
+    {
+      struct dummy_struct *s = va_arg (arg, struct dummy_struct *);
+      i = s->value;
+    }
+  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;
+
+  /* Sanity check that rpl_fcntl is likely to work.  */
+  ASSERT (func2 (1, 2) == 2);
+  ASSERT (func2 (2, -2) == -2);
+  ASSERT (func2 (3, 0x80000000) == 0x80000000);
+  {
+    struct dummy_struct s = { 0L, 4 };
+    ASSERT (func2 (4, &s) == 4);
+  }
+  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;
+}