// directory.cpp --------------------------------------------------------------------// // Copyright 2002-2009, 2014 Beman Dawes // Copyright 2001 Dietmar Kuehl // Copyright 2019 Andrey Semashev // Distributed under the Boost Software License, Version 1.0. // See http://www.boost.org/LICENSE_1_0.txt // See library home page at http://www.boost.org/libs/filesystem //--------------------------------------------------------------------------------------// #include "platform_config.hpp" #include #include #include #include #include #include #include #include // std::malloc, std::free #include // std::nothrow, std::bad_alloc #include #include #include // std::move #include #include #include #ifdef BOOST_POSIX_API #include #include #include #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS+0 >= 0)\ && defined(_SC_THREAD_SAFE_FUNCTIONS)\ && !defined(__CYGWIN__)\ && !(defined(linux) || defined(__linux) || defined(__linux__))\ && !defined(__ANDROID__)\ && (!defined(__hpux) || defined(_REENTRANT)) \ && (!defined(_AIX) || defined(__THREAD_SAFE))\ && !defined(__wasm) #define BOOST_FILESYSTEM_USE_READDIR_R #endif #else // BOOST_WINDOWS_API #include #include #include "windows_tools.hpp" #endif // BOOST_WINDOWS_API #include "error_handling.hpp" // BOOST_FILESYSTEM_STATUS_CACHE enables file_status cache in // dir_itr_increment. The config tests are placed here because some of the // macros being tested come from dirent.h. // // TODO: find out what macros indicate dirent::d_type present in more libraries #if defined(BOOST_WINDOWS_API)\ || defined(_DIRENT_HAVE_D_TYPE)// defined by GNU C library if d_type present #define BOOST_FILESYSTEM_STATUS_CACHE #endif namespace fs = boost::filesystem; using boost::system::error_code; using boost::system::system_category; namespace boost { namespace filesystem { //--------------------------------------------------------------------------------------// // // // directory_entry // // // //--------------------------------------------------------------------------------------// BOOST_FILESYSTEM_DECL file_status directory_entry::get_status(system::error_code* ec) const { if (!status_known(m_status)) { // optimization: if the symlink status is known, and it isn't a symlink, // then status and symlink_status are identical so just copy the // symlink status to the regular status. if (status_known(m_symlink_status) && !is_symlink(m_symlink_status)) { m_status = m_symlink_status; if (ec != 0) ec->clear(); } else { m_status = detail::status(m_path, ec); } } else if (ec != 0) { ec->clear(); } return m_status; } BOOST_FILESYSTEM_DECL file_status directory_entry::get_symlink_status(system::error_code* ec) const { if (!status_known(m_symlink_status)) m_symlink_status = detail::symlink_status(m_path, ec); else if (ec != 0) ec->clear(); return m_symlink_status; } // dispatch directory_entry supplied here rather than in // , thus avoiding header circularity. // test cases are in operations_unit_test.cpp namespace path_traits { void dispatch(const directory_entry& de, #ifdef BOOST_WINDOWS_API std::wstring& to, #else std::string& to, #endif const codecvt_type&) { to = de.path().native(); } void dispatch(const directory_entry& de, #ifdef BOOST_WINDOWS_API std::wstring& to #else std::string& to #endif ) { to = de.path().native(); } } // namespace path_traits //--------------------------------------------------------------------------------------// // // // directory_iterator // // // //--------------------------------------------------------------------------------------// namespace detail { namespace { #ifdef BOOST_POSIX_API #if defined(BOOST_FILESYSTEM_USE_READDIR_R) // Obtains maximum length of a path, not including the terminating zero inline std::size_t get_path_max() { // this code is based on Stevens and Rago, Advanced Programming in the // UNIX envirnment, 2nd Ed., ISBN 0-201-43307-9, page 49 std::size_t max = 0; errno = 0; long res = ::pathconf("/", _PC_PATH_MAX); if (res < 0) { #if defined(PATH_MAX) max = PATH_MAX; #else max = 4096; #endif } else { max = static_cast< std::size_t >(res); // relative root #if defined(PATH_MAX) if (max < PATH_MAX) max = PATH_MAX; #endif } if ((max + 1) < sizeof(dirent().d_name)) max = sizeof(dirent().d_name) - 1; return max; } // Returns maximum length of a path, not including the terminating zero inline std::size_t path_max() { static const std::size_t max = get_path_max(); return max; } #endif // BOOST_FILESYSTEM_USE_READDIR_R error_code dir_itr_first(void*& handle, void*& buffer, const char* dir, std::string& target, fs::file_status&, fs::file_status&) { if ((handle = ::opendir(dir)) == 0) { const int err = errno; return error_code(err, system_category()); } target.assign("."); // string was static but caused trouble // when iteration called from dtor, after // static had already been destroyed return error_code(); } // *result set to NULL on end of directory inline int readdir_r_simulator(DIR* dirp, void*& buffer, struct dirent** result) { #if defined(BOOST_FILESYSTEM_USE_READDIR_R) errno = 0; if (::sysconf(_SC_THREAD_SAFE_FUNCTIONS) >= 0) { struct dirent* storage = static_cast< struct dirent* >(buffer); if (BOOST_UNLIKELY(!storage)) { // According to readdir description, there's no reliable way to predict the length of the d_name string. // It may exceed NAME_MAX and pathconf(_PC_NAME_MAX) limits. We are being conservative here and allocate // buffer that is enough for PATH_MAX as the directory name. Still, this doesn't guarantee there won't be // a buffer overrun. The readdir_r API is fundamentally flawed and we should avoid it as much as possible // in favor of readdir. const std::size_t name_size = path_max(); const std::size_t buffer_size = (sizeof(dirent) - sizeof(dirent().d_name)) + name_size + 1; // + 1 for "\0" buffer = storage = static_cast< struct dirent* >(std::malloc(buffer_size)); if (BOOST_UNLIKELY(!storage)) return boost::system::errc::not_enough_memory; std::memset(storage, 0, buffer_size); } return ::readdir_r(dirp, storage, result); } #endif errno = 0; struct dirent* p = ::readdir(dirp); *result = p; if (!p) return errno; return 0; } error_code dir_itr_increment(void*& handle, void*& buffer, std::string& target, fs::file_status& sf, fs::file_status& symlink_sf) { dirent* result = NULL; int err = readdir_r_simulator(static_cast(handle), buffer, &result); if (BOOST_UNLIKELY(err != 0)) return error_code(err, system_category()); if (result == NULL) return fs::detail::dir_itr_close(handle, buffer); target = result->d_name; #ifdef BOOST_FILESYSTEM_STATUS_CACHE if (result->d_type == DT_UNKNOWN) // filesystem does not supply d_type value { sf = symlink_sf = fs::file_status(fs::status_error); } else // filesystem supplies d_type value { if (result->d_type == DT_DIR) sf = symlink_sf = fs::file_status(fs::directory_file); else if (result->d_type == DT_REG) sf = symlink_sf = fs::file_status(fs::regular_file); else if (result->d_type == DT_LNK) { sf = fs::file_status(fs::status_error); symlink_sf = fs::file_status(fs::symlink_file); } else sf = symlink_sf = fs::file_status(fs::status_error); } #else sf = symlink_sf = fs::file_status(fs::status_error); #endif return error_code(); } #else // BOOST_WINDOWS_API error_code dir_itr_first(void*& handle, const fs::path& dir, std::wstring& target, fs::file_status& sf, fs::file_status& symlink_sf) // Note: an empty root directory has no "." or ".." entries, so this // causes a ERROR_FILE_NOT_FOUND error which we do not considered an // error. It is treated as eof instead. { // use a form of search Sebastian Martel reports will work with Win98 std::wstring dirpath(dir.wstring()); dirpath += (dirpath.empty() || (dirpath[dirpath.size()-1] != L'\\' && dirpath[dirpath.size()-1] != L'/' && dirpath[dirpath.size()-1] != L':'))? L"\\*" : L"*"; WIN32_FIND_DATAW data; if ((handle = ::FindFirstFileW(dirpath.c_str(), &data)) == INVALID_HANDLE_VALUE) { handle = 0; // signal eof DWORD error = ::GetLastError(); return error_code( (error == ERROR_FILE_NOT_FOUND // Windows Mobile returns ERROR_NO_MORE_FILES; see ticket #3551 || error == ERROR_NO_MORE_FILES) ? 0 : error, system_category() ); } target = data.cFileName; if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) // reparse points are complex, so don't try to handle them here; instead just mark // them as status_error which causes directory_entry caching to call status() // and symlink_status() which do handle reparse points fully { sf.type(fs::status_error); symlink_sf.type(fs::status_error); } else { if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { sf.type(fs::directory_file); symlink_sf.type(fs::directory_file); } else { sf.type(fs::regular_file); symlink_sf.type(fs::regular_file); } sf.permissions(make_permissions(data.cFileName, data.dwFileAttributes)); symlink_sf.permissions(sf.permissions()); } return error_code(); } error_code dir_itr_increment(void*& handle, std::wstring& target, fs::file_status& sf, fs::file_status& symlink_sf) { WIN32_FIND_DATAW data; if (::FindNextFileW(handle, &data)== 0)// fails { DWORD error = ::GetLastError(); fs::detail::dir_itr_close(handle); return error_code(error == ERROR_NO_MORE_FILES ? 0 : error, system_category()); } target = data.cFileName; if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) // reparse points are complex, so don't try to handle them here; instead just mark // them as status_error which causes directory_entry caching to call status() // and symlink_status() which do handle reparse points fully { sf.type(fs::status_error); symlink_sf.type(fs::status_error); } else { if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { sf.type(fs::directory_file); symlink_sf.type(fs::directory_file); } else { sf.type(fs::regular_file); symlink_sf.type(fs::regular_file); } sf.permissions(make_permissions(data.cFileName, data.dwFileAttributes)); symlink_sf.permissions(sf.permissions()); } return error_code(); } #endif BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code = #ifdef BOOST_WINDOWS_API ERROR_PATH_NOT_FOUND #else ENOENT #endif ; } // namespace // dir_itr_close is called both from the ~dir_itr_imp()destructor // and dir_itr_increment() BOOST_FILESYSTEM_DECL system::error_code dir_itr_close( // never throws void*& handle #if defined(BOOST_POSIX_API) , void*& buffer #endif ) BOOST_NOEXCEPT { #ifdef BOOST_POSIX_API if (buffer != NULL) { std::free(buffer); buffer = NULL; } if (handle != NULL) { DIR* h = static_cast(handle); handle = NULL; int err = 0; if (BOOST_UNLIKELY(::closedir(h) != 0)) { err = errno; return error_code(err, system_category()); } } return error_code(); #else if (handle != NULL) { ::FindClose(handle); handle = NULL; } return error_code(); #endif } BOOST_FILESYSTEM_DECL void directory_iterator_construct(directory_iterator& it, const path& p, unsigned int opts, system::error_code* ec) { if (error(p.empty() ? not_found_error_code : 0, p, ec, "boost::filesystem::directory_iterator::construct")) { return; } boost::intrusive_ptr< detail::dir_itr_imp > imp; if (!ec) { imp = new detail::dir_itr_imp(); } else { imp = new (std::nothrow) detail::dir_itr_imp(); if (BOOST_UNLIKELY(!imp)) { *ec = make_error_code(system::errc::not_enough_memory); return; } } try { path::string_type filename; file_status file_stat, symlink_file_stat; error_code result = dir_itr_first(imp->handle, # if defined(BOOST_POSIX_API) imp->buffer, # endif p.c_str(), filename, file_stat, symlink_file_stat); if (result) { if (result != make_error_condition(system::errc::permission_denied) || (opts & static_cast< unsigned int >(directory_options::skip_permission_denied)) == 0u) { error(result.value(), p, ec, "boost::filesystem::directory_iterator::construct"); } return; } if (imp->handle) { // Not eof it.m_imp.swap(imp); it.m_imp->dir_entry.assign(p / filename, file_stat, symlink_file_stat); const path::string_type::value_type* filename_str = filename.c_str(); if (filename_str[0] == path::dot // dot or dot-dot && (filename_str[1] == static_cast< path::string_type::value_type >('\0') || (filename_str[1] == path::dot && filename_str[2] == static_cast< path::string_type::value_type >('\0')))) { detail::directory_iterator_increment(it, ec); } } } catch (std::bad_alloc&) { if (!ec) throw; *ec = make_error_code(boost::system::errc::not_enough_memory); it.m_imp.reset(); } } BOOST_FILESYSTEM_DECL void directory_iterator_increment(directory_iterator& it, system::error_code* ec) { BOOST_ASSERT_MSG(!it.is_end(), "attempt to increment end iterator"); if (ec) ec->clear(); try { path::string_type filename; file_status file_stat, symlink_file_stat; system::error_code increment_ec; for (;;) { increment_ec = dir_itr_increment(it.m_imp->handle, # if defined(BOOST_POSIX_API) it.m_imp->buffer, # endif filename, file_stat, symlink_file_stat); if (BOOST_UNLIKELY(!!increment_ec)) // happens if filesystem is corrupt, such as on a damaged optical disc { boost::intrusive_ptr< detail::dir_itr_imp > imp; imp.swap(it.m_imp); path error_path(imp->dir_entry.path().parent_path()); // fix ticket #5900 if (ec == NULL) { BOOST_FILESYSTEM_THROW( filesystem_error("boost::filesystem::directory_iterator::operator++", error_path, increment_ec)); } *ec = increment_ec; return; } if (it.m_imp->handle == NULL) // eof, make end { it.m_imp.reset(); return; } const path::string_type::value_type* filename_str = filename.c_str(); if (!(filename_str[0] == path::dot // !(dot or dot-dot) && (filename_str[1] == static_cast< path::string_type::value_type >('\0') || (filename_str[1] == path::dot && filename_str[2] == static_cast< path::string_type::value_type >('\0'))))) { it.m_imp->dir_entry.replace_filename(filename, file_stat, symlink_file_stat); return; } } } catch (std::bad_alloc&) { if (!ec) throw; it.m_imp.reset(); *ec = make_error_code(boost::system::errc::not_enough_memory); } } //--------------------------------------------------------------------------------------// // // // recursive_directory_iterator // // // //--------------------------------------------------------------------------------------// BOOST_FILESYSTEM_DECL void recursive_directory_iterator_construct(recursive_directory_iterator& it, const path& dir_path, unsigned int opts, system::error_code* ec) { if (ec) ec->clear(); directory_iterator dir_it; detail::directory_iterator_construct(dir_it, dir_path, opts, ec); if ((ec && *ec) || dir_it == directory_iterator()) return; boost::intrusive_ptr< detail::recur_dir_itr_imp > imp; if (!ec) { imp = new detail::recur_dir_itr_imp(opts); } else { imp = new (std::nothrow) detail::recur_dir_itr_imp(opts); if (BOOST_UNLIKELY(!imp)) { *ec = make_error_code(system::errc::not_enough_memory); return; } } try { #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) imp->m_stack.push_back(std::move(dir_it)); #else imp->m_stack.push_back(dir_it); #endif it.m_imp.swap(imp); } catch (std::bad_alloc&) { if (ec) { *ec = make_error_code(system::errc::not_enough_memory); return; } throw; } } namespace { void recursive_directory_iterator_pop_on_error(detail::recur_dir_itr_imp* imp) { imp->m_stack.pop_back(); while (!imp->m_stack.empty()) { directory_iterator& dir_it = imp->m_stack.back(); system::error_code increment_ec; detail::directory_iterator_increment(dir_it, &increment_ec); if (!increment_ec && dir_it != directory_iterator()) break; imp->m_stack.pop_back(); } } } // namespace BOOST_FILESYSTEM_DECL void recursive_directory_iterator_pop(recursive_directory_iterator& it, system::error_code* ec) { BOOST_ASSERT_MSG(!it.is_end(), "pop() on end recursive_directory_iterator"); detail::recur_dir_itr_imp* const imp = it.m_imp.get(); if (ec) ec->clear(); imp->m_stack.pop_back(); while (true) { if (imp->m_stack.empty()) { it.m_imp.reset(); // done, so make end iterator break; } directory_iterator& dir_it = imp->m_stack.back(); system::error_code increment_ec; detail::directory_iterator_increment(dir_it, &increment_ec); if (BOOST_UNLIKELY(!!increment_ec)) { if ((imp->m_options & static_cast< unsigned int >(directory_options::pop_on_error)) == 0u) { // Make an end iterator on errors it.m_imp.reset(); } else { recursive_directory_iterator_pop_on_error(imp); if (imp->m_stack.empty()) it.m_imp.reset(); // done, so make end iterator } if (ec == NULL) { BOOST_FILESYSTEM_THROW( filesystem_error("boost::filesystem::recursive_directory_iterator::pop", increment_ec)); } *ec = increment_ec; return; } if (dir_it != directory_iterator()) break; imp->m_stack.pop_back(); } } namespace { enum push_directory_result { directory_not_pushed = 0u, directory_pushed = 1u, keep_depth = 1u << 1 }; // Returns: true if push occurs, otherwise false. Always returns false on error. inline push_directory_result recursive_directory_iterator_push_directory(detail::recur_dir_itr_imp* imp, system::error_code& ec) BOOST_NOEXCEPT { push_directory_result result = directory_not_pushed; try { // Discover if the iterator is for a directory that needs to be recursed into, // taking symlinks and options into account. if ((imp->m_options & static_cast< unsigned int >(directory_options::_detail_no_push)) != 0u) { imp->m_options &= ~static_cast< unsigned int >(directory_options::_detail_no_push); return result; } file_status symlink_stat; // if we are not recursing into symlinks, we are going to have to know if the // stack top is a symlink, so get symlink_status and verify no error occurred if ((imp->m_options & static_cast< unsigned int >(directory_options::follow_directory_symlink)) == 0u || (imp->m_options & static_cast< unsigned int >(directory_options::skip_dangling_symlinks)) != 0u) { symlink_stat = imp->m_stack.back()->symlink_status(ec); if (ec) return result; } // Logic for following predicate was contributed by Daniel Aarno to handle cyclic // symlinks correctly and efficiently, fixing ticket #5652. // if (((m_options & directory_options::follow_directory_symlink) == directory_options::follow_directory_symlink // || !is_symlink(m_stack.back()->symlink_status())) // && is_directory(m_stack.back()->status())) ... // The predicate code has since been rewritten to pass error_code arguments, // per ticket #5653. if ((imp->m_options & static_cast< unsigned int >(directory_options::follow_directory_symlink)) != 0u || !fs::is_symlink(symlink_stat)) { file_status stat = imp->m_stack.back()->status(ec); if (BOOST_UNLIKELY(!!ec)) { if (ec == make_error_condition(system::errc::no_such_file_or_directory) && fs::is_symlink(symlink_stat) && (imp->m_options & static_cast< unsigned int >(directory_options::follow_directory_symlink | directory_options::skip_dangling_symlinks)) == static_cast< unsigned int >(directory_options::follow_directory_symlink | directory_options::skip_dangling_symlinks)) { // Skip dangling symlink and continue iteration on the current depth level ec = error_code(); } return result; } if (!fs::is_directory(stat)) return result; if (BOOST_UNLIKELY((imp->m_stack.size() - 1u) >= static_cast< std::size_t >((std::numeric_limits< int >::max)()))) { // We cannot let depth to overflow ec = make_error_code(system::errc::value_too_large); // When depth overflow happens, avoid popping the current directory iterator // and attempt to continue iteration on the current depth. result = keep_depth; return result; } directory_iterator next(imp->m_stack.back()->path(), static_cast< BOOST_SCOPED_ENUM_NATIVE(directory_options) >(imp->m_options), ec); if (!ec && next != directory_iterator()) { #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) imp->m_stack.push_back(std::move(next)); // may throw #else imp->m_stack.push_back(next); // may throw #endif return directory_pushed; } } } catch (std::bad_alloc&) { ec = make_error_code(system::errc::not_enough_memory); } return result; } } // namespace BOOST_FILESYSTEM_DECL void recursive_directory_iterator_increment(recursive_directory_iterator& it, system::error_code* ec) { BOOST_ASSERT_MSG(!it.is_end(), "increment() on end recursive_directory_iterator"); detail::recur_dir_itr_imp* const imp = it.m_imp.get(); if (ec) ec->clear(); system::error_code local_ec; // if various conditions are met, push a directory_iterator into the iterator stack push_directory_result push_result = recursive_directory_iterator_push_directory(imp, local_ec); if (push_result == directory_pushed) return; // report errors if any if (BOOST_UNLIKELY(!!local_ec)) { on_error: if ((imp->m_options & static_cast< unsigned int >(directory_options::pop_on_error)) == 0u) { // Make an end iterator on errors it.m_imp.reset(); } else { if ((push_result & keep_depth) != 0u) { system::error_code increment_ec; directory_iterator& dir_it = imp->m_stack.back(); detail::directory_iterator_increment(dir_it, &increment_ec); if (!increment_ec && dir_it != directory_iterator()) goto on_error_return; } recursive_directory_iterator_pop_on_error(imp); if (imp->m_stack.empty()) it.m_imp.reset(); // done, so make end iterator } on_error_return: if (ec == NULL) { BOOST_FILESYSTEM_THROW(filesystem_error( "filesystem::recursive_directory_iterator increment error", local_ec)); } *ec = local_ec; return; } // Do the actual increment operation on the top iterator in the iterator // stack, popping the stack if necessary, until either the stack is empty or a // non-end iterator is reached. while (true) { if (imp->m_stack.empty()) { it.m_imp.reset(); // done, so make end iterator break; } directory_iterator& dir_it = imp->m_stack.back(); detail::directory_iterator_increment(dir_it, &local_ec); if (BOOST_UNLIKELY(!!local_ec)) goto on_error; if (dir_it != directory_iterator()) break; imp->m_stack.pop_back(); } } } // namespace detail } // namespace filesystem } // namespace boost