From: Eric Blake Date: Wed, 16 Dec 2009 16:11:32 +0000 (-0700) Subject: fcntl: port portions of fcntl to mingw X-Git-Tag: v0.1~5031 X-Git-Url: http://erislabs.net/gitweb/?p=gnulib.git;a=commitdiff_plain;h=021c8619190757f535c72ad5cdb1d624e19620d6 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) : Provide replacement for mingw. * modules/fcntl (Description): Update. (Depends-on): Add dup2. * m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Add witness. * modules/fcntl-h (Makefile.am): Substitute it. * lib/fcntl.in.h (fcntl): Update declaration. (F_DUPFD, F_GETFD): New macros, when needed. * doc/posix-headers/fcntl.texi (fcntl.h): Update documentation. * doc/posix-functions/fcntl.texi (fcntl): Likewise. * tests/test-fcntl.c (check_flags, main): Enhance test for items we now guarantee. Signed-off-by: Eric Blake --- diff --git a/ChangeLog b/ChangeLog index 01e54a99c..7117e5a19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2009-12-16 Eric Blake + fcntl: port portions of fcntl to mingw + * m4/fcntl.m4 (gl_FUNC_FCNTL): Also build fcntl.c on mingw. + * lib/fcntl.c (fcntl) : Provide + replacement for mingw. + * modules/fcntl (Description): Update. + (Depends-on): Add dup2. + * m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Add witness. + * modules/fcntl-h (Makefile.am): Substitute it. + * lib/fcntl.in.h (fcntl): Update declaration. + (F_DUPFD, F_GETFD): New macros, when needed. + * doc/posix-headers/fcntl.texi (fcntl.h): Update documentation. + * doc/posix-functions/fcntl.texi (fcntl): Likewise. + * tests/test-fcntl.c (check_flags, main): Enhance test for items + we now guarantee. + fcntl: work around cygwin bug in F_DUPFD * m4/fcntl.m4 (gl_REPLACE_FCNTL): New macro. (gl_FUNC_FCNTL): Use it. Test for F_DUPFD bug. diff --git a/doc/posix-functions/fcntl.texi b/doc/posix-functions/fcntl.texi index ce38e429d..2a41ad1d3 100644 --- a/doc/posix-functions/fcntl.texi +++ b/doc/posix-functions/fcntl.texi @@ -20,11 +20,17 @@ Note that the gnulib replacement code is functional but not atomic. The @code{F_DUPFD} action of this function does not reject out-of-range targets properly on some platforms: Cygwin 1.5.x. + +@item +This function is missing on some platforms: +mingw. @end itemize Portability problems not fixed by Gnulib: @itemize @item -This function is missing on some platforms: -mingw. +The replacement function does not support @code{F_SETFD}, +@code{F_GETFL}, @code{F_SETFL}, @code{F_GETOWN}, @code{F_SETOWN}, +@code{F_GETLK}, @code{F_SETLK}, and @code{F_SETLKW} on some platforms: +mingw @end itemize diff --git a/doc/posix-headers/fcntl.texi b/doc/posix-headers/fcntl.texi index 299f1b42e..97cd12a23 100644 --- a/doc/posix-headers/fcntl.texi +++ b/doc/posix-headers/fcntl.texi @@ -23,7 +23,8 @@ and @samp{O_NOLINKS} (not specified by POSIX) are defined on some platforms but not on others. @item -@samp{FD_CLOEXEC} is not defined on some platforms: +@samp{FD_CLOEXEC}, @samp{F_DUPFD}, and @samp{F_GETFD} are not defined +on some platforms: mingw. @item @@ -56,10 +57,6 @@ replacement is not atomic on these platforms. on some platforms. @item -@samp{F_DUPFD} and @samp{F_GETFD} are not defined on some platforms: -mingw. - -@item @samp{F_SETFD}, @samp{F_GETFL}, @samp{F_SETFL}, @samp{F_GETLK}, @samp{F_SETLK}, @samp{F_SETLOKW}, @samp{F_GETOWN}, and @samp{F_SETOWN} are not defined on some platforms: diff --git a/lib/fcntl.c b/lib/fcntl.c index 894b478f6..05d73a94f 100644 --- a/lib/fcntl.c +++ b/lib/fcntl.c @@ -23,17 +23,130 @@ #include #include +#include #include #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 + +/* Upper bound on getdtablesize(). See lib/getdtablesize.c. */ +# define OPEN_MAX_MAX 0x10000 + +/* Duplicate OLDFD into the first available slot of at least NEWFD, + which must be positive, with FLAGS determining whether the duplicate + will be inheritable. */ +static int +dupfd (int oldfd, int newfd, int flags) +{ + /* Mingw has no way to create an arbitrary fd. Iterate until all + file descriptors less than newfd are filled up. */ + HANDLE curr_process = GetCurrentProcess (); + HANDLE old_handle = (HANDLE) _get_osfhandle (oldfd); + unsigned char fds_to_close[OPEN_MAX_MAX / CHAR_BIT]; + unsigned int fds_to_close_bound = 0; + int result; + BOOL inherit = flags & O_CLOEXEC ? FALSE : TRUE; + int mode; + + if (newfd < 0 || getdtablesize () <= newfd) + { + errno = EINVAL; + return -1; + } + if (old_handle == INVALID_HANDLE_VALUE + || (mode = setmode (oldfd, O_BINARY)) == -1) + { + /* oldfd is not open, or is an unassigned standard file + descriptor. */ + errno = EBADF; + return -1; + } + setmode (oldfd, mode); + flags |= mode; + + for (;;) + { + HANDLE new_handle; + int duplicated_fd; + unsigned int index; + + if (!DuplicateHandle (curr_process, /* SourceProcessHandle */ + old_handle, /* SourceHandle */ + curr_process, /* TargetProcessHandle */ + (PHANDLE) &new_handle, /* TargetHandle */ + (DWORD) 0, /* DesiredAccess */ + inherit, /* InheritHandle */ + DUPLICATE_SAME_ACCESS)) /* Options */ + { + /* TODO: Translate GetLastError () into errno. */ + errno = EMFILE; + result = -1; + break; + } + duplicated_fd = _open_osfhandle ((long) new_handle, flags); + if (duplicated_fd < 0) + { + CloseHandle (new_handle); + errno = EMFILE; + result = -1; + break; + } + if (newfd <= duplicated_fd) + { + result = duplicated_fd; + break; + } + + /* Set the bit duplicated_fd in fds_to_close[]. */ + index = (unsigned int) duplicated_fd / CHAR_BIT; + if (fds_to_close_bound <= index) + { + if (sizeof fds_to_close <= index) + /* Need to increase OPEN_MAX_MAX. */ + abort (); + memset (fds_to_close + fds_to_close_bound, '\0', + index + 1 - fds_to_close_bound); + fds_to_close_bound = index + 1; + } + fds_to_close[index] |= 1 << ((unsigned int) duplicated_fd % CHAR_BIT); + } + + /* Close the previous fds that turned out to be too small. */ + { + int saved_errno = errno; + unsigned int duplicated_fd; + + for (duplicated_fd = 0; + duplicated_fd < fds_to_close_bound * CHAR_BIT; + duplicated_fd++) + if ((fds_to_close[duplicated_fd / CHAR_BIT] + >> (duplicated_fd % CHAR_BIT)) + & 1) + close (duplicated_fd); + + errno = saved_errno; + } + +# if REPLACE_FCHDIR + if (0 <= result) + result = _gl_register_dup (oldfd, result); +# endif + return result; +} +#endif /* W32 */ + /* Perform the specified ACTION on the file descriptor FD, possibly using the argument ARG further described below. This replacement handles the following actions, and forwards all others on to the - native fcntl. + native fcntl. An unrecognized ACTION returns -1 with errno set to + EINVAL. F_DUPFD - duplicate FD, with int ARG being the minimum target fd. If successful, return the duplicate, which will be inheritable; @@ -41,7 +154,12 @@ F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum target fd. If successful, return the duplicate, which will not be - inheritable; otherwise return -1 and set errno. */ + inheritable; otherwise return -1 and set errno. + + F_GETFD - ARG need not be present. If successful, return a + non-negative value containing the descriptor flags of FD (only + FD_CLOEXEC is portable, but other flags may be present); otherwise + return -1 and set errno. */ int rpl_fcntl (int fd, int action, /* arg */...) @@ -52,7 +170,14 @@ rpl_fcntl (int fd, int action, /* arg */...) switch (action) { -#if FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR +#if !HAVE_FCNTL + case F_DUPFD: + { + int target = va_arg (arg, int); + result = dupfd (fd, target, 0); + break; + } +#elif FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR case F_DUPFD: { int target = va_arg (arg, int); @@ -75,6 +200,10 @@ rpl_fcntl (int fd, int action, /* arg */...) { int target = va_arg (arg, int); +#if !HAVE_FCNTL + result = dupfd (fd, target, O_CLOEXEC); + break; +#else /* HAVE_FCNTL */ /* Try the system call first, if the headers claim it exists (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we may be running with a glibc that has the macro but with an @@ -89,10 +218,10 @@ rpl_fcntl (int fd, int action, /* arg */...) if (0 <= result || errno != EINVAL) { have_dupfd_cloexec = 1; -#if REPLACE_FCHDIR +# if REPLACE_FCHDIR if (0 <= result) result = _gl_register_dup (fd, result); -#endif +# endif } else { @@ -116,12 +245,46 @@ rpl_fcntl (int fd, int action, /* arg */...) } } 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 34ea1cfc8..1a4602bfc 100644 --- a/lib/fcntl.in.h +++ b/lib/fcntl.in.h @@ -59,6 +59,8 @@ extern "C" { # if @REPLACE_FCNTL@ # undef fcntl # define fcntl rpl_fcntl +# endif +# if !@HAVE_FCNTL@ || @REPLACE_FCNTL@ extern int fcntl (int fd, int action, ...); # endif #elif defined GNULIB_POSIXCHECK @@ -104,14 +106,14 @@ extern int openat (int fd, char const *file, int flags, /* mode_t mode */ ...) } #endif -/* Fix up the FD_* macros. */ +/* Fix up the FD_* macros, only known to be missing on mingw. */ #ifndef FD_CLOEXEC # define FD_CLOEXEC 1 #endif /* Fix up the supported F_* macros. Intentionally leave other F_* - macros undefined. */ + macros undefined. Only known to be missing on mingw. */ #ifndef F_DUPFD_CLOEXEC # define F_DUPFD_CLOEXEC 0x40000000 @@ -121,6 +123,14 @@ extern int openat (int fd, char const *file, int flags, /* mode_t mode */ ...) # define GNULIB_defined_F_DUPFD_CLOEXEC 0 #endif +#ifndef F_DUPFD +# define F_DUPFD 1 +#endif + +#ifndef F_GETFD +# define F_GETFD 2 +#endif + /* Fix up the O_* macros. */ #if !defined O_DIRECT && defined O_DIRECTIO diff --git a/m4/fcntl.m4 b/m4/fcntl.m4 index b2a7e6bc2..4fc5f6bfa 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 @@ -21,7 +21,7 @@ AC_DEFUN([gl_FUNC_FCNTL], AC_REQUIRE([AC_CANONICAL_HOST]) AC_CHECK_FUNCS_ONCE([fcntl]) if test $ac_cv_func_fcntl = no; then - HAVE_FCNTL=0 + gl_REPLACE_FCNTL else dnl cygwin 1.5.x F_DUPFD has wrong errno, and allows negative target AC_CACHE_CHECK([whether fcntl handles F_DUPFD correctly], @@ -74,8 +74,10 @@ AC_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 3825adf97..12e435e1a 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 b4b2ae43e..14298b864 100644 --- a/modules/fcntl +++ b/modules/fcntl @@ -1,11 +1,12 @@ Description: -Support for fcntl() action F_DUPFD_CLOEXEC. +Support for fcntl() action F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD. Files: m4/fcntl.m4 lib/fcntl.c Depends-on: +dup2 fcntl-h configure.ac: diff --git a/modules/fcntl-h b/modules/fcntl-h index 322920a71..aa6d8a0b3 100644 --- a/modules/fcntl-h +++ b/modules/fcntl-h @@ -29,6 +29,7 @@ fcntl.h: fcntl.in.h $(LINK_WARNING_H) $(ARG_NONNULL_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 c5a931c09..8ea130101 100644 --- a/tests/test-fcntl.c +++ b/tests/test-fcntl.c @@ -155,22 +155,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 @@ -241,8 +235,6 @@ main (int argc, char **argv) } check_flags (); -#if HAVE_FCNTL - /* Assume std descriptors were provided by invoker, and ignore fds that might have been inherited. */ fd = creat (file, 0600); @@ -335,11 +327,30 @@ 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; }