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 */