Blame lib/fdopendir.c

Packit 709fb3
/* provide a replacement fdopendir function
Packit 709fb3
   Copyright (C) 2004-2017 Free Software Foundation, Inc.
Packit 709fb3
Packit 709fb3
   This program is free software: you can redistribute it and/or modify
Packit 709fb3
   it under the terms of the GNU General Public License as published by
Packit 709fb3
   the Free Software Foundation; either version 3 of the License, or
Packit 709fb3
   (at your option) any later version.
Packit 709fb3
Packit 709fb3
   This program is distributed in the hope that it will be useful,
Packit 709fb3
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 709fb3
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 709fb3
   GNU General Public License for more details.
Packit 709fb3
Packit 709fb3
   You should have received a copy of the GNU General Public License
Packit 709fb3
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 709fb3
Packit 709fb3
/* written by Jim Meyering */
Packit 709fb3
Packit 709fb3
#include <config.h>
Packit 709fb3
Packit 709fb3
#include <dirent.h>
Packit 709fb3
Packit 709fb3
#include <stdlib.h>
Packit 709fb3
#include <unistd.h>
Packit 709fb3
Packit 709fb3
#if !HAVE_FDOPENDIR
Packit 709fb3
Packit 709fb3
# include "openat.h"
Packit 709fb3
# include "openat-priv.h"
Packit 709fb3
# include "save-cwd.h"
Packit 709fb3
Packit 709fb3
# if GNULIB_DIRENT_SAFER
Packit 709fb3
#  include "dirent--.h"
Packit 709fb3
# endif
Packit 709fb3
Packit 709fb3
# ifndef REPLACE_FCHDIR
Packit 709fb3
#  define REPLACE_FCHDIR 0
Packit 709fb3
# endif
Packit 709fb3
Packit 709fb3
static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
Packit 709fb3
static DIR *fd_clone_opendir (int, struct saved_cwd const *);
Packit 709fb3
Packit 709fb3
/* Replacement for POSIX fdopendir.
Packit 709fb3
Packit 709fb3
   First, try to simulate it via opendir ("/proc/self/fd/...").  Failing
Packit 709fb3
   that, simulate it by using fchdir metadata, or by doing
Packit 709fb3
   save_cwd/fchdir/opendir(".")/restore_cwd.
Packit 709fb3
   If either the save_cwd or the restore_cwd fails (relatively unlikely),
Packit 709fb3
   then give a diagnostic and exit nonzero.
Packit 709fb3
Packit 709fb3
   If successful, the resulting stream is based on FD in
Packit 709fb3
   implementations where streams are based on file descriptors and in
Packit 709fb3
   applications where no other thread or signal handler allocates or
Packit 709fb3
   frees file descriptors.  In other cases, consult dirfd on the result
Packit 709fb3
   to find out whether FD is still being used.
Packit 709fb3
Packit 709fb3
   Otherwise, this function works just like POSIX fdopendir.
Packit 709fb3
Packit 709fb3
   W A R N I N G:
Packit 709fb3
Packit 709fb3
   Unlike other fd-related functions, this one places constraints on FD.
Packit 709fb3
   If this function returns successfully, FD is under control of the
Packit 709fb3
   dirent.h system, and the caller should not close or modify the state of
Packit 709fb3
   FD other than by the dirent.h functions.  */
Packit 709fb3
# ifdef __KLIBC__
Packit 709fb3
#  include <InnoTekLIBC/backend.h>
Packit 709fb3
Packit 709fb3
DIR *
Packit 709fb3
fdopendir (int fd)
Packit 709fb3
{
Packit 709fb3
  char path[_MAX_PATH];
Packit 709fb3
  DIR *dirp;
Packit 709fb3
Packit 709fb3
  /* Get a path from fd */
Packit 709fb3
  if (__libc_Back_ioFHToPath (fd, path, sizeof (path)))
Packit 709fb3
    return NULL;
Packit 709fb3
Packit 709fb3
  dirp = opendir (path);
Packit 709fb3
  if (!dirp)
Packit 709fb3
    return NULL;
Packit 709fb3
Packit 709fb3
  /* Unregister fd registered by opendir() */
Packit 709fb3
  _gl_unregister_dirp_fd (dirfd (dirp));
Packit 709fb3
Packit 709fb3
  /* Register our fd */
Packit 709fb3
  if (_gl_register_dirp_fd (fd, dirp))
Packit 709fb3
    {
Packit 709fb3
      int saved_errno = errno;
Packit 709fb3
Packit 709fb3
      closedir (dirp);
Packit 709fb3
Packit 709fb3
      errno = saved_errno;
Packit 709fb3
Packit 709fb3
      dirp = NULL;
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  return dirp;
Packit 709fb3
}
Packit 709fb3
# else
Packit 709fb3
DIR *
Packit 709fb3
fdopendir (int fd)
Packit 709fb3
{
Packit 709fb3
  DIR *dir = fdopendir_with_dup (fd, -1, NULL);
Packit 709fb3
Packit 709fb3
  if (! REPLACE_FCHDIR && ! dir)
Packit 709fb3
    {
Packit 709fb3
      int saved_errno = errno;
Packit 709fb3
      if (EXPECTED_ERRNO (saved_errno))
Packit 709fb3
        {
Packit 709fb3
          struct saved_cwd cwd;
Packit 709fb3
          if (save_cwd (&cwd) != 0)
Packit 709fb3
            openat_save_fail (errno);
Packit 709fb3
          dir = fdopendir_with_dup (fd, -1, &cwd;;
Packit 709fb3
          saved_errno = errno;
Packit 709fb3
          free_cwd (&cwd;;
Packit 709fb3
          errno = saved_errno;
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  return dir;
Packit 709fb3
}
Packit 709fb3
# endif
Packit 709fb3
Packit 709fb3
/* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
Packit 709fb3
   to be a dup of FD which is less than FD - 1 and which will be
Packit 709fb3
   closed by the caller and not otherwise used by the caller.  This
Packit 709fb3
   function makes sure that FD is closed and all file descriptors less
Packit 709fb3
   than FD are open, and then calls fd_clone_opendir on a dup of FD.
Packit 709fb3
   That way, barring race conditions, fd_clone_opendir returns a
Packit 709fb3
   stream whose file descriptor is FD.
Packit 709fb3
Packit 709fb3
   If REPLACE_FCHDIR or CWD is null, use opendir ("/proc/self/fd/...",
Packit 709fb3
   falling back on fchdir metadata.  Otherwise, CWD is a saved version
Packit 709fb3
   of the working directory; use fchdir/opendir(".")/restore_cwd(CWD).  */
Packit 709fb3
static DIR *
Packit 709fb3
fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
Packit 709fb3
{
Packit 709fb3
  int dupfd = dup (fd);
Packit 709fb3
  if (dupfd < 0 && errno == EMFILE)
Packit 709fb3
    dupfd = older_dupfd;
Packit 709fb3
  if (dupfd < 0)
Packit 709fb3
    return NULL;
Packit 709fb3
  else
Packit 709fb3
    {
Packit 709fb3
      DIR *dir;
Packit 709fb3
      int saved_errno;
Packit 709fb3
      if (dupfd < fd - 1 && dupfd != older_dupfd)
Packit 709fb3
        {
Packit 709fb3
          dir = fdopendir_with_dup (fd, dupfd, cwd);
Packit 709fb3
          saved_errno = errno;
Packit 709fb3
        }
Packit 709fb3
      else
Packit 709fb3
        {
Packit 709fb3
          close (fd);
Packit 709fb3
          dir = fd_clone_opendir (dupfd, cwd);
Packit 709fb3
          saved_errno = errno;
Packit 709fb3
          if (! dir)
Packit 709fb3
            {
Packit 709fb3
              int fd1 = dup (dupfd);
Packit 709fb3
              if (fd1 != fd)
Packit 709fb3
                openat_save_fail (fd1 < 0 ? errno : EBADF);
Packit 709fb3
            }
Packit 709fb3
        }
Packit 709fb3
Packit 709fb3
      if (dupfd != older_dupfd)
Packit 709fb3
        close (dupfd);
Packit 709fb3
      errno = saved_errno;
Packit 709fb3
      return dir;
Packit 709fb3
    }
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Like fdopendir, except the result controls a clone of FD.  It is
Packit 709fb3
   the caller's responsibility both to close FD and (if the result is
Packit 709fb3
   not null) to closedir the result.  */
Packit 709fb3
static DIR *
Packit 709fb3
fd_clone_opendir (int fd, struct saved_cwd const *cwd)
Packit 709fb3
{
Packit 709fb3
  if (REPLACE_FCHDIR || ! cwd)
Packit 709fb3
    {
Packit 709fb3
      DIR *dir = NULL;
Packit 709fb3
      int saved_errno = EOPNOTSUPP;
Packit 709fb3
      char buf[OPENAT_BUFFER_SIZE];
Packit 709fb3
      char *proc_file = openat_proc_name (buf, fd, ".");
Packit 709fb3
      if (proc_file)
Packit 709fb3
        {
Packit 709fb3
          dir = opendir (proc_file);
Packit 709fb3
          saved_errno = errno;
Packit 709fb3
          if (proc_file != buf)
Packit 709fb3
            free (proc_file);
Packit 709fb3
        }
Packit 709fb3
# if REPLACE_FCHDIR
Packit 709fb3
      if (! dir && EXPECTED_ERRNO (saved_errno))
Packit 709fb3
        {
Packit 709fb3
          char const *name = _gl_directory_name (fd);
Packit 709fb3
          DIR *dp = name ? opendir (name) : NULL;
Packit 709fb3
Packit 709fb3
          /* The caller has done an elaborate dance to arrange for opendir to
Packit 709fb3
             consume just the right file descriptor.  If dirfd returns -1,
Packit 709fb3
             though, we're on a system like mingw where opendir does not
Packit 709fb3
             consume a file descriptor.  Consume it via 'dup' instead.  */
Packit 709fb3
          if (dp && dirfd (dp) < 0)
Packit 709fb3
            dup (fd);
Packit 709fb3
Packit 709fb3
          return dp;
Packit 709fb3
        }
Packit 709fb3
# endif
Packit 709fb3
      errno = saved_errno;
Packit 709fb3
      return dir;
Packit 709fb3
    }
Packit 709fb3
  else
Packit 709fb3
    {
Packit 709fb3
      if (fchdir (fd) != 0)
Packit 709fb3
        return NULL;
Packit 709fb3
      else
Packit 709fb3
        {
Packit 709fb3
          DIR *dir = opendir (".");
Packit 709fb3
          int saved_errno = errno;
Packit 709fb3
          if (restore_cwd (cwd) != 0)
Packit 709fb3
            openat_restore_fail (errno);
Packit 709fb3
          errno = saved_errno;
Packit 709fb3
          return dir;
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
#else /* HAVE_FDOPENDIR */
Packit 709fb3
Packit 709fb3
# include <errno.h>
Packit 709fb3
# include <sys/stat.h>
Packit 709fb3
Packit 709fb3
# undef fdopendir
Packit 709fb3
Packit 709fb3
/* Like fdopendir, but work around GNU/Hurd bug by validating FD.  */
Packit 709fb3
Packit 709fb3
DIR *
Packit 709fb3
rpl_fdopendir (int fd)
Packit 709fb3
{
Packit 709fb3
  struct stat st;
Packit 709fb3
  if (fstat (fd, &st))
Packit 709fb3
    return NULL;
Packit 709fb3
  if (!S_ISDIR (st.st_mode))
Packit 709fb3
    {
Packit 709fb3
      errno = ENOTDIR;
Packit 709fb3
      return NULL;
Packit 709fb3
    }
Packit 709fb3
  return fdopendir (fd);
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
#endif /* HAVE_FDOPENDIR */