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