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 <e...@byu.net> 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 <e...@byu.net> --- 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 <e...@byu.net> + 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 +...@item +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 +...@samp{f_dupfd_cloexec} is not defined on some platforms (but for now, +the replacement is only defined on platforms where @code{fcntl} exists). + +...@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: 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 <e...@byu.net>. */ + +#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 !...@have_openat@ || @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; \ } > $...@-t && \ 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 <e...@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, 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 <e...@byu.net> 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 <e...@byu.net> --- 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 <e...@byu.net> + 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. +...@item +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 <e...@byu.net> 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 <e...@byu.net> --- 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 <e...@byu.net> + fcntl: port portions of fcntl to mingw + * m4/fcntl.m4 (gl_FUNC_FCNTL): Also build fcntl.c on mingw. + * lib/fcntl.c (fcntl) <F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD>: Provide + replacement for mingw. + * modules/fcntl (Description): Update. + * 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. +...@item +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}, +...@code{f_setfl}, @code{F_GETOWN}, @code{F_SETOWN}, @code{F_GETLK}, +...@code{f_setlk}, and @code{F_SETLKW} on some platforms: +mingw @end itemize 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 -...@samp{fd_cloexec} is not defined on some platforms: +...@samp{fd_cloexec}, @samp{F_DUPFD}, and @samp{F_GETFD} are not defined +on some platforms: mingw. @item -...@samp{f_dupfd_cloexec} is not defined on some platforms (but for now, -the replacement is only defined on platforms where @code{fcntl} exists). +...@samp{f_dupfd_cloexec} 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 -...@samp{f_dupfd}, @samp{F_DUPFD_CLOEXEC}, @samp{F_GETFD}, and -...@samp{f_setfd} 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 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 !...@have_fcntl@ || @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 <e...@byu.net> 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 <e...@byu.net> --- 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 <e...@byu.net> + 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