Blame gnu/fcntl.c

Packit 1ef1a9
/* Provide file descriptor control.
Packit 1ef1a9
Packit 1ef1a9
   Copyright (C) 2009-2015 Free Software Foundation, Inc.
Packit 1ef1a9
Packit 1ef1a9
   This program is free software: you can redistribute it and/or modify
Packit 1ef1a9
   it under the terms of the GNU General Public License as published by
Packit 1ef1a9
   the Free Software Foundation; either version 3 of the License, or
Packit 1ef1a9
   (at your option) any later version.
Packit 1ef1a9
Packit 1ef1a9
   This program is distributed in the hope that it will be useful,
Packit 1ef1a9
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 1ef1a9
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 1ef1a9
   GNU General Public License for more details.
Packit 1ef1a9
Packit 1ef1a9
   You should have received a copy of the GNU General Public License
Packit 1ef1a9
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 1ef1a9
Packit 1ef1a9
/* Written by Eric Blake <ebb9@byu.net>.  */
Packit 1ef1a9
Packit 1ef1a9
#include <config.h>
Packit 1ef1a9
Packit 1ef1a9
/* Specification.  */
Packit 1ef1a9
#include <fcntl.h>
Packit 1ef1a9
Packit 1ef1a9
#include <errno.h>
Packit 1ef1a9
#include <limits.h>
Packit 1ef1a9
#include <stdarg.h>
Packit 1ef1a9
#include <unistd.h>
Packit 1ef1a9
Packit 1ef1a9
#if !HAVE_FCNTL
Packit 1ef1a9
# define rpl_fcntl fcntl
Packit 1ef1a9
#endif
Packit 1ef1a9
#undef fcntl
Packit 1ef1a9
Packit 1ef1a9
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
Packit 1ef1a9
/* Get declarations of the native Windows API functions.  */
Packit 1ef1a9
# define WIN32_LEAN_AND_MEAN
Packit 1ef1a9
# include <windows.h>
Packit 1ef1a9
Packit 1ef1a9
/* Get _get_osfhandle.  */
Packit 1ef1a9
# include "msvc-nothrow.h"
Packit 1ef1a9
Packit 1ef1a9
/* Upper bound on getdtablesize().  See lib/getdtablesize.c.  */
Packit 1ef1a9
# define OPEN_MAX_MAX 0x10000
Packit 1ef1a9
Packit 1ef1a9
/* Duplicate OLDFD into the first available slot of at least NEWFD,
Packit 1ef1a9
   which must be positive, with FLAGS determining whether the duplicate
Packit 1ef1a9
   will be inheritable.  */
Packit 1ef1a9
static int
Packit 1ef1a9
dupfd (int oldfd, int newfd, int flags)
Packit 1ef1a9
{
Packit 1ef1a9
  /* Mingw has no way to create an arbitrary fd.  Iterate until all
Packit 1ef1a9
     file descriptors less than newfd are filled up.  */
Packit 1ef1a9
  HANDLE curr_process = GetCurrentProcess ();
Packit 1ef1a9
  HANDLE old_handle = (HANDLE) _get_osfhandle (oldfd);
Packit 1ef1a9
  unsigned char fds_to_close[OPEN_MAX_MAX / CHAR_BIT];
Packit 1ef1a9
  unsigned int fds_to_close_bound = 0;
Packit 1ef1a9
  int result;
Packit 1ef1a9
  BOOL inherit = flags & O_CLOEXEC ? FALSE : TRUE;
Packit 1ef1a9
  int mode;
Packit 1ef1a9
Packit 1ef1a9
  if (newfd < 0 || getdtablesize () <= newfd)
Packit 1ef1a9
    {
Packit 1ef1a9
      errno = EINVAL;
Packit 1ef1a9
      return -1;
Packit 1ef1a9
    }
Packit 1ef1a9
  if (old_handle == INVALID_HANDLE_VALUE
Packit 1ef1a9
      || (mode = setmode (oldfd, O_BINARY)) == -1)
Packit 1ef1a9
    {
Packit 1ef1a9
      /* oldfd is not open, or is an unassigned standard file
Packit 1ef1a9
         descriptor.  */
Packit 1ef1a9
      errno = EBADF;
Packit 1ef1a9
      return -1;
Packit 1ef1a9
    }
Packit 1ef1a9
  setmode (oldfd, mode);
Packit 1ef1a9
  flags |= mode;
Packit 1ef1a9
Packit 1ef1a9
  for (;;)
Packit 1ef1a9
    {
Packit 1ef1a9
      HANDLE new_handle;
Packit 1ef1a9
      int duplicated_fd;
Packit 1ef1a9
      unsigned int index;
Packit 1ef1a9
Packit 1ef1a9
      if (!DuplicateHandle (curr_process,           /* SourceProcessHandle */
Packit 1ef1a9
                            old_handle,             /* SourceHandle */
Packit 1ef1a9
                            curr_process,           /* TargetProcessHandle */
Packit 1ef1a9
                            (PHANDLE) &new_handle,  /* TargetHandle */
Packit 1ef1a9
                            (DWORD) 0,              /* DesiredAccess */
Packit 1ef1a9
                            inherit,                /* InheritHandle */
Packit 1ef1a9
                            DUPLICATE_SAME_ACCESS)) /* Options */
Packit 1ef1a9
        {
Packit 1ef1a9
          switch (GetLastError ())
Packit 1ef1a9
            {
Packit 1ef1a9
              case ERROR_TOO_MANY_OPEN_FILES:
Packit 1ef1a9
                errno = EMFILE;
Packit 1ef1a9
                break;
Packit 1ef1a9
              case ERROR_INVALID_HANDLE:
Packit 1ef1a9
              case ERROR_INVALID_TARGET_HANDLE:
Packit 1ef1a9
              case ERROR_DIRECT_ACCESS_HANDLE:
Packit 1ef1a9
                errno = EBADF;
Packit 1ef1a9
                break;
Packit 1ef1a9
              case ERROR_INVALID_PARAMETER:
Packit 1ef1a9
              case ERROR_INVALID_FUNCTION:
Packit 1ef1a9
              case ERROR_INVALID_ACCESS:
Packit 1ef1a9
                errno = EINVAL;
Packit 1ef1a9
                break;
Packit 1ef1a9
              default:
Packit 1ef1a9
                errno = EACCES;
Packit 1ef1a9
                break;
Packit 1ef1a9
            }
Packit 1ef1a9
          result = -1;
Packit 1ef1a9
          break;
Packit 1ef1a9
        }
Packit 1ef1a9
      duplicated_fd = _open_osfhandle ((intptr_t) new_handle, flags);
Packit 1ef1a9
      if (duplicated_fd < 0)
Packit 1ef1a9
        {
Packit 1ef1a9
          CloseHandle (new_handle);
Packit 1ef1a9
          result = -1;
Packit 1ef1a9
          break;
Packit 1ef1a9
        }
Packit 1ef1a9
      if (newfd <= duplicated_fd)
Packit 1ef1a9
        {
Packit 1ef1a9
          result = duplicated_fd;
Packit 1ef1a9
          break;
Packit 1ef1a9
        }
Packit 1ef1a9
Packit 1ef1a9
      /* Set the bit duplicated_fd in fds_to_close[].  */
Packit 1ef1a9
      index = (unsigned int) duplicated_fd / CHAR_BIT;
Packit 1ef1a9
      if (fds_to_close_bound <= index)
Packit 1ef1a9
        {
Packit 1ef1a9
          if (sizeof fds_to_close <= index)
Packit 1ef1a9
            /* Need to increase OPEN_MAX_MAX.  */
Packit 1ef1a9
            abort ();
Packit 1ef1a9
          memset (fds_to_close + fds_to_close_bound, '\0',
Packit 1ef1a9
                  index + 1 - fds_to_close_bound);
Packit 1ef1a9
          fds_to_close_bound = index + 1;
Packit 1ef1a9
        }
Packit 1ef1a9
      fds_to_close[index] |= 1 << ((unsigned int) duplicated_fd % CHAR_BIT);
Packit 1ef1a9
    }
Packit 1ef1a9
Packit 1ef1a9
  /* Close the previous fds that turned out to be too small.  */
Packit 1ef1a9
  {
Packit 1ef1a9
    int saved_errno = errno;
Packit 1ef1a9
    unsigned int duplicated_fd;
Packit 1ef1a9
Packit 1ef1a9
    for (duplicated_fd = 0;
Packit 1ef1a9
         duplicated_fd < fds_to_close_bound * CHAR_BIT;
Packit 1ef1a9
         duplicated_fd++)
Packit 1ef1a9
      if ((fds_to_close[duplicated_fd / CHAR_BIT]
Packit 1ef1a9
           >> (duplicated_fd % CHAR_BIT))
Packit 1ef1a9
          & 1)
Packit 1ef1a9
        close (duplicated_fd);
Packit 1ef1a9
Packit 1ef1a9
    errno = saved_errno;
Packit 1ef1a9
  }
Packit 1ef1a9
Packit 1ef1a9
# if REPLACE_FCHDIR
Packit 1ef1a9
  if (0 <= result)
Packit 1ef1a9
    result = _gl_register_dup (oldfd, result);
Packit 1ef1a9
# endif
Packit 1ef1a9
  return result;
Packit 1ef1a9
}
Packit 1ef1a9
#endif /* W32 */
Packit 1ef1a9
Packit 1ef1a9
/* Perform the specified ACTION on the file descriptor FD, possibly
Packit 1ef1a9
   using the argument ARG further described below.  This replacement
Packit 1ef1a9
   handles the following actions, and forwards all others on to the
Packit 1ef1a9
   native fcntl.  An unrecognized ACTION returns -1 with errno set to
Packit 1ef1a9
   EINVAL.
Packit 1ef1a9
Packit 1ef1a9
   F_DUPFD - duplicate FD, with int ARG being the minimum target fd.
Packit 1ef1a9
   If successful, return the duplicate, which will be inheritable;
Packit 1ef1a9
   otherwise return -1 and set errno.
Packit 1ef1a9
Packit 1ef1a9
   F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum
Packit 1ef1a9
   target fd.  If successful, return the duplicate, which will not be
Packit 1ef1a9
   inheritable; otherwise return -1 and set errno.
Packit 1ef1a9
Packit 1ef1a9
   F_GETFD - ARG need not be present.  If successful, return a
Packit 1ef1a9
   non-negative value containing the descriptor flags of FD (only
Packit 1ef1a9
   FD_CLOEXEC is portable, but other flags may be present); otherwise
Packit 1ef1a9
   return -1 and set errno.  */
Packit 1ef1a9
Packit 1ef1a9
int
Packit 1ef1a9
rpl_fcntl (int fd, int action, /* arg */...)
Packit 1ef1a9
{
Packit 1ef1a9
  va_list arg;
Packit 1ef1a9
  int result = -1;
Packit 1ef1a9
  va_start (arg, action);
Packit 1ef1a9
  switch (action)
Packit 1ef1a9
    {
Packit 1ef1a9
Packit 1ef1a9
#if !HAVE_FCNTL
Packit 1ef1a9
    case F_DUPFD:
Packit 1ef1a9
      {
Packit 1ef1a9
        int target = va_arg (arg, int);
Packit 1ef1a9
        result = dupfd (fd, target, 0);
Packit 1ef1a9
        break;
Packit 1ef1a9
      }
Packit 1ef1a9
#elif FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
Packit 1ef1a9
    case F_DUPFD:
Packit 1ef1a9
      {
Packit 1ef1a9
        int target = va_arg (arg, int);
Packit 1ef1a9
        /* Detect invalid target; needed for cygwin 1.5.x.  */
Packit 1ef1a9
        if (target < 0 || getdtablesize () <= target)
Packit 1ef1a9
          errno = EINVAL;
Packit 1ef1a9
        else
Packit 1ef1a9
          {
Packit 1ef1a9
            /* Haiku alpha 2 loses fd flags on original.  */
Packit 1ef1a9
            int flags = fcntl (fd, F_GETFD);
Packit 1ef1a9
            if (flags < 0)
Packit 1ef1a9
              {
Packit 1ef1a9
                result = -1;
Packit 1ef1a9
                break;
Packit 1ef1a9
              }
Packit 1ef1a9
            result = fcntl (fd, action, target);
Packit 1ef1a9
            if (0 <= result && fcntl (fd, F_SETFD, flags) == -1)
Packit 1ef1a9
              {
Packit 1ef1a9
                int saved_errno = errno;
Packit 1ef1a9
                close (result);
Packit 1ef1a9
                result = -1;
Packit 1ef1a9
                errno = saved_errno;
Packit 1ef1a9
              }
Packit 1ef1a9
# if REPLACE_FCHDIR
Packit 1ef1a9
            if (0 <= result)
Packit 1ef1a9
              result = _gl_register_dup (fd, result);
Packit 1ef1a9
# endif
Packit 1ef1a9
          }
Packit 1ef1a9
        break;
Packit 1ef1a9
      } /* F_DUPFD */
Packit 1ef1a9
#endif /* FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR */
Packit 1ef1a9
Packit 1ef1a9
    case F_DUPFD_CLOEXEC:
Packit 1ef1a9
      {
Packit 1ef1a9
        int target = va_arg (arg, int);
Packit 1ef1a9
Packit 1ef1a9
#if !HAVE_FCNTL
Packit 1ef1a9
        result = dupfd (fd, target, O_CLOEXEC);
Packit 1ef1a9
        break;
Packit 1ef1a9
#else /* HAVE_FCNTL */
Packit 1ef1a9
        /* Try the system call first, if the headers claim it exists
Packit 1ef1a9
           (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we
Packit 1ef1a9
           may be running with a glibc that has the macro but with an
Packit 1ef1a9
           older kernel that does not support it.  Cache the
Packit 1ef1a9
           information on whether the system call really works, but
Packit 1ef1a9
           avoid caching failure if the corresponding F_DUPFD fails
Packit 1ef1a9
           for any reason.  0 = unknown, 1 = yes, -1 = no.  */
Packit 1ef1a9
        static int have_dupfd_cloexec = GNULIB_defined_F_DUPFD_CLOEXEC ? -1 : 0;
Packit 1ef1a9
        if (0 <= have_dupfd_cloexec)
Packit 1ef1a9
          {
Packit 1ef1a9
            result = fcntl (fd, action, target);
Packit 1ef1a9
            if (0 <= result || errno != EINVAL)
Packit 1ef1a9
              {
Packit 1ef1a9
                have_dupfd_cloexec = 1;
Packit 1ef1a9
# if REPLACE_FCHDIR
Packit 1ef1a9
                if (0 <= result)
Packit 1ef1a9
                  result = _gl_register_dup (fd, result);
Packit 1ef1a9
# endif
Packit 1ef1a9
              }
Packit 1ef1a9
            else
Packit 1ef1a9
              {
Packit 1ef1a9
                result = rpl_fcntl (fd, F_DUPFD, target);
Packit 1ef1a9
                if (result < 0)
Packit 1ef1a9
                  break;
Packit 1ef1a9
                have_dupfd_cloexec = -1;
Packit 1ef1a9
              }
Packit 1ef1a9
          }
Packit 1ef1a9
        else
Packit 1ef1a9
          result = rpl_fcntl (fd, F_DUPFD, target);
Packit 1ef1a9
        if (0 <= result && have_dupfd_cloexec == -1)
Packit 1ef1a9
          {
Packit 1ef1a9
            int flags = fcntl (result, F_GETFD);
Packit 1ef1a9
            if (flags < 0 || fcntl (result, F_SETFD, flags | FD_CLOEXEC) == -1)
Packit 1ef1a9
              {
Packit 1ef1a9
                int saved_errno = errno;
Packit 1ef1a9
                close (result);
Packit 1ef1a9
                errno = saved_errno;
Packit 1ef1a9
                result = -1;
Packit 1ef1a9
              }
Packit 1ef1a9
          }
Packit 1ef1a9
        break;
Packit 1ef1a9
#endif /* HAVE_FCNTL */
Packit 1ef1a9
      } /* F_DUPFD_CLOEXEC */
Packit 1ef1a9
Packit 1ef1a9
#if !HAVE_FCNTL
Packit 1ef1a9
    case F_GETFD:
Packit 1ef1a9
      {
Packit 1ef1a9
# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
Packit 1ef1a9
        HANDLE handle = (HANDLE) _get_osfhandle (fd);
Packit 1ef1a9
        DWORD flags;
Packit 1ef1a9
        if (handle == INVALID_HANDLE_VALUE
Packit 1ef1a9
            || GetHandleInformation (handle, &flags) == 0)
Packit 1ef1a9
          errno = EBADF;
Packit 1ef1a9
        else
Packit 1ef1a9
          result = (flags & HANDLE_FLAG_INHERIT) ? 0 : FD_CLOEXEC;
Packit 1ef1a9
# else /* !W32 */
Packit 1ef1a9
        /* Use dup2 to reject invalid file descriptors.  No way to
Packit 1ef1a9
           access this information, so punt.  */
Packit 1ef1a9
        if (0 <= dup2 (fd, fd))
Packit 1ef1a9
          result = 0;
Packit 1ef1a9
# endif /* !W32 */
Packit 1ef1a9
        break;
Packit 1ef1a9
      } /* F_GETFD */
Packit 1ef1a9
#endif /* !HAVE_FCNTL */
Packit 1ef1a9
Packit 1ef1a9
      /* Implementing F_SETFD on mingw is not trivial - there is no
Packit 1ef1a9
         API for changing the O_NOINHERIT bit on an fd, and merely
Packit 1ef1a9
         changing the HANDLE_FLAG_INHERIT bit on the underlying handle
Packit 1ef1a9
         can lead to odd state.  It may be possible by duplicating the
Packit 1ef1a9
         handle, using _open_osfhandle with the right flags, then
Packit 1ef1a9
         using dup2 to move the duplicate onto the original, but that
Packit 1ef1a9
         is not supported for now.  */
Packit 1ef1a9
Packit 1ef1a9
    default:
Packit 1ef1a9
      {
Packit 1ef1a9
#if HAVE_FCNTL
Packit 1ef1a9
        void *p = va_arg (arg, void *);
Packit 1ef1a9
        result = fcntl (fd, action, p);
Packit 1ef1a9
#else
Packit 1ef1a9
        errno = EINVAL;
Packit 1ef1a9
#endif
Packit 1ef1a9
        break;
Packit 1ef1a9
      }
Packit 1ef1a9
    }
Packit 1ef1a9
  va_end (arg);
Packit 1ef1a9
  return result;
Packit 1ef1a9
}