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