Blame gnu/fdopendir.c

Packit 1ef1a9
/* provide a replacement fdopendir function
Packit 1ef1a9
   Copyright (C) 2004-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 Jim Meyering */
Packit 1ef1a9
Packit 1ef1a9
#include <config.h>
Packit 1ef1a9
Packit 1ef1a9
#include <dirent.h>
Packit 1ef1a9
Packit 1ef1a9
#include <stdlib.h>
Packit 1ef1a9
#include <unistd.h>
Packit 1ef1a9
Packit 1ef1a9
#if !HAVE_FDOPENDIR
Packit 1ef1a9
Packit 1ef1a9
# include "openat.h"
Packit 1ef1a9
# include "openat-priv.h"
Packit 1ef1a9
# include "save-cwd.h"
Packit 1ef1a9
Packit 1ef1a9
# if GNULIB_DIRENT_SAFER
Packit 1ef1a9
#  include "dirent--.h"
Packit 1ef1a9
# endif
Packit 1ef1a9
Packit 1ef1a9
# ifndef REPLACE_FCHDIR
Packit 1ef1a9
#  define REPLACE_FCHDIR 0
Packit 1ef1a9
# endif
Packit 1ef1a9
Packit 1ef1a9
static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
Packit 1ef1a9
static DIR *fd_clone_opendir (int, struct saved_cwd const *);
Packit 1ef1a9
Packit 1ef1a9
/* Replacement for POSIX fdopendir.
Packit 1ef1a9
Packit 1ef1a9
   First, try to simulate it via opendir ("/proc/self/fd/...").  Failing
Packit 1ef1a9
   that, simulate it by using fchdir metadata, or by doing
Packit 1ef1a9
   save_cwd/fchdir/opendir(".")/restore_cwd.
Packit 1ef1a9
   If either the save_cwd or the restore_cwd fails (relatively unlikely),
Packit 1ef1a9
   then give a diagnostic and exit nonzero.
Packit 1ef1a9
Packit 1ef1a9
   If successful, the resulting stream is based on FD in
Packit 1ef1a9
   implementations where streams are based on file descriptors and in
Packit 1ef1a9
   applications where no other thread or signal handler allocates or
Packit 1ef1a9
   frees file descriptors.  In other cases, consult dirfd on the result
Packit 1ef1a9
   to find out whether FD is still being used.
Packit 1ef1a9
Packit 1ef1a9
   Otherwise, this function works just like POSIX fdopendir.
Packit 1ef1a9
Packit 1ef1a9
   W A R N I N G:
Packit 1ef1a9
Packit 1ef1a9
   Unlike other fd-related functions, this one places constraints on FD.
Packit 1ef1a9
   If this function returns successfully, FD is under control of the
Packit 1ef1a9
   dirent.h system, and the caller should not close or modify the state of
Packit 1ef1a9
   FD other than by the dirent.h functions.  */
Packit 1ef1a9
DIR *
Packit 1ef1a9
fdopendir (int fd)
Packit 1ef1a9
{
Packit 1ef1a9
  DIR *dir = fdopendir_with_dup (fd, -1, NULL);
Packit 1ef1a9
Packit 1ef1a9
  if (! REPLACE_FCHDIR && ! dir)
Packit 1ef1a9
    {
Packit 1ef1a9
      int saved_errno = errno;
Packit 1ef1a9
      if (EXPECTED_ERRNO (saved_errno))
Packit 1ef1a9
        {
Packit 1ef1a9
          struct saved_cwd cwd;
Packit 1ef1a9
          if (save_cwd (&cwd) != 0)
Packit 1ef1a9
            openat_save_fail (errno);
Packit 1ef1a9
          dir = fdopendir_with_dup (fd, -1, &cwd;;
Packit 1ef1a9
          saved_errno = errno;
Packit 1ef1a9
          free_cwd (&cwd;;
Packit 1ef1a9
          errno = saved_errno;
Packit 1ef1a9
        }
Packit 1ef1a9
    }
Packit 1ef1a9
Packit 1ef1a9
  return dir;
Packit 1ef1a9
}
Packit 1ef1a9
Packit 1ef1a9
/* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
Packit 1ef1a9
   to be a dup of FD which is less than FD - 1 and which will be
Packit 1ef1a9
   closed by the caller and not otherwise used by the caller.  This
Packit 1ef1a9
   function makes sure that FD is closed and all file descriptors less
Packit 1ef1a9
   than FD are open, and then calls fd_clone_opendir on a dup of FD.
Packit 1ef1a9
   That way, barring race conditions, fd_clone_opendir returns a
Packit 1ef1a9
   stream whose file descriptor is FD.
Packit 1ef1a9
Packit 1ef1a9
   If REPLACE_FCHDIR or CWD is null, use opendir ("/proc/self/fd/...",
Packit 1ef1a9
   falling back on fchdir metadata.  Otherwise, CWD is a saved version
Packit 1ef1a9
   of the working directory; use fchdir/opendir(".")/restore_cwd(CWD).  */
Packit 1ef1a9
static DIR *
Packit 1ef1a9
fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
Packit 1ef1a9
{
Packit 1ef1a9
  int dupfd = dup (fd);
Packit 1ef1a9
  if (dupfd < 0 && errno == EMFILE)
Packit 1ef1a9
    dupfd = older_dupfd;
Packit 1ef1a9
  if (dupfd < 0)
Packit 1ef1a9
    return NULL;
Packit 1ef1a9
  else
Packit 1ef1a9
    {
Packit 1ef1a9
      DIR *dir;
Packit 1ef1a9
      int saved_errno;
Packit 1ef1a9
      if (dupfd < fd - 1 && dupfd != older_dupfd)
Packit 1ef1a9
        {
Packit 1ef1a9
          dir = fdopendir_with_dup (fd, dupfd, cwd);
Packit 1ef1a9
          saved_errno = errno;
Packit 1ef1a9
        }
Packit 1ef1a9
      else
Packit 1ef1a9
        {
Packit 1ef1a9
          close (fd);
Packit 1ef1a9
          dir = fd_clone_opendir (dupfd, cwd);
Packit 1ef1a9
          saved_errno = errno;
Packit 1ef1a9
          if (! dir)
Packit 1ef1a9
            {
Packit 1ef1a9
              int fd1 = dup (dupfd);
Packit 1ef1a9
              if (fd1 != fd)
Packit 1ef1a9
                openat_save_fail (fd1 < 0 ? errno : EBADF);
Packit 1ef1a9
            }
Packit 1ef1a9
        }
Packit 1ef1a9
Packit 1ef1a9
      if (dupfd != older_dupfd)
Packit 1ef1a9
        close (dupfd);
Packit 1ef1a9
      errno = saved_errno;
Packit 1ef1a9
      return dir;
Packit 1ef1a9
    }
Packit 1ef1a9
}
Packit 1ef1a9
Packit 1ef1a9
/* Like fdopendir, except the result controls a clone of FD.  It is
Packit 1ef1a9
   the caller's responsibility both to close FD and (if the result is
Packit 1ef1a9
   not null) to closedir the result.  */
Packit 1ef1a9
static DIR *
Packit 1ef1a9
fd_clone_opendir (int fd, struct saved_cwd const *cwd)
Packit 1ef1a9
{
Packit 1ef1a9
  if (REPLACE_FCHDIR || ! cwd)
Packit 1ef1a9
    {
Packit 1ef1a9
      DIR *dir = NULL;
Packit 1ef1a9
      int saved_errno = EOPNOTSUPP;
Packit 1ef1a9
      char buf[OPENAT_BUFFER_SIZE];
Packit 1ef1a9
      char *proc_file = openat_proc_name (buf, fd, ".");
Packit 1ef1a9
      if (proc_file)
Packit 1ef1a9
        {
Packit 1ef1a9
          dir = opendir (proc_file);
Packit 1ef1a9
          saved_errno = errno;
Packit 1ef1a9
          if (proc_file != buf)
Packit 1ef1a9
            free (proc_file);
Packit 1ef1a9
        }
Packit 1ef1a9
# if REPLACE_FCHDIR
Packit 1ef1a9
      if (! dir && EXPECTED_ERRNO (saved_errno))
Packit 1ef1a9
        {
Packit 1ef1a9
          char const *name = _gl_directory_name (fd);
Packit 1ef1a9
          DIR *dp = name ? opendir (name) : NULL;
Packit 1ef1a9
Packit 1ef1a9
          /* The caller has done an elaborate dance to arrange for opendir to
Packit 1ef1a9
             consume just the right file descriptor.  If dirfd returns -1,
Packit 1ef1a9
             though, we're on a system like mingw where opendir does not
Packit 1ef1a9
             consume a file descriptor.  Consume it via 'dup' instead.  */
Packit 1ef1a9
          if (dp && dirfd (dp) < 0)
Packit 1ef1a9
            dup (fd);
Packit 1ef1a9
Packit 1ef1a9
          return dp;
Packit 1ef1a9
        }
Packit 1ef1a9
# endif
Packit 1ef1a9
      errno = saved_errno;
Packit 1ef1a9
      return dir;
Packit 1ef1a9
    }
Packit 1ef1a9
  else
Packit 1ef1a9
    {
Packit 1ef1a9
      if (fchdir (fd) != 0)
Packit 1ef1a9
        return NULL;
Packit 1ef1a9
      else
Packit 1ef1a9
        {
Packit 1ef1a9
          DIR *dir = opendir (".");
Packit 1ef1a9
          int saved_errno = errno;
Packit 1ef1a9
          if (restore_cwd (cwd) != 0)
Packit 1ef1a9
            openat_restore_fail (errno);
Packit 1ef1a9
          errno = saved_errno;
Packit 1ef1a9
          return dir;
Packit 1ef1a9
        }
Packit 1ef1a9
    }
Packit 1ef1a9
}
Packit 1ef1a9
Packit 1ef1a9
#else /* HAVE_FDOPENDIR */
Packit 1ef1a9
Packit 1ef1a9
# include <errno.h>
Packit 1ef1a9
# include <sys/stat.h>
Packit 1ef1a9
Packit 1ef1a9
# undef fdopendir
Packit 1ef1a9
Packit 1ef1a9
/* Like fdopendir, but work around GNU/Hurd bug by validating FD.  */
Packit 1ef1a9
Packit 1ef1a9
DIR *
Packit 1ef1a9
rpl_fdopendir (int fd)
Packit 1ef1a9
{
Packit 1ef1a9
  struct stat st;
Packit 1ef1a9
  if (fstat (fd, &st))
Packit 1ef1a9
    return NULL;
Packit 1ef1a9
  if (!S_ISDIR (st.st_mode))
Packit 1ef1a9
    {
Packit 1ef1a9
      errno = ENOTDIR;
Packit 1ef1a9
      return NULL;
Packit 1ef1a9
    }
Packit 1ef1a9
  return fdopendir (fd);
Packit 1ef1a9
}
Packit 1ef1a9
Packit 1ef1a9
#endif /* HAVE_FDOPENDIR */