1 // Class filesystem::directory_entry etc. -*- C++ -*- 2 3 // Copyright (C) 2014-2015 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 <experimental/filesystem> 30 #include <utility> 31 #include <stack> 32 #include <string.h> 33 #include <errno.h> 34 #ifdef _GLIBCXX_HAVE_DIRENT_H 35 # ifdef _GLIBCXX_HAVE_SYS_TYPES_H 36 # include <sys/types.h> 37 # endif 38 # include <dirent.h> 39 #else 40 # error "the <dirent.h> header is needed to build the Filesystem TS" 41 #endif 42 43 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 44 # undef opendir 45 # define opendir _wopendir 46 #endif 47 48 namespace fs = std::experimental::filesystem; 49 50 struct fs::_Dir 51 { 52 _Dir() : dirp(nullptr) { } 53 54 _Dir(DIR* dirp, const fs::path& path) : dirp(dirp), path(path) { } 55 56 _Dir(_Dir&& d) 57 : dirp(std::exchange(d.dirp, nullptr)), path(std::move(d.path)), 58 entry(std::move(d.entry)), type(d.type) 59 { } 60 61 _Dir& operator=(_Dir&&) = delete; 62 63 ~_Dir() { if (dirp) ::closedir(dirp); } 64 65 bool advance(std::error_code*, directory_options = directory_options::none); 66 67 DIR* dirp; 68 fs::path path; 69 directory_entry entry; 70 file_type type = file_type::none; 71 }; 72 73 namespace 74 { 75 template<typename Bitmask> 76 inline bool 77 is_set(Bitmask obj, Bitmask bits) 78 { 79 return (obj & bits) != Bitmask::none; 80 } 81 82 // Returns {dirp, p} on success, {nullptr, p} on error. 83 // If an ignored EACCES error occurs returns {}. 84 inline fs::_Dir 85 open_dir(const fs::path& p, fs::directory_options options, 86 std::error_code* ec) 87 { 88 if (ec) 89 ec->clear(); 90 91 if (DIR* dirp = ::opendir(p.c_str())) 92 return {dirp, p}; 93 94 const int err = errno; 95 if (err == EACCES 96 && is_set(options, fs::directory_options::skip_permission_denied)) 97 return {}; 98 99 if (!ec) 100 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( 101 "directory iterator cannot open directory", p, 102 std::error_code(err, std::generic_category()))); 103 104 ec->assign(err, std::generic_category()); 105 return {nullptr, p}; 106 } 107 108 inline fs::file_type 109 get_file_type(const ::dirent& d __attribute__((__unused__))) 110 { 111 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE 112 switch (d.d_type) 113 { 114 case DT_BLK: 115 return fs::file_type::block; 116 case DT_CHR: 117 return fs::file_type::character; 118 case DT_DIR: 119 return fs::file_type::directory; 120 case DT_FIFO: 121 return fs::file_type::fifo; 122 case DT_LNK: 123 return fs::file_type::symlink; 124 case DT_REG: 125 return fs::file_type::regular; 126 case DT_SOCK: 127 return fs::file_type::socket; 128 case DT_UNKNOWN: 129 return fs::file_type::unknown; 130 default: 131 return fs::file_type::none; 132 } 133 #else 134 return fs::file_type::none; 135 #endif 136 } 137 } 138 139 140 // Returns false when the end of the directory entries is reached. 141 // Reports errors by setting ec or throwing. 142 bool 143 fs::_Dir::advance(error_code* ec, directory_options options) 144 { 145 if (ec) 146 ec->clear(); 147 148 int err = std::exchange(errno, 0); 149 const auto entp = readdir(dirp); 150 std::swap(errno, err); 151 152 if (entp) 153 { 154 // skip past dot and dot-dot 155 if (!strcmp(entp->d_name, ".") || !strcmp(entp->d_name, "..")) 156 return advance(ec, options); 157 entry = fs::directory_entry{path / entp->d_name}; 158 type = get_file_type(*entp); 159 return true; 160 } 161 else if (err) 162 { 163 if (err == EACCES 164 && is_set(options, directory_options::skip_permission_denied)) 165 return false; 166 167 if (!ec) 168 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 169 "directory iterator cannot advance", 170 std::error_code(err, std::generic_category()))); 171 ec->assign(err, std::generic_category()); 172 return true; 173 } 174 else 175 { 176 // reached the end 177 entry = {}; 178 type = fs::file_type::none; 179 return false; 180 } 181 } 182 183 fs::directory_iterator:: 184 directory_iterator(const path& p, directory_options options, error_code* ec) 185 { 186 _Dir dir = open_dir(p, options, ec); 187 188 if (dir.dirp) 189 { 190 auto sp = std::make_shared<fs::_Dir>(std::move(dir)); 191 if (sp->advance(ec, options)) 192 _M_dir.swap(sp); 193 } 194 else if (!dir.path.empty()) 195 { 196 // An error occurred, we need a non-empty shared_ptr so that *this will 197 // not compare equal to the end iterator. 198 _M_dir.reset(static_cast<fs::_Dir*>(nullptr)); 199 } 200 } 201 202 const fs::directory_entry& 203 fs::directory_iterator::operator*() const 204 { 205 if (!_M_dir) 206 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 207 "non-dereferenceable directory iterator", 208 std::make_error_code(errc::invalid_argument))); 209 return _M_dir->entry; 210 } 211 212 fs::directory_iterator& 213 fs::directory_iterator::operator++() 214 { 215 if (!_M_dir) 216 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 217 "cannot advance non-dereferenceable directory iterator", 218 std::make_error_code(errc::invalid_argument))); 219 if (!_M_dir->advance(nullptr)) 220 _M_dir.reset(); 221 return *this; 222 } 223 224 fs::directory_iterator& 225 fs::directory_iterator::increment(error_code& ec) noexcept 226 { 227 if (!_M_dir) 228 { 229 ec = std::make_error_code(errc::invalid_argument); 230 return *this; 231 } 232 if (!_M_dir->advance(&ec)) 233 _M_dir.reset(); 234 return *this; 235 } 236 237 using Dir_iter_pair = std::pair<fs::_Dir, fs::directory_iterator>; 238 239 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> 240 { 241 void clear() { c.clear(); } 242 }; 243 244 fs::recursive_directory_iterator:: 245 recursive_directory_iterator(const path& p, directory_options options, 246 error_code* ec) 247 : _M_options(options), _M_pending(true) 248 { 249 if (DIR* dirp = ::opendir(p.c_str())) 250 { 251 auto sp = std::make_shared<_Dir_stack>(); 252 sp->push(_Dir{ dirp, p }); 253 if (sp->top().advance(ec)) 254 _M_dirs.swap(sp); 255 } 256 else 257 { 258 const int err = errno; 259 if (err == EACCES 260 && is_set(options, fs::directory_options::skip_permission_denied)) 261 { 262 if (ec) 263 ec->clear(); 264 return; 265 } 266 267 if (!ec) 268 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 269 "recursive directory iterator cannot open directory", p, 270 std::error_code(err, std::generic_category()))); 271 272 ec->assign(err, std::generic_category()); 273 274 // An error occurred, we need a non-empty shared_ptr so that *this will 275 // not compare equal to the end iterator. 276 _M_dirs.reset(static_cast<_Dir_stack*>(nullptr)); 277 } 278 } 279 280 fs::recursive_directory_iterator::~recursive_directory_iterator() = default; 281 282 int 283 fs::recursive_directory_iterator::depth() const 284 { 285 return int(_M_dirs->size()) - 1; 286 } 287 288 const fs::directory_entry& 289 fs::recursive_directory_iterator::operator*() const 290 { 291 return _M_dirs->top().entry; 292 } 293 294 fs::recursive_directory_iterator& 295 fs::recursive_directory_iterator:: 296 operator=(const recursive_directory_iterator& other) noexcept = default; 297 298 fs::recursive_directory_iterator& 299 fs::recursive_directory_iterator:: 300 operator=(recursive_directory_iterator&& other) noexcept = default; 301 302 fs::recursive_directory_iterator& 303 fs::recursive_directory_iterator::operator++() 304 { 305 error_code ec; 306 increment(ec); 307 if (ec.value()) 308 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 309 "cannot increment recursive directory iterator", ec)); 310 return *this; 311 } 312 313 namespace 314 { 315 bool 316 recurse(const fs::_Dir& d, fs::directory_options options, std::error_code& ec) 317 { 318 bool follow_symlink 319 = is_set(options, fs::directory_options::follow_directory_symlink); 320 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE 321 if (d.type == fs::file_type::directory) 322 return true; 323 if (d.type == fs::file_type::symlink && follow_symlink) 324 return d.entry.status().type() == fs::file_type::directory; 325 if (d.type != fs::file_type::none && d.type != fs::file_type::unknown) 326 return false; 327 #endif 328 const fs::path& path = d.entry.path(); 329 auto type = fs::symlink_status(path, ec).type(); 330 if (ec.value()) 331 return false; 332 if (type == fs::file_type::symlink) 333 { 334 if (!follow_symlink) 335 return false; 336 type = fs::status(path, ec).type(); 337 } 338 return type == fs::file_type::directory; 339 } 340 } 341 342 fs::recursive_directory_iterator& 343 fs::recursive_directory_iterator::increment(error_code& ec) noexcept 344 { 345 if (!_M_dirs) 346 { 347 ec = std::make_error_code(errc::invalid_argument); 348 return *this; 349 } 350 351 auto& top = _M_dirs->top(); 352 353 if (std::exchange(_M_pending, true) && recurse(top, _M_options, ec)) 354 { 355 _Dir dir = open_dir(top.entry.path(), _M_options, &ec); 356 if (ec) 357 return *this; 358 if (dir.dirp) 359 _M_dirs->push(std::move(dir)); 360 } 361 362 while (!_M_dirs->top().advance(&ec, _M_options) && !ec) 363 { 364 _M_dirs->pop(); 365 if (_M_dirs->empty()) 366 { 367 _M_dirs.reset(); 368 return *this; 369 } 370 } 371 return *this; 372 } 373 374 void 375 fs::recursive_directory_iterator::pop() 376 { 377 if (!_M_dirs) 378 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 379 "cannot pop non-dereferenceable recursive directory iterator", 380 std::make_error_code(errc::invalid_argument))); 381 382 do { 383 _M_dirs->pop(); 384 if (_M_dirs->empty()) 385 { 386 _M_dirs.reset(); 387 return; 388 } 389 } while (!_M_dirs->top().advance(nullptr, _M_options)); 390 } 391