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