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