(root)/
tar-1.35/
gnu/
fdopendir.c
       1  /* provide a replacement fdopendir function
       2     Copyright (C) 2004-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* written by Jim Meyering */
      18  
      19  #include <config.h>
      20  
      21  #include <dirent.h>
      22  
      23  #include <stdlib.h>
      24  #include <unistd.h>
      25  
      26  #if !HAVE_FDOPENDIR
      27  
      28  # if GNULIB_defined_DIR
      29  /* We are in control of the file descriptor of a DIR.  */
      30  
      31  #  include "dirent-private.h"
      32  
      33  #  if !REPLACE_FCHDIR
      34  #   error "unexpected configuration: GNULIB_defined_DIR but fchdir not replaced"
      35  #  endif
      36  
      37  DIR *
      38  fdopendir (int fd)
      39  {
      40    char const *name = _gl_directory_name (fd);
      41    DIR *dirp = name ? opendir (name) : NULL;
      42    if (dirp != NULL)
      43      dirp->fd_to_close = fd;
      44    return dirp;
      45  }
      46  
      47  # elif defined __KLIBC__
      48  
      49  #  include <InnoTekLIBC/backend.h>
      50  
      51  DIR *
      52  fdopendir (int fd)
      53  {
      54    char path[_MAX_PATH];
      55    DIR *dirp;
      56  
      57    /* Get a path from fd */
      58    if (__libc_Back_ioFHToPath (fd, path, sizeof (path)))
      59      return NULL;
      60  
      61    dirp = opendir (path);
      62    if (!dirp)
      63      return NULL;
      64  
      65    /* Unregister fd registered by opendir() */
      66    _gl_unregister_dirp_fd (dirfd (dirp));
      67  
      68    /* Register our fd */
      69    if (_gl_register_dirp_fd (fd, dirp))
      70      {
      71        int saved_errno = errno;
      72  
      73        closedir (dirp);
      74  
      75        errno = saved_errno;
      76  
      77        dirp = NULL;
      78      }
      79  
      80    return dirp;
      81  }
      82  
      83  # else
      84  /* We are not in control of the file descriptor of a DIR, and therefore have to
      85     play tricks with file descriptors before and after a call to opendir().  */
      86  
      87  #  include "openat.h"
      88  #  include "openat-priv.h"
      89  #  include "save-cwd.h"
      90  
      91  #  if GNULIB_DIRENT_SAFER
      92  #   include "dirent--.h"
      93  #  endif
      94  
      95  #  ifndef REPLACE_FCHDIR
      96  #   define REPLACE_FCHDIR 0
      97  #  endif
      98  
      99  static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
     100  static DIR *fd_clone_opendir (int, struct saved_cwd const *);
     101  
     102  /* Replacement for POSIX fdopendir.
     103  
     104     First, try to simulate it via opendir ("/proc/self/fd/...").  Failing
     105     that, simulate it by using fchdir metadata, or by doing
     106     save_cwd/fchdir/opendir(".")/restore_cwd.
     107     If either the save_cwd or the restore_cwd fails (relatively unlikely),
     108     then give a diagnostic and exit nonzero.
     109  
     110     If successful, the resulting stream is based on FD in
     111     implementations where streams are based on file descriptors and in
     112     applications where no other thread or signal handler allocates or
     113     frees file descriptors.  In other cases, consult dirfd on the result
     114     to find out whether FD is still being used.
     115  
     116     Otherwise, this function works just like POSIX fdopendir.
     117  
     118     W A R N I N G:
     119  
     120     Unlike other fd-related functions, this one places constraints on FD.
     121     If this function returns successfully, FD is under control of the
     122     dirent.h system, and the caller should not close or modify the state of
     123     FD other than by the dirent.h functions.  */
     124  DIR *
     125  fdopendir (int fd)
     126  {
     127    DIR *dir = fdopendir_with_dup (fd, -1, NULL);
     128  
     129    if (! REPLACE_FCHDIR && ! dir)
     130      {
     131        int saved_errno = errno;
     132        if (EXPECTED_ERRNO (saved_errno))
     133          {
     134            struct saved_cwd cwd;
     135            if (save_cwd (&cwd) != 0)
     136              openat_save_fail (errno);
     137            dir = fdopendir_with_dup (fd, -1, &cwd);
     138            saved_errno = errno;
     139            free_cwd (&cwd);
     140            errno = saved_errno;
     141          }
     142      }
     143  
     144    return dir;
     145  }
     146  
     147  /* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
     148     to be a dup of FD which is less than FD - 1 and which will be
     149     closed by the caller and not otherwise used by the caller.  This
     150     function makes sure that FD is closed and all file descriptors less
     151     than FD are open, and then calls fd_clone_opendir on a dup of FD.
     152     That way, barring race conditions, fd_clone_opendir returns a
     153     stream whose file descriptor is FD.
     154  
     155     If REPLACE_FCHDIR or CWD is null, use opendir ("/proc/self/fd/...",
     156     falling back on fchdir metadata.  Otherwise, CWD is a saved version
     157     of the working directory; use fchdir/opendir(".")/restore_cwd(CWD).  */
     158  static DIR *
     159  fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
     160  {
     161    int dupfd = dup (fd);
     162    if (dupfd < 0 && errno == EMFILE)
     163      dupfd = older_dupfd;
     164    if (dupfd < 0)
     165      return NULL;
     166    else
     167      {
     168        DIR *dir;
     169        int saved_errno;
     170        if (dupfd < fd - 1 && dupfd != older_dupfd)
     171          {
     172            dir = fdopendir_with_dup (fd, dupfd, cwd);
     173            saved_errno = errno;
     174          }
     175        else
     176          {
     177            close (fd);
     178            dir = fd_clone_opendir (dupfd, cwd);
     179            saved_errno = errno;
     180            if (! dir)
     181              {
     182                int fd1 = dup (dupfd);
     183                if (fd1 != fd)
     184                  openat_save_fail (fd1 < 0 ? errno : EBADF);
     185              }
     186          }
     187  
     188        if (dupfd != older_dupfd)
     189          close (dupfd);
     190        errno = saved_errno;
     191        return dir;
     192      }
     193  }
     194  
     195  /* Like fdopendir, except the result controls a clone of FD.  It is
     196     the caller's responsibility both to close FD and (if the result is
     197     not null) to closedir the result.  */
     198  static DIR *
     199  fd_clone_opendir (int fd, struct saved_cwd const *cwd)
     200  {
     201    if (REPLACE_FCHDIR || ! cwd)
     202      {
     203        DIR *dir = NULL;
     204        int saved_errno = EOPNOTSUPP;
     205        char buf[OPENAT_BUFFER_SIZE];
     206        char *proc_file = openat_proc_name (buf, fd, ".");
     207        if (proc_file)
     208          {
     209            dir = opendir (proc_file);
     210            saved_errno = errno;
     211            if (proc_file != buf)
     212              free (proc_file);
     213          }
     214  #  if REPLACE_FCHDIR
     215        if (! dir && EXPECTED_ERRNO (saved_errno))
     216          {
     217            char const *name = _gl_directory_name (fd);
     218            DIR *dp = name ? opendir (name) : NULL;
     219  
     220            /* The caller has done an elaborate dance to arrange for opendir to
     221               consume just the right file descriptor.  If dirfd returns -1,
     222               though, we're on a system like mingw where opendir does not
     223               consume a file descriptor.  Consume it via 'dup' instead.  */
     224            if (dp && dirfd (dp) < 0)
     225              dup (fd);
     226  
     227            return dp;
     228          }
     229  #  endif
     230        errno = saved_errno;
     231        return dir;
     232      }
     233    else
     234      {
     235        if (fchdir (fd) != 0)
     236          return NULL;
     237        else
     238          {
     239            DIR *dir = opendir (".");
     240            int saved_errno = errno;
     241            if (restore_cwd (cwd) != 0)
     242              openat_restore_fail (errno);
     243            errno = saved_errno;
     244            return dir;
     245          }
     246      }
     247  }
     248  
     249  # endif
     250  
     251  #else /* HAVE_FDOPENDIR */
     252  
     253  # include <errno.h>
     254  # include <sys/stat.h>
     255  
     256  # undef fdopendir
     257  
     258  /* Like fdopendir, but work around GNU/Hurd bug by validating FD.  */
     259  
     260  DIR *
     261  rpl_fdopendir (int fd)
     262  {
     263    struct stat st;
     264    if (fstat (fd, &st))
     265      return NULL;
     266    if (!S_ISDIR (st.st_mode))
     267      {
     268        errno = ENOTDIR;
     269        return NULL;
     270      }
     271    return fdopendir (fd);
     272  }
     273  
     274  #endif /* HAVE_FDOPENDIR */