exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / fdopendir.c
blobbdbb2ea912fbc3da700fe125e1201b28318a3c60
1 /* provide a replacement fdopendir function
2 Copyright (C) 2004-2024 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* written by Jim Meyering */
19 #include <config.h>
21 #include <dirent.h>
23 #include <stdlib.h>
24 #include <unistd.h>
26 #if !HAVE_FDOPENDIR
28 # if GNULIB_defined_DIR
29 /* We are in control of the file descriptor of a DIR. */
31 # include "dirent-private.h"
33 # if !REPLACE_FCHDIR
34 # error "unexpected configuration: GNULIB_defined_DIR but fchdir not replaced"
35 # endif
37 DIR *
38 fdopendir (int fd)
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;
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(). */
51 # include "openat.h"
52 # include "openat-priv.h"
53 # include "save-cwd.h"
55 # if GNULIB_DIRENT_SAFER
56 # include "dirent--.h"
57 # endif
59 # ifndef REPLACE_FCHDIR
60 # define REPLACE_FCHDIR 0
61 # endif
63 static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
64 static DIR *fd_clone_opendir (int, struct saved_cwd const *);
66 /* Replacement for POSIX fdopendir.
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.
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.
80 Otherwise, this function works just like POSIX fdopendir.
82 W A R N I N G:
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)
91 DIR *dir = fdopendir_with_dup (fd, -1, NULL);
93 if (! REPLACE_FCHDIR && ! dir)
95 int saved_errno = errno;
96 if (EXPECTED_ERRNO (saved_errno))
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;
108 return dir;
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.
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)
125 int dupfd = dup (fd);
126 if (dupfd < 0 && errno == EMFILE)
127 dupfd = older_dupfd;
128 if (dupfd < 0)
129 return NULL;
130 else
132 DIR *dir;
133 int saved_errno;
134 if (dupfd < fd - 1 && dupfd != older_dupfd)
136 dir = fdopendir_with_dup (fd, dupfd, cwd);
137 saved_errno = errno;
139 else
141 close (fd);
142 dir = fd_clone_opendir (dupfd, cwd);
143 saved_errno = errno;
144 if (! dir)
146 int fd1 = dup (dupfd);
147 if (fd1 != fd)
148 openat_save_fail (fd1 < 0 ? errno : EBADF);
152 if (dupfd != older_dupfd)
153 close (dupfd);
154 errno = saved_errno;
155 return dir;
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)
165 if (REPLACE_FCHDIR || ! cwd)
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)
173 dir = opendir (proc_file);
174 saved_errno = errno;
175 if (proc_file != buf)
176 free (proc_file);
178 # if REPLACE_FCHDIR
179 if (! dir && EXPECTED_ERRNO (saved_errno))
181 char const *name = _gl_directory_name (fd);
182 DIR *dp = name ? opendir (name) : NULL;
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);
191 return dp;
193 # endif
194 errno = saved_errno;
195 return dir;
197 else
199 if (fchdir (fd) != 0)
200 return NULL;
201 else
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;
213 # endif
215 #else /* HAVE_FDOPENDIR */
217 # include <errno.h>
218 # include <sys/stat.h>
220 # undef fdopendir
222 /* Like fdopendir, but work around GNU/Hurd bug by validating FD. */
224 DIR *
225 rpl_fdopendir (int fd)
227 struct stat st;
228 if (fstat (fd, &st))
229 return NULL;
230 if (!S_ISDIR (st.st_mode))
232 errno = ENOTDIR;
233 return NULL;
235 return fdopendir (fd);
238 #endif /* HAVE_FDOPENDIR */