(root)/
gettext-0.22.4/
gettext-tools/
gnulib-lib/
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  # else
      48  /* We are not in control of the file descriptor of a DIR, and therefore have to
      49     play tricks with file descriptors before and after a call to opendir().  */
      50  
      51  #  include "openat.h"
      52  #  include "openat-priv.h"
      53  #  include "save-cwd.h"
      54  
      55  #  if GNULIB_DIRENT_SAFER
      56  #   include "dirent--.h"
      57  #  endif
      58  
      59  #  ifndef REPLACE_FCHDIR
      60  #   define REPLACE_FCHDIR 0
      61  #  endif
      62  
      63  static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
      64  static DIR *fd_clone_opendir (int, struct saved_cwd const *);
      65  
      66  /* Replacement for POSIX fdopendir.
      67  
      68     First, try to simulate it via opendir ("/proc/self/fd/...").  Failing
      69     that, simulate it by using fchdir metadata, or by doing
      70     save_cwd/fchdir/opendir(".")/restore_cwd.
      71     If either the save_cwd or the restore_cwd fails (relatively unlikely),
      72     then give a diagnostic and exit nonzero.
      73  
      74     If successful, the resulting stream is based on FD in
      75     implementations where streams are based on file descriptors and in
      76     applications where no other thread or signal handler allocates or
      77     frees file descriptors.  In other cases, consult dirfd on the result
      78     to find out whether FD is still being used.
      79  
      80     Otherwise, this function works just like POSIX fdopendir.
      81  
      82     W A R N I N G:
      83  
      84     Unlike other fd-related functions, this one places constraints on FD.
      85     If this function returns successfully, FD is under control of the
      86     dirent.h system, and the caller should not close or modify the state of
      87     FD other than by the dirent.h functions.  */
      88  DIR *
      89  fdopendir (int fd)
      90  {
      91    DIR *dir = fdopendir_with_dup (fd, -1, NULL);
      92  
      93    if (! REPLACE_FCHDIR && ! dir)
      94      {
      95        int saved_errno = errno;
      96        if (EXPECTED_ERRNO (saved_errno))
      97          {
      98            struct saved_cwd cwd;
      99            if (save_cwd (&cwd) != 0)
     100              openat_save_fail (errno);
     101            dir = fdopendir_with_dup (fd, -1, &cwd);
     102            saved_errno = errno;
     103            free_cwd (&cwd);
     104            errno = saved_errno;
     105          }
     106      }
     107  
     108    return dir;
     109  }
     110  
     111  /* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
     112     to be a dup of FD which is less than FD - 1 and which will be
     113     closed by the caller and not otherwise used by the caller.  This
     114     function makes sure that FD is closed and all file descriptors less
     115     than FD are open, and then calls fd_clone_opendir on a dup of FD.
     116     That way, barring race conditions, fd_clone_opendir returns a
     117     stream whose file descriptor is FD.
     118  
     119     If REPLACE_FCHDIR or CWD is null, use opendir ("/proc/self/fd/...",
     120     falling back on fchdir metadata.  Otherwise, CWD is a saved version
     121     of the working directory; use fchdir/opendir(".")/restore_cwd(CWD).  */
     122  static DIR *
     123  fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
     124  {
     125    int dupfd = dup (fd);
     126    if (dupfd < 0 && errno == EMFILE)
     127      dupfd = older_dupfd;
     128    if (dupfd < 0)
     129      return NULL;
     130    else
     131      {
     132        DIR *dir;
     133        int saved_errno;
     134        if (dupfd < fd - 1 && dupfd != older_dupfd)
     135          {
     136            dir = fdopendir_with_dup (fd, dupfd, cwd);
     137            saved_errno = errno;
     138          }
     139        else
     140          {
     141            close (fd);
     142            dir = fd_clone_opendir (dupfd, cwd);
     143            saved_errno = errno;
     144            if (! dir)
     145              {
     146                int fd1 = dup (dupfd);
     147                if (fd1 != fd)
     148                  openat_save_fail (fd1 < 0 ? errno : EBADF);
     149              }
     150          }
     151  
     152        if (dupfd != older_dupfd)
     153          close (dupfd);
     154        errno = saved_errno;
     155        return dir;
     156      }
     157  }
     158  
     159  /* Like fdopendir, except the result controls a clone of FD.  It is
     160     the caller's responsibility both to close FD and (if the result is
     161     not null) to closedir the result.  */
     162  static DIR *
     163  fd_clone_opendir (int fd, struct saved_cwd const *cwd)
     164  {
     165    if (REPLACE_FCHDIR || ! cwd)
     166      {
     167        DIR *dir = NULL;
     168        int saved_errno = EOPNOTSUPP;
     169        char buf[OPENAT_BUFFER_SIZE];
     170        char *proc_file = openat_proc_name (buf, fd, ".");
     171        if (proc_file)
     172          {
     173            dir = opendir (proc_file);
     174            saved_errno = errno;
     175            if (proc_file != buf)
     176              free (proc_file);
     177          }
     178  #  if REPLACE_FCHDIR
     179        if (! dir && EXPECTED_ERRNO (saved_errno))
     180          {
     181            char const *name = _gl_directory_name (fd);
     182            DIR *dp = name ? opendir (name) : NULL;
     183  
     184            /* The caller has done an elaborate dance to arrange for opendir to
     185               consume just the right file descriptor.  If dirfd returns -1,
     186               though, we're on a system like mingw where opendir does not
     187               consume a file descriptor.  Consume it via 'dup' instead.  */
     188            if (dp && dirfd (dp) < 0)
     189              dup (fd);
     190  
     191            return dp;
     192          }
     193  #  endif
     194        errno = saved_errno;
     195        return dir;
     196      }
     197    else
     198      {
     199        if (fchdir (fd) != 0)
     200          return NULL;
     201        else
     202          {
     203            DIR *dir = opendir (".");
     204            int saved_errno = errno;
     205            if (restore_cwd (cwd) != 0)
     206              openat_restore_fail (errno);
     207            errno = saved_errno;
     208            return dir;
     209          }
     210      }
     211  }
     212  
     213  # endif
     214  
     215  #else /* HAVE_FDOPENDIR */
     216  
     217  # include <errno.h>
     218  # include <sys/stat.h>
     219  
     220  # undef fdopendir
     221  
     222  /* Like fdopendir, but work around GNU/Hurd bug by validating FD.  */
     223  
     224  DIR *
     225  rpl_fdopendir (int fd)
     226  {
     227    struct stat st;
     228    if (fstat (fd, &st))
     229      return NULL;
     230    if (!S_ISDIR (st.st_mode))
     231      {
     232        errno = ENOTDIR;
     233        return NULL;
     234      }
     235    return fdopendir (fd);
     236  }
     237  
     238  #endif /* HAVE_FDOPENDIR */