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