1 // Class filesystem::directory_entry etc. -*- 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_USE_CXX11_ABI 26 # define _GLIBCXX_USE_CXX11_ABI 1 27 #endif 28 #ifndef _GNU_SOURCE 29 // Cygwin needs this for secure_getenv 30 # define _GNU_SOURCE 1 31 #endif 32 33 #include <bits/largefile-config.h> 34 #include <experimental/filesystem> 35 36 #ifndef _GLIBCXX_HAVE_DIRENT_H 37 # error "the <dirent.h> header is needed to build the Filesystem TS" 38 #endif 39 40 #include <utility> 41 #include <stack> 42 #include <string.h> 43 #include <errno.h> 44 #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM \ 45 namespace experimental { namespace filesystem { 46 #define _GLIBCXX_END_NAMESPACE_FILESYSTEM } } 47 #include "dir-common.h" 48 49 namespace fs = std::experimental::filesystem; 50 namespace posix = std::filesystem::__gnu_posix; 51 52 struct fs::_Dir : std::filesystem::_Dir_base 53 { 54 _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow, 55 error_code& ec) 56 : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec) 57 { 58 if (!ec) 59 path = p; 60 } 61 62 _Dir(_Dir_base&& d, const path& p) : _Dir_base(std::move(d)), path(p) { } 63 64 _Dir(_Dir&&) = default; 65 66 // Returns false when the end of the directory entries is reached. 67 // Reports errors by setting ec. 68 bool advance(bool skip_permission_denied, error_code& ec) noexcept 69 { 70 if (const auto entp = _Dir_base::advance(skip_permission_denied, ec)) 71 { 72 entry = fs::directory_entry{path / entp->d_name}; 73 type = get_file_type(*entp); 74 return true; 75 } 76 else if (!ec) 77 { 78 // reached the end 79 entry = {}; 80 type = file_type::none; 81 } 82 return false; 83 } 84 85 bool advance(error_code& ec) noexcept { return advance(false, ec); } 86 87 // Returns false when the end of the directory entries is reached. 88 // Reports errors by throwing. 89 bool advance(bool skip_permission_denied = false) 90 { 91 error_code ec; 92 const bool ok = advance(skip_permission_denied, ec); 93 if (ec) 94 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 95 "directory iterator cannot advance", ec)); 96 return ok; 97 } 98 99 bool should_recurse(bool follow_symlink, error_code& ec) const 100 { 101 file_type type = this->type; 102 if (type == file_type::none || type == file_type::unknown) 103 { 104 type = entry.symlink_status(ec).type(); 105 if (ec) 106 return false; 107 } 108 109 if (type == file_type::directory) 110 return true; 111 if (type == file_type::symlink) 112 return follow_symlink && is_directory(entry.status(ec)); 113 return false; 114 } 115 116 // Return a pathname for the current directory entry, as an _At_path. 117 _Dir_base::_At_path 118 current() const noexcept 119 { 120 const fs::path& p = entry.path(); 121 #if _GLIBCXX_HAVE_DIRFD 122 auto len = std::prev(p.end())->native().size(); 123 return {::dirfd(this->dirp), p.c_str(), p.native().size() - len}; 124 #else 125 return p.c_str(); 126 #endif 127 } 128 129 // Create a new _Dir for the directory this->entry.path(). 130 _Dir 131 open_subdir(bool skip_permission_denied, bool nofollow, 132 error_code& ec) noexcept 133 { 134 _Dir_base d(current(), skip_permission_denied, nofollow, ec); 135 return _Dir(std::move(d), entry.path()); 136 } 137 138 fs::path path; 139 directory_entry entry; 140 file_type type = file_type::none; 141 }; 142 143 namespace 144 { 145 template<typename Bitmask> 146 inline bool 147 is_set(Bitmask obj, Bitmask bits) 148 { 149 return (obj & bits) != Bitmask::none; 150 } 151 } 152 153 fs::directory_iterator:: 154 directory_iterator(const path& p, directory_options options, error_code* ecptr) 155 { 156 // Do not report an error for permission denied errors. 157 const bool skip_permission_denied 158 = is_set(options, directory_options::skip_permission_denied); 159 160 error_code ec; 161 _Dir dir(p, skip_permission_denied, /*nofollow*/false, ec); 162 163 if (dir.dirp) 164 { 165 auto sp = std::make_shared<fs::_Dir>(std::move(dir)); 166 if (sp->advance(skip_permission_denied, ec)) 167 _M_dir.swap(sp); 168 } 169 if (ecptr) 170 *ecptr = ec; 171 else if (ec) 172 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( 173 "directory iterator cannot open directory", p, ec)); 174 } 175 176 const fs::directory_entry& 177 fs::directory_iterator::operator*() const 178 { 179 if (!_M_dir) 180 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 181 "non-dereferenceable directory iterator", 182 std::make_error_code(errc::invalid_argument))); 183 return _M_dir->entry; 184 } 185 186 fs::directory_iterator& 187 fs::directory_iterator::operator++() 188 { 189 if (!_M_dir) 190 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 191 "cannot advance non-dereferenceable directory iterator", 192 std::make_error_code(errc::invalid_argument))); 193 if (!_M_dir->advance()) 194 _M_dir.reset(); 195 return *this; 196 } 197 198 fs::directory_iterator& 199 fs::directory_iterator::increment(error_code& ec) noexcept 200 { 201 if (!_M_dir) 202 { 203 ec = std::make_error_code(errc::invalid_argument); 204 return *this; 205 } 206 if (!_M_dir->advance(ec)) 207 _M_dir.reset(); 208 return *this; 209 } 210 211 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> 212 { 213 _Dir_stack(_Dir&& dir) 214 { 215 this->push(std::move(dir)); 216 } 217 218 void clear() { c.clear(); } 219 }; 220 221 fs::recursive_directory_iterator:: 222 recursive_directory_iterator(const path& p, directory_options options, 223 error_code* ecptr) 224 : _M_options(options), _M_pending(true) 225 { 226 // Do not report an error for permission denied errors. 227 const bool skip_permission_denied 228 = is_set(options, directory_options::skip_permission_denied); 229 230 error_code ec; 231 _Dir dir(p, skip_permission_denied, /*nofollow*/false, ec); 232 233 if (dir.dirp) 234 { 235 auto sp = std::__make_shared<_Dir_stack>(std::move(dir)); 236 if (ecptr ? sp->top().advance(skip_permission_denied, *ecptr) 237 : sp->top().advance(skip_permission_denied)) 238 { 239 _M_dirs.swap(sp); 240 } 241 } 242 else if (ecptr) 243 *ecptr = ec; 244 else if (ec) 245 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( 246 "recursive directory iterator cannot open directory", p, ec)); 247 } 248 249 fs::recursive_directory_iterator::~recursive_directory_iterator() = default; 250 251 int 252 fs::recursive_directory_iterator::depth() const 253 { 254 return int(_M_dirs->size()) - 1; 255 } 256 257 const fs::directory_entry& 258 fs::recursive_directory_iterator::operator*() const 259 { 260 return _M_dirs->top().entry; 261 } 262 263 fs::recursive_directory_iterator& 264 fs::recursive_directory_iterator:: 265 operator=(const recursive_directory_iterator& other) noexcept = default; 266 267 fs::recursive_directory_iterator& 268 fs::recursive_directory_iterator:: 269 operator=(recursive_directory_iterator&& other) noexcept = default; 270 271 fs::recursive_directory_iterator& 272 fs::recursive_directory_iterator::operator++() 273 { 274 error_code ec; 275 increment(ec); 276 if (ec.value()) 277 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 278 "cannot increment recursive directory iterator", ec)); 279 return *this; 280 } 281 282 fs::recursive_directory_iterator& 283 fs::recursive_directory_iterator::increment(error_code& ec) noexcept 284 { 285 if (!_M_dirs) 286 { 287 ec = std::make_error_code(errc::invalid_argument); 288 return *this; 289 } 290 291 const bool follow 292 = is_set(_M_options, directory_options::follow_directory_symlink); 293 const bool skip_permission_denied 294 = is_set(_M_options, directory_options::skip_permission_denied); 295 296 auto& top = _M_dirs->top(); 297 298 if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec)) 299 { 300 _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec); 301 if (ec) 302 { 303 _M_dirs.reset(); 304 return *this; 305 } 306 if (dir.dirp) 307 _M_dirs->push(std::move(dir)); 308 } 309 310 while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec) 311 { 312 _M_dirs->pop(); 313 if (_M_dirs->empty()) 314 { 315 _M_dirs.reset(); 316 return *this; 317 } 318 } 319 320 if (ec) 321 _M_dirs.reset(); 322 323 return *this; 324 } 325 326 void 327 fs::recursive_directory_iterator::pop(error_code& ec) 328 { 329 if (!_M_dirs) 330 { 331 ec = std::make_error_code(errc::invalid_argument); 332 return; 333 } 334 335 const bool skip_permission_denied 336 = is_set(_M_options, directory_options::skip_permission_denied); 337 338 do { 339 _M_dirs->pop(); 340 if (_M_dirs->empty()) 341 { 342 _M_dirs.reset(); 343 ec.clear(); 344 return; 345 } 346 } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec); 347 348 if (ec) 349 _M_dirs.reset(); 350 } 351 352 void 353 fs::recursive_directory_iterator::pop() 354 { 355 [[maybe_unused]] const bool dereferenceable = _M_dirs != nullptr; 356 error_code ec; 357 pop(ec); 358 if (ec) 359 _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable 360 ? "recursive directory iterator cannot pop" 361 : "non-dereferenceable recursive directory iterator cannot pop", 362 ec)); 363 } 364