# ChangeLog fixes
[emacs.git] / lib / fdopendir.c
blob837a8219b3399f7431c70169e0694ee7cb523f1d
1 /* provide a replacement fdopendir function
2 Copyright (C) 2004-2015 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 <http://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 # include "openat.h"
29 # include "openat-priv.h"
30 # include "save-cwd.h"
32 # if GNULIB_DIRENT_SAFER
33 # include "dirent--.h"
34 # endif
36 # ifndef REPLACE_FCHDIR
37 # define REPLACE_FCHDIR 0
38 # endif
40 static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
41 static DIR *fd_clone_opendir (int, struct saved_cwd const *);
43 /* Replacement for POSIX fdopendir.
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.
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.
57 Otherwise, this function works just like POSIX fdopendir.
59 W A R N I N G:
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 DIR *
66 fdopendir (int fd)
68 DIR *dir = fdopendir_with_dup (fd, -1, NULL);
70 if (! REPLACE_FCHDIR && ! dir)
72 int saved_errno = errno;
73 if (EXPECTED_ERRNO (saved_errno))
75 struct saved_cwd cwd;
76 if (save_cwd (&cwd) != 0)
77 openat_save_fail (errno);
78 dir = fdopendir_with_dup (fd, -1, &cwd);
79 saved_errno = errno;
80 free_cwd (&cwd);
81 errno = saved_errno;
85 return dir;
88 /* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
89 to be a dup of FD which is less than FD - 1 and which will be
90 closed by the caller and not otherwise used by the caller. This
91 function makes sure that FD is closed and all file descriptors less
92 than FD are open, and then calls fd_clone_opendir on a dup of FD.
93 That way, barring race conditions, fd_clone_opendir returns a
94 stream whose file descriptor is FD.
96 If REPLACE_FCHDIR or CWD is null, use opendir ("/proc/self/fd/...",
97 falling back on fchdir metadata. Otherwise, CWD is a saved version
98 of the working directory; use fchdir/opendir(".")/restore_cwd(CWD). */
99 static DIR *
100 fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
102 int dupfd = dup (fd);
103 if (dupfd < 0 && errno == EMFILE)
104 dupfd = older_dupfd;
105 if (dupfd < 0)
106 return NULL;
107 else
109 DIR *dir;
110 int saved_errno;
111 if (dupfd < fd - 1 && dupfd != older_dupfd)
113 dir = fdopendir_with_dup (fd, dupfd, cwd);
114 saved_errno = errno;
116 else
118 close (fd);
119 dir = fd_clone_opendir (dupfd, cwd);
120 saved_errno = errno;
121 if (! dir)
123 int fd1 = dup (dupfd);
124 if (fd1 != fd)
125 openat_save_fail (fd1 < 0 ? errno : EBADF);
129 if (dupfd != older_dupfd)
130 close (dupfd);
131 errno = saved_errno;
132 return dir;
136 /* Like fdopendir, except the result controls a clone of FD. It is
137 the caller's responsibility both to close FD and (if the result is
138 not null) to closedir the result. */
139 static DIR *
140 fd_clone_opendir (int fd, struct saved_cwd const *cwd)
142 if (REPLACE_FCHDIR || ! cwd)
144 DIR *dir = NULL;
145 int saved_errno = EOPNOTSUPP;
146 char buf[OPENAT_BUFFER_SIZE];
147 char *proc_file = openat_proc_name (buf, fd, ".");
148 if (proc_file)
150 dir = opendir (proc_file);
151 saved_errno = errno;
152 if (proc_file != buf)
153 free (proc_file);
155 # if REPLACE_FCHDIR
156 if (! dir && EXPECTED_ERRNO (saved_errno))
158 char const *name = _gl_directory_name (fd);
159 DIR *dp = name ? opendir (name) : NULL;
161 /* The caller has done an elaborate dance to arrange for opendir to
162 consume just the right file descriptor. If dirfd returns -1,
163 though, we're on a system like mingw where opendir does not
164 consume a file descriptor. Consume it via 'dup' instead. */
165 if (dp && dirfd (dp) < 0)
166 dup (fd);
168 return dp;
170 # endif
171 errno = saved_errno;
172 return dir;
174 else
176 if (fchdir (fd) != 0)
177 return NULL;
178 else
180 DIR *dir = opendir (".");
181 int saved_errno = errno;
182 if (restore_cwd (cwd) != 0)
183 openat_restore_fail (errno);
184 errno = saved_errno;
185 return dir;
190 #else /* HAVE_FDOPENDIR */
192 # include <errno.h>
193 # include <sys/stat.h>
195 # undef fdopendir
197 /* Like fdopendir, but work around GNU/Hurd bug by validating FD. */
199 DIR *
200 rpl_fdopendir (int fd)
202 struct stat st;
203 if (fstat (fd, &st))
204 return NULL;
205 if (!S_ISDIR (st.st_mode))
207 errno = ENOTDIR;
208 return NULL;
210 return fdopendir (fd);
213 #endif /* HAVE_FDOPENDIR */