1 // Filesystem directory iterator utilities -*- C++ -*- 2 3 // Copyright (C) 2014-2022 Free Software Foundation, Inc. 4 // 5 // This file is part of the GNU ISO C++ Library. This library is free 6 // software; you can redistribute it and/or modify it under the 7 // terms of the GNU General Public License as published by the 8 // Free Software Foundation; either version 3, or (at your option) 9 // any later version. 10 11 // This library is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 16 // Under Section 7 of GPL version 3, you are granted additional 17 // permissions described in the GCC Runtime Library Exception, version 18 // 3.1, as published by the Free Software Foundation. 19 20 // You should have received a copy of the GNU General Public License and 21 // a copy of the GCC Runtime Library Exception along with this program; 22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23 // <http://www.gnu.org/licenses/>. 24 25 #ifndef _GLIBCXX_DIR_COMMON_H 26 #define _GLIBCXX_DIR_COMMON_H 1 27 28 #include <stdint.h> // uint32_t 29 #include <string.h> // strcmp 30 #include <errno.h> 31 #if _GLIBCXX_FILESYSTEM_IS_WINDOWS 32 #include <wchar.h> // wcscmp 33 #endif 34 #ifdef _GLIBCXX_HAVE_DIRENT_H 35 # ifdef _GLIBCXX_HAVE_SYS_TYPES_H 36 # include <sys/types.h> 37 # endif 38 # include <dirent.h> // opendir, readdir, fdopendir, dirfd 39 # ifdef _GLIBCXX_HAVE_FCNTL_H 40 # include <fcntl.h> // open, openat, fcntl, AT_FDCWD, O_NOFOLLOW etc. 41 # include <unistd.h> // close, unlinkat 42 # endif 43 #endif 44 45 namespace std _GLIBCXX_VISIBILITY(default) 46 { 47 _GLIBCXX_BEGIN_NAMESPACE_VERSION 48 namespace filesystem 49 { 50 namespace __gnu_posix 51 { 52 #if _GLIBCXX_FILESYSTEM_IS_WINDOWS 53 // Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*. 54 using char_type = wchar_t; 55 using DIR = ::_WDIR; 56 using dirent = _wdirent; 57 inline DIR* opendir(const wchar_t* path) { return ::_wopendir(path); } 58 inline dirent* readdir(DIR* dir) { return ::_wreaddir(dir); } 59 inline int closedir(DIR* dir) { return ::_wclosedir(dir); } 60 #elif defined _GLIBCXX_HAVE_DIRENT_H 61 using char_type = char; 62 using DIR = ::DIR; 63 typedef struct ::dirent dirent; 64 using ::opendir; 65 using ::readdir; 66 using ::closedir; 67 #else 68 using char_type = char; 69 struct dirent { const char* d_name; }; 70 struct DIR { }; 71 inline DIR* opendir(const char*) { return nullptr; } 72 inline dirent* readdir(DIR*) { return nullptr; } 73 inline int closedir(DIR*) { return -1; } 74 #undef _GLIBCXX_HAVE_DIRFD 75 #undef _GLIBCXX_HAVE_UNLINKAT 76 #endif 77 } // namespace __gnu_posix 78 79 namespace posix = __gnu_posix; 80 81 inline bool 82 is_permission_denied_error(int e) 83 { 84 if (e == EACCES) 85 return true; 86 #ifdef __APPLE__ 87 if (e == EPERM) // See PR 99533 88 return true; 89 #endif 90 return false; 91 } 92 93 struct _Dir_base 94 { 95 // As well as the full pathname (including the directory iterator's path) 96 // this type contains a file descriptor for a directory and a second pathname 97 // relative to that directory. The file descriptor and relative pathname 98 // can be used with POSIX openat and unlinkat. 99 struct _At_path 100 { 101 // No file descriptor given, so interpret the pathname relative to the CWD. 102 _At_path(const posix::char_type* p) noexcept 103 : pathname(p), dir_fd(fdcwd()), offset(0) 104 { } 105 106 _At_path(int fd, const posix::char_type* p, size_t offset) noexcept 107 : pathname(p), dir_fd(fd), offset(offset) 108 { } 109 110 const posix::char_type* 111 path() const noexcept { return pathname; } 112 113 int 114 dir() const noexcept { return dir_fd; } 115 116 const posix::char_type* 117 path_at_dir() const noexcept { return pathname + offset; } 118 119 private: 120 const posix::char_type* pathname; // Full path relative to CWD. 121 int dir_fd; // A directory descriptor (either the parent dir, or AT_FDCWD). 122 uint32_t offset; // Offset into pathname for the part relative to dir_fd. 123 124 // Special value representing the current working directory. 125 // Not a valid file descriptor for an open directory stream. 126 static constexpr int 127 fdcwd() noexcept 128 { 129 #ifdef AT_FDCWD 130 return AT_FDCWD; 131 #else 132 return -1; // Use invalid fd if AT_FDCWD isn't supported. 133 #endif 134 } 135 }; 136 137 // If no error occurs then dirp is non-null, 138 // otherwise null (even if a permission denied error is ignored). 139 _Dir_base(const _At_path& atp, 140 bool skip_permission_denied, bool nofollow, 141 error_code& ec) noexcept 142 : dirp(_Dir_base::openat(atp, nofollow)) 143 { 144 if (dirp) 145 ec.clear(); 146 else if (is_permission_denied_error(errno) && skip_permission_denied) 147 ec.clear(); 148 else 149 ec.assign(errno, std::generic_category()); 150 } 151 152 _Dir_base(_Dir_base&& d) : dirp(std::exchange(d.dirp, nullptr)) { } 153 154 _Dir_base& operator=(_Dir_base&&) = delete; 155 156 ~_Dir_base() { if (dirp) posix::closedir(dirp); } 157 158 const posix::dirent* 159 advance(bool skip_permission_denied, error_code& ec) noexcept 160 { 161 ec.clear(); 162 163 int err = std::exchange(errno, 0); 164 const posix::dirent* entp = posix::readdir(dirp); 165 // std::swap cannot be used with Bionic's errno 166 err = std::exchange(errno, err); 167 168 if (entp) 169 { 170 // skip past dot and dot-dot 171 if (is_dot_or_dotdot(entp->d_name)) 172 return advance(skip_permission_denied, ec); 173 return entp; 174 } 175 else if (err) 176 { 177 if (err == EACCES && skip_permission_denied) 178 return nullptr; 179 ec.assign(err, std::generic_category()); 180 return nullptr; 181 } 182 else 183 { 184 // reached the end 185 return nullptr; 186 } 187 } 188 189 static bool is_dot_or_dotdot(const char* s) noexcept 190 { return !strcmp(s, ".") || !strcmp(s, ".."); } 191 192 #if _GLIBCXX_FILESYSTEM_IS_WINDOWS 193 static bool is_dot_or_dotdot(const wchar_t* s) noexcept 194 { return !wcscmp(s, L".") || !wcscmp(s, L".."); } 195 #endif 196 197 // Set the close-on-exec flag if not already done via O_CLOEXEC. 198 static bool 199 set_close_on_exec([[maybe_unused]] int fd) 200 { 201 #if ! defined O_CLOEXEC && defined FD_CLOEXEC 202 int flags = ::fcntl(fd, F_GETFD); 203 if (flags == -1 || ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) 204 return false; 205 #endif 206 return true; 207 } 208 209 static posix::DIR* 210 openat(const _At_path& atp, bool nofollow) 211 { 212 #if _GLIBCXX_HAVE_FDOPENDIR && defined O_RDONLY && defined O_DIRECTORY \ 213 && ! _GLIBCXX_FILESYSTEM_IS_WINDOWS 214 215 // Any file descriptor we open here should be closed on exec. 216 #ifdef O_CLOEXEC 217 constexpr int close_on_exec = O_CLOEXEC; 218 #else 219 constexpr int close_on_exec = 0; 220 #endif 221 222 int flags = O_RDONLY | O_DIRECTORY | close_on_exec; 223 224 // Directory iterators are vulnerable to race conditions unless O_NOFOLLOW 225 // is supported, because a directory could be replaced with a symlink after 226 // checking is_directory(symlink_status(f)). O_NOFOLLOW avoids the race. 227 #ifdef O_NOFOLLOW 228 if (nofollow) 229 flags |= O_NOFOLLOW; 230 #else 231 nofollow = false; 232 #endif 233 234 int fd; 235 236 #if _GLIBCXX_HAVE_OPENAT 237 fd = ::openat(atp.dir(), atp.path_at_dir(), flags); 238 #else 239 // If we cannot use openat, there's no benefit to using posix::open unless 240 // we will use O_NOFOLLOW, so just use the simpler posix::opendir. 241 if (!nofollow) 242 return posix::opendir(atp.path()); 243 244 fd = ::open(atp.path(), flags); 245 #endif 246 247 if (fd == -1) 248 return nullptr; 249 if (set_close_on_exec(fd)) 250 if (::DIR* dirp = ::fdopendir(fd)) 251 return dirp; 252 int err = errno; 253 ::close(fd); 254 errno = err; 255 return nullptr; 256 #else 257 return posix::opendir(atp.path()); 258 #endif 259 } 260 261 posix::DIR* dirp; 262 }; 263 264 } // namespace filesystem 265 266 // BEGIN/END macros must be defined before including this file. 267 _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM 268 269 inline file_type 270 get_file_type(const std::filesystem::__gnu_posix::dirent& d [[gnu::unused]]) 271 { 272 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE 273 switch (d.d_type) 274 { 275 case DT_BLK: 276 return file_type::block; 277 case DT_CHR: 278 return file_type::character; 279 case DT_DIR: 280 return file_type::directory; 281 case DT_FIFO: 282 return file_type::fifo; 283 case DT_LNK: 284 return file_type::symlink; 285 case DT_REG: 286 return file_type::regular; 287 case DT_SOCK: 288 return file_type::socket; 289 case DT_UNKNOWN: 290 return file_type::unknown; 291 default: 292 return file_type::none; 293 } 294 #else 295 return file_type::none; 296 #endif 297 } 298 299 _GLIBCXX_END_NAMESPACE_FILESYSTEM 300 301 _GLIBCXX_END_NAMESPACE_VERSION 302 } // namespace std 303 304 #endif // _GLIBCXX_DIR_COMMON_H 305