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 48namespace fs = std::experimental::filesystem; 49 50struct 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 73namespace 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. 142bool 143fs::_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 183fs::directory_iterator:: 184directory_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 202const fs::directory_entry& 203fs::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 212fs::directory_iterator& 213fs::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 224fs::directory_iterator& 225fs::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 237using Dir_iter_pair = std::pair<fs::_Dir, fs::directory_iterator>; 238 239struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> 240{ 241 void clear() { c.clear(); } 242}; 243 244fs::recursive_directory_iterator:: 245recursive_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 280fs::recursive_directory_iterator::~recursive_directory_iterator() = default; 281 282int 283fs::recursive_directory_iterator::depth() const 284{ 285 return int(_M_dirs->size()) - 1; 286} 287 288const fs::directory_entry& 289fs::recursive_directory_iterator::operator*() const 290{ 291 return _M_dirs->top().entry; 292} 293 294fs::recursive_directory_iterator& 295fs::recursive_directory_iterator:: 296operator=(const recursive_directory_iterator& other) noexcept = default; 297 298fs::recursive_directory_iterator& 299fs::recursive_directory_iterator:: 300operator=(recursive_directory_iterator&& other) noexcept = default; 301 302fs::recursive_directory_iterator& 303fs::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 313namespace 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 342fs::recursive_directory_iterator& 343fs::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 374void 375fs::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