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