# HG changeset patch # User Eric Blake # Date 1260211859 25200 # Node ID f7624052e60d23aa28c5b456cd338c95474a1d7c # Parent 41db359d31eee2502d369df662e16a5c332cd605 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. (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 diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,21 @@ 2009-12-16 Eric Blake + 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. diff --git a/doc/posix-functions/fcntl.texi b/doc/posix-functions/fcntl.texi --- a/doc/posix-functions/fcntl.texi +++ b/doc/posix-functions/fcntl.texi @@ -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: diff --git a/doc/posix-headers/fcntl.texi b/doc/posix-headers/fcntl.texi --- a/doc/posix-headers/fcntl.texi +++ b/doc/posix-headers/fcntl.texi @@ -27,6 +27,12 @@ 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 @@ 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 --- /dev/null +++ b/lib/fcntl.c @@ -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 . */ + +/* Written by Eric Blake . */ + +#include + +/* Specification. */ +#include + +#include +#include + +#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; +} diff --git a/lib/fcntl.in.h b/lib/fcntl.in.h --- a/lib/fcntl.in.h +++ b/lib/fcntl.in.h @@ -55,6 +55,20 @@ 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 @@ # 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 --- /dev/null +++ b/m4/fcntl.m4 @@ -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 +#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 +]) diff --git a/m4/fcntl_h.m4 b/m4/fcntl_h.m4 --- 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_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_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 --- a/modules/fcntl +++ b/modules/fcntl @@ -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: diff --git a/modules/fcntl-h b/modules/fcntl-h --- a/modules/fcntl-h +++ b/modules/fcntl-h @@ -16,7 +16,7 @@ gl_FCNTL_H Makefile.am: -BUILT_SOURCES += $(FCNTL_H) +BUILT_SOURCES += fcntl.h # We need the following in order to create when the system # doesn't have one that works with the given compiler. @@ -26,11 +26,13 @@ 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 --- /dev/null +++ b/modules/fcntl-tests @@ -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 --- /dev/null +++ b/tests/test-fcntl.c @@ -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 . */ + +/* Written by Eric Blake , 2009. */ + +#include + +/* Specification. */ +#include + +/* Helpers. */ +#include +#include +#include +#include +#include +#include + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +/* Get declarations of the Win32 API functions. */ +# define WIN32_LEAN_AND_MEAN +# include +#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; +}