doc: use ASCII in .texi files where UTF-8 isn't needed
[gnulib.git] / lib / fcntl.c
index 894b478..54f7486 100644 (file)
@@ -1,6 +1,6 @@
 /* Provide file descriptor control.
 
-   Copyright (C) 2009 Free Software Foundation, Inc.
+   Copyright (C) 2009-2014 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
 #include <fcntl.h>
 
 #include <errno.h>
+#include <limits.h>
 #include <stdarg.h>
+#include <unistd.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 native Windows API functions.  */
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+/* Get _get_osfhandle.  */
+# include "msvc-nothrow.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 ((intptr_t) 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;
 
    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 +174,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);
@@ -61,7 +190,21 @@ rpl_fcntl (int fd, int action, /* arg */...)
           errno = EINVAL;
         else
           {
+            /* Haiku alpha 2 loses fd flags on original.  */
+            int flags = fcntl (fd, F_GETFD);
+            if (flags < 0)
+              {
+                result = -1;
+                break;
+              }
             result = fcntl (fd, action, target);
+            if (0 <= result && fcntl (fd, F_SETFD, flags) == -1)
+              {
+                int saved_errno = errno;
+                close (result);
+                result = -1;
+                errno = saved_errno;
+              }
 # if REPLACE_FCHDIR
             if (0 <= result)
               result = _gl_register_dup (fd, result);
@@ -75,6 +218,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 +236,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 +263,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;
       }
     }