diff --git a/doc/release_history.html b/doc/release_history.html index 1f4ee86..90a746b 100644 --- a/doc/release_history.html +++ b/doc/release_history.html @@ -42,9 +42,12 @@

1.79.0

1.78.0

diff --git a/include/boost/filesystem/directory.hpp b/include/boost/filesystem/directory.hpp index fa1e69b..66e1dd9 100644 --- a/include/boost/filesystem/directory.hpp +++ b/include/boost/filesystem/directory.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include // std::move @@ -258,40 +259,29 @@ class directory_iterator; namespace detail { -BOOST_FILESYSTEM_DECL -system::error_code dir_itr_close( // never throws() - void*& handle -#if defined(BOOST_POSIX_API) - , void*& buffer -#endif - ) BOOST_NOEXCEPT; - struct dir_itr_imp : public boost::intrusive_ref_counter< dir_itr_imp > { +#ifdef BOOST_WINDOWS_API + unsigned char extra_data_format; + std::size_t current_offset; +#endif directory_entry dir_entry; void* handle; -#if defined(BOOST_POSIX_API) - void* buffer; // see dir_itr_increment implementation -#endif - dir_itr_imp() BOOST_NOEXCEPT : - handle(0) -#if defined(BOOST_POSIX_API) - , buffer(0) +#ifdef BOOST_WINDOWS_API + extra_data_format(0u), + current_offset(0u), #endif + handle(NULL) { } + BOOST_FILESYSTEM_DECL ~dir_itr_imp() BOOST_NOEXCEPT; - ~dir_itr_imp() BOOST_NOEXCEPT - { - dir_itr_close(handle -#if defined(BOOST_POSIX_API) - , buffer -#endif - ); - } + BOOST_FILESYSTEM_DECL static void* operator new(std::size_t class_size, std::size_t extra_size) BOOST_NOEXCEPT; + BOOST_FILESYSTEM_DECL static void operator delete(void* p, std::size_t extra_size) BOOST_NOEXCEPT; + BOOST_FILESYSTEM_DECL static void operator delete(void* p) BOOST_NOEXCEPT; }; BOOST_FILESYSTEM_DECL void directory_iterator_construct(directory_iterator& it, path const& p, unsigned int opts, system::error_code* ec); diff --git a/src/directory.cpp b/src/directory.cpp index dd2f941..e7a4c1a 100644 --- a/src/directory.cpp +++ b/src/directory.cpp @@ -13,6 +13,7 @@ #include "platform_config.hpp" +#include #include #include #include @@ -61,6 +62,7 @@ #include #include +#include // NTSTATUS_ #include "windows_tools.hpp" @@ -170,10 +172,58 @@ void dispatch(directory_entry const& de, namespace detail { +BOOST_CONSTEXPR_OR_CONST std::size_t dir_itr_imp_extra_data_alignment = 16u; + +BOOST_FILESYSTEM_DECL void* dir_itr_imp::operator new(std::size_t class_size, std::size_t extra_size) BOOST_NOEXCEPT +{ + if (extra_size > 0) + class_size = (class_size + dir_itr_imp_extra_data_alignment - 1u) & ~(dir_itr_imp_extra_data_alignment - 1u); + std::size_t total_size = class_size + extra_size; + + // Return NULL on OOM + void* p = std::malloc(total_size); + if (BOOST_LIKELY(p != NULL)) + std::memset(p, 0, total_size); + return p; +} + +BOOST_FILESYSTEM_DECL void dir_itr_imp::operator delete(void* p, std::size_t extra_size) BOOST_NOEXCEPT +{ + std::free(p); +} + +BOOST_FILESYSTEM_DECL void dir_itr_imp::operator delete(void* p) BOOST_NOEXCEPT +{ + std::free(p); +} + namespace { +inline void* get_dir_itr_imp_extra_data(dir_itr_imp* imp) BOOST_NOEXCEPT +{ + BOOST_CONSTEXPR_OR_CONST std::size_t extra_data_offset = (sizeof(dir_itr_imp) + dir_itr_imp_extra_data_alignment - 1u) & ~(dir_itr_imp_extra_data_alignment - 1u); + return reinterpret_cast< unsigned char* >(imp) + extra_data_offset; +} + #ifdef BOOST_POSIX_API +inline system::error_code dir_itr_close(dir_itr_imp& imp) BOOST_NOEXCEPT +{ + if (imp.handle != NULL) + { + DIR* h = static_cast< DIR* >(imp.handle); + imp.handle = NULL; + int err = 0; + if (BOOST_UNLIKELY(::closedir(h) != 0)) + { + err = errno; + return error_code(err, system_category()); + } + } + + return error_code(); +} + #if defined(BOOST_FILESYSTEM_USE_READDIR_R) // Obtains maximum length of a path, not including the terminating zero @@ -216,61 +266,15 @@ inline std::size_t path_max() #endif // BOOST_FILESYSTEM_USE_READDIR_R -error_code dir_itr_first(void*& handle, void*& buffer, const char* dir, std::string& target, unsigned int opts, fs::file_status&, fs::file_status&) -{ -#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) - int flags = O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC; - if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u) - flags |= O_NOFOLLOW; - - int fd = ::open(dir, flags); - if (BOOST_UNLIKELY(fd < 0)) - { - const int err = errno; - return error_code(err, system_category()); - } - -#if defined(BOOST_FILESYSTEM_NO_O_CLOEXEC) && defined(FD_CLOEXEC) - int res = ::fcntl(fd, F_SETFD, FD_CLOEXEC); - if (BOOST_UNLIKELY(res < 0)) - { - const int err = errno; - close_fd(fd); - return error_code(err, system_category()); - } -#endif - - handle = ::fdopendir(fd); - if (BOOST_UNLIKELY(!handle)) - { - const int err = errno; - close_fd(fd); - return error_code(err, system_category()); - } -#else // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) - handle = ::opendir(dir); - if (BOOST_UNLIKELY(!handle)) - { - const int err = errno; - return error_code(err, system_category()); - } -#endif // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) - - 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 #if !defined(BOOST_FILESYSTEM_USE_READDIR_R) inline #endif -int readdir_impl(DIR* dirp, void*&, struct dirent** result) +int readdir_impl(dir_itr_imp& imp, struct dirent** result) { errno = 0; - struct dirent* p = ::readdir(dirp); + struct dirent* p = ::readdir(static_cast< DIR* >(imp.handle)); *result = p; if (!p) return errno; @@ -279,37 +283,26 @@ int readdir_impl(DIR* dirp, void*&, struct dirent** result) #if !defined(BOOST_FILESYSTEM_USE_READDIR_R) -inline int invoke_readdir(DIR* dirp, void*& buffer, struct dirent** result) +inline int invoke_readdir(dir_itr_imp& imp, struct dirent** result) { - return readdir_impl(dirp, buffer, result); + return readdir_impl(imp, result); } #else // !defined(BOOST_FILESYSTEM_USE_READDIR_R) -int readdir_r_impl(DIR* dirp, void*& buffer, struct dirent** result) +int readdir_r_impl(dir_itr_imp& imp, struct dirent** result) { - 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); + return ::readdir_r + ( + static_cast< DIR* >(imp.handle), + static_cast< struct dirent* >(get_dir_itr_imp_extra_data(&imp)), + result + ); } -int readdir_select_impl(DIR* dirp, void*& buffer, struct dirent** result); +int readdir_select_impl(dir_itr_imp& imp, struct dirent** result); -typedef int readdir_impl_t(DIR* dirp, void*& buffer, struct dirent** result); +typedef int readdir_impl_t(dir_itr_imp& imp, struct dirent** result); //! Pointer to the actual implementation of the copy_file_data implementation readdir_impl_t* readdir_impl_ptr = &readdir_select_impl; @@ -334,29 +327,29 @@ struct readdir_initializer BOOST_FILESYSTEM_INIT_PRIORITY(BOOST_FILESYSTEM_FUNC_PTR_INIT_PRIORITY) BOOST_ATTRIBUTE_UNUSED BOOST_FILESYSTEM_ATTRIBUTE_RETAIN const readdir_initializer readdir_init; -int readdir_select_impl(DIR* dirp, void*& buffer, struct dirent** result) +int readdir_select_impl(dir_itr_imp& imp, struct dirent** result) { init_readdir_impl(); - return filesystem::detail::atomic_load_relaxed(readdir_impl_ptr)(dirp, buffer, result); + return filesystem::detail::atomic_load_relaxed(readdir_impl_ptr)(imp, result); } -inline int invoke_readdir(DIR* dirp, void*& buffer, struct dirent** result) +inline int invoke_readdir(dir_itr_imp& imp, struct dirent** result) { - return filesystem::detail::atomic_load_relaxed(readdir_impl_ptr)(dirp, buffer, result); + return filesystem::detail::atomic_load_relaxed(readdir_impl_ptr)(imp, result); } #endif // !defined(BOOST_FILESYSTEM_USE_READDIR_R) -error_code dir_itr_increment(void*& handle, void*& buffer, std::string& target, fs::file_status& sf, fs::file_status& symlink_sf) +error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf) { dirent* result = NULL; - int err = invoke_readdir(static_cast< DIR* >(handle), buffer, &result); + int err = invoke_readdir(imp, &result); if (BOOST_UNLIKELY(err != 0)) return error_code(err, system_category()); if (result == NULL) - return fs::detail::dir_itr_close(handle, buffer); + return dir_itr_close(imp); - target = result->d_name; + filename = result->d_name; #ifdef BOOST_FILESYSTEM_STATUS_CACHE if (result->d_type == DT_UNKNOWN) // filesystem does not supply d_type value @@ -383,203 +376,709 @@ error_code dir_itr_increment(void*& handle, void*& buffer, std::string& target, return error_code(); } +error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, unsigned int opts, fs::path& first_filename, fs::file_status&, fs::file_status&) +{ + std::size_t extra_size = 0u; +#if defined(BOOST_FILESYSTEM_USE_READDIR_R) + { + readdir_impl_t* rdimpl = filesystem::detail::atomic_load_relaxed(readdir_impl_ptr); + if (BOOST_UNLIKELY(rdimpl == &readdir_select_impl)) + { + init_readdir_impl(); + rdimpl = filesystem::detail::atomic_load_relaxed(readdir_impl_ptr); + } + + if (rdimpl == &readdir_r_impl) + { + // 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. + extra_size = (sizeof(dirent) - sizeof(dirent().d_name)) + path_max() + 1u; // + 1 for "\0" + } + } +#endif // defined(BOOST_FILESYSTEM_USE_READDIR_R) + + boost::intrusive_ptr< detail::dir_itr_imp > pimpl(new (extra_size) detail::dir_itr_imp()); + if (BOOST_UNLIKELY(!pimpl)) + return make_error_code(boost::system::errc::not_enough_memory); + +#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) + int flags = O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC; + if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u) + flags |= O_NOFOLLOW; + + int fd = ::open(dir.c_str(), flags); + if (BOOST_UNLIKELY(fd < 0)) + { + const int err = errno; + return error_code(err, system_category()); + } + +#if defined(BOOST_FILESYSTEM_NO_O_CLOEXEC) && defined(FD_CLOEXEC) + int res = ::fcntl(fd, F_SETFD, FD_CLOEXEC); + if (BOOST_UNLIKELY(res < 0)) + { + const int err = errno; + close_fd(fd); + return error_code(err, system_category()); + } +#endif + + pimpl->handle = ::fdopendir(fd); + if (BOOST_UNLIKELY(!pimpl->handle)) + { + const int err = errno; + close_fd(fd); + return error_code(err, system_category()); + } +#else // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) + pimpl->handle = ::opendir(dir.c_str()); + if (BOOST_UNLIKELY(!pimpl->handle)) + { + const int err = errno; + return error_code(err, system_category()); + } +#endif // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) + + // Force initial readdir call by the caller. This will initialize the actual first filename and statuses. + first_filename.assign("."); + + imp.swap(pimpl); + return error_code(); +} + +BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code = ENOENT; + #else // BOOST_WINDOWS_API -error_code dir_itr_first(void*& handle, fs::path const& dir, std::wstring& target, unsigned int opts, 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. +inline void set_file_statuses(DWORD attrs, const ULONG* reparse_point_tag, fs::path const& filename, fs::file_status& sf, fs::file_status& symlink_sf) { + if ((attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0u) + { + // Reparse points are complex, so don't try to resolve 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 + if (reparse_point_tag) + { + // If we have a reparse point tag we can at least populate the symlink status, + // consistent with symlink_status() behavior + symlink_sf.type(is_reparse_point_tag_a_symlink(*reparse_point_tag) ? fs::symlink_file : fs::reparse_file); + symlink_sf.permissions(make_permissions(filename, attrs)); + } + else + { + symlink_sf.type(fs::status_error); + } + + sf.type(fs::status_error); + } + else + { + if ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0u) + { + 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(filename, attrs)); + symlink_sf.permissions(sf.permissions()); + } +} + +#if !defined(UNDER_CE) + +//! FILE_ID_128 definition from Windows SDK +struct file_id_128 +{ + BYTE Identifier[16]; +}; + +//! FILE_DIRECTORY_INFORMATION definition from Windows DDK. Used by NtQueryDirectoryFile, supported since Windows NT 4.0 (probably). +struct file_directory_information +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + WCHAR FileName[1]; +}; + +//! FILE_ID_BOTH_DIR_INFO definition from Windows SDK. Basic support for directory iteration using GetFileInformationByHandleEx, supported since Windows Vista. +struct file_id_both_dir_info +{ + DWORD NextEntryOffset; + DWORD FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + DWORD FileAttributes; + DWORD FileNameLength; + DWORD EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +}; + +//! FILE_FULL_DIR_INFO definition from Windows SDK. More lightweight than FILE_ID_BOTH_DIR_INFO, supported since Windows 8. +struct file_full_dir_info +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + WCHAR FileName[1]; +}; + +//! FILE_ID_EXTD_DIR_INFO definition from Windows SDK. Provides reparse point tag, which saves us querying it with a few separate syscalls. Supported since Windows 8. +struct file_id_extd_dir_info +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + file_id_128 FileId; + WCHAR FileName[1]; +}; + +//! Indicates format of the extra data in the directory iterator +enum extra_data_format +{ + file_directory_information_format, + file_id_both_dir_info_format, + file_full_dir_info_format, + file_id_extd_dir_info_format +}; + +//! Indicates extra data format that should be used by directory iterator by default +extra_data_format g_extra_data_format = file_directory_information_format; + +/*! + * \brief Extra buffer size for GetFileInformationByHandleEx-based or NtQueryDirectoryFile-based directory iterator. + * + * Must be large enough to accommodate at least one FILE_DIRECTORY_INFORMATION or *_DIR_INFO struct and one filename. + * NTFS, VFAT, exFAT support filenames up to 255 UTF-16/UCS-2 characters. ReFS supports filenames up to 32768 UTF-16 characters. + */ +BOOST_CONSTEXPR_OR_CONST std::size_t dir_itr_extra_size = sizeof(file_id_extd_dir_info) + 65536u; + +inline system::error_code dir_itr_close(dir_itr_imp& imp) BOOST_NOEXCEPT +{ + imp.extra_data_format = 0u; + imp.current_offset = 0u; + + if (imp.handle != NULL) + { + ::CloseHandle(imp.handle); + imp.handle = NULL; + } + + return error_code(); +} + +error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf) +{ + void* extra_data = get_dir_itr_imp_extra_data(&imp); + const void* current_data = static_cast< const unsigned char* >(extra_data) + imp.current_offset; + switch (imp.extra_data_format) + { + case file_id_extd_dir_info_format: + { + const file_id_extd_dir_info* data = static_cast< const file_id_extd_dir_info* >(current_data); + if (data->NextEntryOffset == 0u) + { + if (!filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api)(imp.handle, file_id_extd_directory_info_class, extra_data, dir_itr_extra_size)) + { + DWORD error = ::GetLastError(); + + dir_itr_close(imp); + if (error == ERROR_NO_MORE_FILES) + goto done; + + return error_code(error, system_category()); + } + + imp.current_offset = 0u; + data = static_cast< const file_id_extd_dir_info* >(extra_data); + } + else + { + imp.current_offset += data->NextEntryOffset; + data = reinterpret_cast< const file_id_extd_dir_info* >(static_cast< const unsigned char* >(current_data) + data->NextEntryOffset); + } + + filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + set_file_statuses(data->FileAttributes, &data->ReparsePointTag, filename, sf, symlink_sf); + } + break; + + case file_full_dir_info_format: + { + const file_full_dir_info* data = static_cast< const file_full_dir_info* >(current_data); + if (data->NextEntryOffset == 0u) + { + if (!filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api)(imp.handle, file_full_directory_info_class, extra_data, dir_itr_extra_size)) + { + DWORD error = ::GetLastError(); + + dir_itr_close(imp); + if (error == ERROR_NO_MORE_FILES) + goto done; + + return error_code(error, system_category()); + } + + imp.current_offset = 0u; + data = static_cast< const file_full_dir_info* >(extra_data); + } + else + { + imp.current_offset += data->NextEntryOffset; + data = reinterpret_cast< const file_full_dir_info* >(static_cast< const unsigned char* >(current_data) + data->NextEntryOffset); + } + + filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + set_file_statuses(data->FileAttributes, NULL, filename, sf, symlink_sf); + } + break; + + case file_id_both_dir_info_format: + { + const file_id_both_dir_info* data = static_cast< const file_id_both_dir_info* >(current_data); + if (data->NextEntryOffset == 0u) + { + if (!filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api)(imp.handle, file_id_both_directory_info_class, extra_data, dir_itr_extra_size)) + { + DWORD error = ::GetLastError(); + + dir_itr_close(imp); + if (error == ERROR_NO_MORE_FILES) + goto done; + + return error_code(error, system_category()); + } + + imp.current_offset = 0u; + data = static_cast< const file_id_both_dir_info* >(extra_data); + } + else + { + imp.current_offset += data->NextEntryOffset; + data = reinterpret_cast< const file_id_both_dir_info* >(static_cast< const unsigned char* >(current_data) + data->NextEntryOffset); + } + + filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + set_file_statuses(data->FileAttributes, NULL, filename, sf, symlink_sf); + } + break; + + default: + { + const file_directory_information* data = static_cast< const file_directory_information* >(current_data); + if (data->NextEntryOffset == 0u) + { + io_status_block iosb; + boost::winapi::NTSTATUS_ status = filesystem::detail::atomic_load_relaxed(nt_query_directory_file_api) + ( + imp.handle, + NULL, // Event + NULL, // ApcRoutine + NULL, // ApcContext + &iosb, + extra_data, + dir_itr_extra_size, + file_directory_information_class, + FALSE, // ReturnSingleEntry + NULL, // FileName + FALSE // RestartScan + ); + + if (!NT_SUCCESS(status)) + { + dir_itr_close(imp); + if (status == STATUS_NO_MORE_FILES) + goto done; + + return error_code(translate_ntstatus(status), system_category()); + } + + imp.current_offset = 0u; + data = static_cast< const file_directory_information* >(extra_data); + } + else + { + imp.current_offset += data->NextEntryOffset; + data = reinterpret_cast< const file_directory_information* >(static_cast< const unsigned char* >(current_data) + data->NextEntryOffset); + } + + filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + set_file_statuses(data->FileAttributes, NULL, filename, sf, symlink_sf); + } + break; + } + +done: + return error_code(); +} + +error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, unsigned int opts, fs::path& first_filename, fs::file_status& sf, fs::file_status& symlink_sf) +{ + boost::intrusive_ptr< detail::dir_itr_imp > pimpl(new (dir_itr_extra_size) detail::dir_itr_imp()); + if (BOOST_UNLIKELY(!pimpl)) + return make_error_code(boost::system::errc::not_enough_memory); + + DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; + if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u) + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + handle_wrapper h(create_file_handle(dir, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags)); + if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE)) + { + return_last_error: + DWORD error = ::GetLastError(); + return error_code(error, system_category()); + } + + if ((opts & static_cast< unsigned int >(directory_options::_detail_no_follow)) != 0u) + { + GetFileInformationByHandleEx_t* get_file_information_by_handle_ex = filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api); + if (BOOST_LIKELY(get_file_information_by_handle_ex != NULL)) + { + file_attribute_tag_info info; + BOOL res = get_file_information_by_handle_ex(h.handle, file_attribute_tag_info_class, &info, sizeof(info)); + if (BOOST_UNLIKELY(!res)) + goto return_last_error; + + if ((info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u && is_reparse_point_tag_a_symlink(info.ReparseTag)) + return make_error_code(boost::system::errc::too_many_symbolic_link_levels); + } + else + { + BY_HANDLE_FILE_INFORMATION info; + BOOL res = ::GetFileInformationByHandle(h.handle, &info); + if (BOOST_UNLIKELY(!res)) + goto return_last_error; + + if ((info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u) + return make_error_code(boost::system::errc::too_many_symbolic_link_levels); + } + } + + void* extra_data = get_dir_itr_imp_extra_data(pimpl.get()); + switch (filesystem::detail::atomic_load_relaxed(g_extra_data_format)) + { + case file_id_extd_dir_info_format: + { + if (!filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api)(h.handle, file_id_extd_directory_restart_info_class, extra_data, dir_itr_extra_size)) + { + DWORD error = ::GetLastError(); + + if (error == ERROR_NOT_SUPPORTED || error == ERROR_INVALID_PARAMETER) + { + // Fall back to file_full_dir_info_format. + // Note that some mounted filesystems may not support FILE_ID_128 identifiers, which will cause + // GetFileInformationByHandleEx(FileIdExtdDirectoryRestartInfo) return ERROR_INVALID_PARAMETER, + // even though in general the operation is supported by the kernel. So don't downgrade to + // FileFullDirectoryRestartInfo permanently in this case - only for this particular iterator. + if (error == ERROR_NOT_SUPPORTED) + filesystem::detail::atomic_store_relaxed(g_extra_data_format, file_full_dir_info_format); + goto fallback_to_file_full_dir_info_format; + } + + if (error == ERROR_NO_MORE_FILES || error == ERROR_FILE_NOT_FOUND) + goto done; + + return error_code(error, system_category()); + } + + pimpl->extra_data_format = file_id_extd_dir_info_format; + + const file_id_extd_dir_info* data = static_cast< const file_id_extd_dir_info* >(extra_data); + first_filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + + set_file_statuses(data->FileAttributes, &data->ReparsePointTag, first_filename, sf, symlink_sf); + } + break; + + case file_full_dir_info_format: + fallback_to_file_full_dir_info_format: + { + if (!filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api)(h.handle, file_full_directory_restart_info_class, extra_data, dir_itr_extra_size)) + { + DWORD error = ::GetLastError(); + + if (error == ERROR_NOT_SUPPORTED || error == ERROR_INVALID_PARAMETER) + { + // Fall back to file_id_both_dir_info + filesystem::detail::atomic_store_relaxed(g_extra_data_format, file_id_both_dir_info_format); + goto fallback_to_file_id_both_dir_info; + } + + if (error == ERROR_NO_MORE_FILES || error == ERROR_FILE_NOT_FOUND) + goto done; + + return error_code(error, system_category()); + } + + pimpl->extra_data_format = file_full_dir_info_format; + + const file_full_dir_info* data = static_cast< const file_full_dir_info* >(extra_data); + first_filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + + set_file_statuses(data->FileAttributes, NULL, first_filename, sf, symlink_sf); + } + break; + + case file_id_both_dir_info_format: + fallback_to_file_id_both_dir_info: + { + if (!filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api)(h.handle, file_id_both_directory_restart_info_class, extra_data, dir_itr_extra_size)) + { + DWORD error = ::GetLastError(); + + if (error == ERROR_NO_MORE_FILES || error == ERROR_FILE_NOT_FOUND) + goto done; + + return error_code(error, system_category()); + } + + pimpl->extra_data_format = file_id_both_dir_info_format; + + const file_id_both_dir_info* data = static_cast< const file_id_both_dir_info* >(extra_data); + first_filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + + set_file_statuses(data->FileAttributes, NULL, first_filename, sf, symlink_sf); + } + break; + + default: + { + NtQueryDirectoryFile_t* nt_query_directory_file = filesystem::detail::atomic_load_relaxed(boost::filesystem::detail::nt_query_directory_file_api); + if (BOOST_UNLIKELY(!nt_query_directory_file)) + return error_code(ERROR_NOT_SUPPORTED, system_category()); + + io_status_block iosb; + boost::winapi::NTSTATUS_ status = nt_query_directory_file + ( + h.handle, + NULL, // Event + NULL, // ApcRoutine + NULL, // ApcContext + &iosb, + extra_data, + dir_itr_extra_size, + file_directory_information_class, + FALSE, // ReturnSingleEntry + NULL, // FileName + TRUE // RestartScan + ); + + if (!NT_SUCCESS(status)) + { + // Note: an empty root directory has no "." or ".." entries, so this + // causes a ERROR_FILE_NOT_FOUND error returned from FindFirstFileW + // (which is presumably equivalent to STATUS_NO_SUCH_FILE) which we + // do not consider an error. It is treated as eof instead. + if (status == STATUS_NO_MORE_FILES || status == STATUS_NO_SUCH_FILE) + goto done; + + return error_code(translate_ntstatus(status), system_category()); + } + + pimpl->extra_data_format = file_directory_information_format; + + const file_directory_information* data = static_cast< const file_directory_information* >(extra_data); + first_filename.assign(data->FileName, data->FileName + data->FileNameLength / sizeof(WCHAR)); + + set_file_statuses(data->FileAttributes, NULL, first_filename, sf, symlink_sf); + } + break; + } + + pimpl->handle = h.handle; + h.handle = INVALID_HANDLE_VALUE; + +done: + imp.swap(pimpl); + return error_code(); +} + +#else // !defined(UNDER_CE) + +inline system::error_code dir_itr_close(dir_itr_imp& imp) BOOST_NOEXCEPT +{ + if (imp.handle != NULL) + { + ::FindClose(imp.handle); + imp.handle = NULL; + } + + return error_code(); +} + +error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf) +{ + WIN32_FIND_DATAW data; + if (::FindNextFileW(imp.handle, &data) == 0) // fails + { + DWORD error = ::GetLastError(); + dir_itr_close(imp); + if (error == ERROR_NO_MORE_FILES) + goto done; + return error_code(error, system_category()); + } + + filename = data.cFileName; + set_file_statuses(data.dwFileAttributes, NULL, filename, sf, symlink_sf); + +done: + return error_code(); +} + +error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, unsigned int opts, fs::path& first_filename, fs::file_status& sf, fs::file_status& symlink_sf) +{ + boost::intrusive_ptr< detail::dir_itr_imp > pimpl(new (static_cast< std::size_t >(0u)) detail::dir_itr_imp()); + if (BOOST_UNLIKELY(!pimpl)) + return make_error_code(boost::system::errc::not_enough_memory); + // 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"*"; + fs::path dirpath(dir); + dirpath.make_preferred(); + dirpath /= L"*"; WIN32_FIND_DATAW data; - if ((handle = ::FindFirstFileW(dirpath.c_str(), &data)) == INVALID_HANDLE_VALUE) + pimpl->handle = ::FindFirstFileW(dirpath.c_str(), &data); + if (BOOST_UNLIKELY(pimpl->handle == INVALID_HANDLE_VALUE)) { - handle = 0; // signal eof + pimpl->handle = NULL; // signal eof + + // Note: an empty root directory has no "." or ".." entries, so this + // causes a ERROR_FILE_NOT_FOUND error which we do not consider an + // error. It is treated as eof instead. + // Windows Mobile returns ERROR_NO_MORE_FILES; see ticket #3551 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()); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_NO_MORE_FILES) + goto done; + + return error_code(error, system_category()); } + + first_filename = data.cFileName; + set_file_statuses(data.dwFileAttributes, NULL, first_filename, sf, symlink_sf); + +done: + imp.swap(pimpl); 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 +#endif // !defined(UNDER_CE) -BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code = -#ifdef BOOST_WINDOWS_API - ERROR_PATH_NOT_FOUND -#else - ENOENT -#endif - ; +BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code = ERROR_PATH_NOT_FOUND; + +#endif // BOOST_WINDOWS_API } // 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 +#if defined(BOOST_WINDOWS_API) && !defined(UNDER_CE) + +//! Initializes directory iterator implementation +void init_directory_iterator_impl() BOOST_NOEXCEPT { -#ifdef BOOST_POSIX_API - - if (buffer != NULL) + if (filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api) != NULL) { - std::free(buffer); - buffer = NULL; + // Enable the latest format we support. It will get downgraded, if needed, as we attempt + // to create the directory iterator the first time. + filesystem::detail::atomic_store_relaxed(g_extra_data_format, file_id_extd_dir_info_format); } +} - if (handle != NULL) - { - DIR* h = static_cast< DIR* >(handle); - handle = NULL; - int err = 0; - if (BOOST_UNLIKELY(::closedir(h) != 0)) - { - err = errno; - return error_code(err, system_category()); - } - } +#endif // defined(BOOST_WINDOWS_API) && !defined(UNDER_CE) - return error_code(); - -#else - - if (handle != NULL) - { - ::FindClose(handle); - handle = NULL; - } - return error_code(); - -#endif +BOOST_FILESYSTEM_DECL +dir_itr_imp::~dir_itr_imp() BOOST_NOEXCEPT +{ + dir_itr_close(*this); } BOOST_FILESYSTEM_DECL void directory_iterator_construct(directory_iterator& it, path const& p, unsigned int opts, system::error_code* ec) { - if (error(p.empty() ? not_found_error_code : 0, p, ec, "boost::filesystem::directory_iterator::construct")) + if (BOOST_UNLIKELY(p.empty())) { + emit_error(not_found_error_code, 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; - } - } + if (ec) + ec->clear(); try { - path::string_type filename; + boost::intrusive_ptr< detail::dir_itr_imp > imp; + path 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, opts, file_stat, symlink_file_stat); + system::error_code result = dir_itr_create(imp, p, opts, filename, file_stat, symlink_file_stat); - if (result) + while (true) { - if (result != make_error_condition(system::errc::permission_denied) || - (opts & static_cast< unsigned int >(directory_options::skip_permission_denied)) == 0u) + if (result) { - error(result.value(), p, ec, "boost::filesystem::directory_iterator::construct"); + if (result != make_error_condition(system::errc::permission_denied) || + (opts & static_cast< unsigned int >(directory_options::skip_permission_denied)) == 0u) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::directory_iterator::construct", p, result)); + *ec = result; + } + + return; } - return; - } + if (imp->handle == NULL) // eof, make end + 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 + 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')))) + (filename_str[1] == path::dot && filename_str[2] == static_cast< path::string_type::value_type >('\0'))))) { - detail::directory_iterator_increment(it, ec); + imp->dir_entry.assign(p / filename, file_stat, symlink_file_stat); + it.m_imp.swap(imp); + return; } + + // If dot or dot-dot name produced by the underlying API, skip it until the first actual file + result = dir_itr_increment(*imp, filename, file_stat, symlink_file_stat); } } catch (std::bad_alloc&) @@ -602,17 +1101,13 @@ void directory_iterator_increment(directory_iterator& it, system::error_code* ec try { - path::string_type filename; + path filename; file_status file_stat, symlink_file_stat; system::error_code increment_ec; - for (;;) + while (true) { - 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); + increment_ec = dir_itr_increment(*it.m_imp, filename, file_stat, symlink_file_stat); if (BOOST_UNLIKELY(!!increment_ec)) // happens if filesystem is corrupt, such as on a damaged optical disc { diff --git a/src/error_handling.hpp b/src/error_handling.hpp index 91edde4..cd6a72e 100644 --- a/src/error_handling.hpp +++ b/src/error_handling.hpp @@ -49,6 +49,73 @@ typedef boost::winapi::DWORD_ err_t; #define BOOST_ERROR_ALREADY_EXISTS boost::winapi::ERROR_ALREADY_EXISTS_ #define BOOST_ERROR_NOT_SUPPORTED boost::winapi::ERROR_NOT_SUPPORTED_ +// Note: Legacy MinGW doesn't have ntstatus.h and doesn't define NTSTATUS error codes other than STATUS_SUCCESS. +#if !defined(NT_SUCCESS) +#define NT_SUCCESS(Status) (((boost::winapi::NTSTATUS_)(Status)) >= 0) +#endif +#if !defined(STATUS_SUCCESS) +#define STATUS_SUCCESS ((boost::winapi::NTSTATUS_)0x00000000l) +#endif +#if !defined(STATUS_NOT_IMPLEMENTED) +#define STATUS_NOT_IMPLEMENTED ((boost::winapi::NTSTATUS_)0xC0000002l) +#endif +#if !defined(STATUS_INVALID_INFO_CLASS) +#define STATUS_INVALID_INFO_CLASS ((boost::winapi::NTSTATUS_)0xC0000003l) +#endif +#if !defined(STATUS_INVALID_HANDLE) +#define STATUS_INVALID_HANDLE ((boost::winapi::NTSTATUS_)0xC0000008l) +#endif +#if !defined(STATUS_INVALID_PARAMETER) +#define STATUS_INVALID_PARAMETER ((boost::winapi::NTSTATUS_)0xC000000Dl) +#endif +#if !defined(STATUS_NO_SUCH_DEVICE) +#define STATUS_NO_SUCH_DEVICE ((boost::winapi::NTSTATUS_)0xC000000El) +#endif +#if !defined(STATUS_NO_SUCH_FILE) +#define STATUS_NO_SUCH_FILE ((boost::winapi::NTSTATUS_)0xC000000Fl) +#endif +#if !defined(STATUS_NO_MORE_FILES) +#define STATUS_NO_MORE_FILES ((boost::winapi::NTSTATUS_)0x80000006l) +#endif +#if !defined(STATUS_BUFFER_OVERFLOW) +#define STATUS_BUFFER_OVERFLOW ((boost::winapi::NTSTATUS_)0x80000005l) +#endif +#if !defined(STATUS_NO_MEMORY) +#define STATUS_NO_MEMORY ((boost::winapi::NTSTATUS_)0xC0000017l) +#endif +#if !defined(STATUS_ACCESS_DENIED) +#define STATUS_ACCESS_DENIED ((boost::winapi::NTSTATUS_)0xC0000022l) +#endif + +//! Converts NTSTATUS error codes to Win32 error codes for reporting +inline boost::winapi::DWORD_ translate_ntstatus(boost::winapi::NTSTATUS_ status) +{ + // We have to cast to unsigned integral type to avoid signed overflow and narrowing conversion in the constants. + switch (static_cast< boost::winapi::ULONG_ >(status)) + { + case static_cast< boost::winapi::ULONG_ >(STATUS_NO_MEMORY): + return boost::winapi::ERROR_OUTOFMEMORY_; + case static_cast< boost::winapi::ULONG_ >(STATUS_BUFFER_OVERFLOW): + return boost::winapi::ERROR_BUFFER_OVERFLOW_; + case static_cast< boost::winapi::ULONG_ >(STATUS_INVALID_HANDLE): + return boost::winapi::ERROR_INVALID_HANDLE_; + case static_cast< boost::winapi::ULONG_ >(STATUS_INVALID_PARAMETER): + return boost::winapi::ERROR_INVALID_PARAMETER_; + case static_cast< boost::winapi::ULONG_ >(STATUS_NO_MORE_FILES): + return boost::winapi::ERROR_NO_MORE_FILES_; + case static_cast< boost::winapi::ULONG_ >(STATUS_NO_SUCH_DEVICE): + return boost::winapi::ERROR_DEV_NOT_EXIST_; + case static_cast< boost::winapi::ULONG_ >(STATUS_NO_SUCH_FILE): + return boost::winapi::ERROR_FILE_NOT_FOUND_; + case static_cast< boost::winapi::ULONG_ >(STATUS_ACCESS_DENIED): + return boost::winapi::ERROR_ACCESS_DENIED_; + // map "invalid info class" to "not supported" as this error likely indicates that the kernel does not support what we request + case static_cast< boost::winapi::ULONG_ >(STATUS_INVALID_INFO_CLASS): + default: + return boost::winapi::ERROR_NOT_SUPPORTED_; + } +} + #endif // error handling helpers ----------------------------------------------------------// diff --git a/src/operations.cpp b/src/operations.cpp index 42bc078..29dc7d7 100644 --- a/src/operations.cpp +++ b/src/operations.cpp @@ -184,74 +184,6 @@ using boost::system::system_category; #else // defined(BOOST_POSIX_API) -// REPARSE_DATA_BUFFER related definitions are found in ntifs.h, which is part of the -// Windows Device Driver Kit. Since that's inconvenient, the definitions are provided -// here. See http://msdn.microsoft.com/en-us/library/ms791514.aspx - -#if !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) // mingw winnt.h does provide the defs - -#define SYMLINK_FLAG_RELATIVE 1 - -typedef struct _REPARSE_DATA_BUFFER -{ - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union - { - /* - * In SymbolicLink and MountPoint reparse points, there are two names. - * SubstituteName is the effective replacement path for the reparse point. - * This is what should be used for path traversal. - * PrintName is intended for presentation to the user and may omit some - * elements of the path or be absent entirely. - * - * Examples of substitute and print names: - * mklink /D ldrive c:\ - * SubstituteName: "\??\c:\" - * PrintName: "c:\" - * - * mklink /J ldrive c:\ - * SubstituteName: "\??\C:\" - * PrintName: "c:\" - * - * junction ldrive c:\ - * SubstituteName: "\??\C:\" - * PrintName: "" - * - * box.com mounted cloud storage - * SubstituteName: "\??\Volume{}\" - * PrintName: "" - */ - struct - { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - } SymbolicLinkReparseBuffer; - struct - { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPointReparseBuffer; - struct - { - UCHAR DataBuffer[1]; - } GenericReparseBuffer; - }; -} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; - -#define REPARSE_DATA_BUFFER_HEADER_SIZE \ - FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) - -#endif // !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) - #ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE #define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) #endif @@ -260,8 +192,8 @@ typedef struct _REPARSE_DATA_BUFFER #define FSCTL_GET_REPARSE_POINT 0x900a8 #endif -#ifndef IO_REPARSE_TAG_SYMLINK -#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#ifndef SYMLINK_FLAG_RELATIVE +#define SYMLINK_FLAG_RELATIVE 1 #endif // Fallback for MinGW/Cygwin @@ -273,13 +205,6 @@ typedef struct _REPARSE_DATA_BUFFER #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 #endif -// Our convenience type for allocating REPARSE_DATA_BUFFER along with sufficient space after it -union reparse_data_buffer -{ - REPARSE_DATA_BUFFER rdb; - unsigned char storage[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; -}; - #endif // defined(BOOST_POSIX_API) // POSIX/Windows macros ----------------------------------------------------// @@ -294,16 +219,14 @@ union reparse_data_buffer #if defined(BOOST_POSIX_API) #define BOOST_SET_CURRENT_DIRECTORY(P) (::chdir(P) == 0) -#define BOOST_CREATE_HARD_LINK(F, T) (::link(T, F) == 0) #define BOOST_MOVE_FILE(OLD, NEW) (::rename(OLD, NEW) == 0) #define BOOST_RESIZE_FILE(P, SZ) (::truncate(P, SZ) == 0) #else // BOOST_WINDOWS_API #define BOOST_SET_CURRENT_DIRECTORY(P) (::SetCurrentDirectoryW(P) != 0) -#define BOOST_CREATE_HARD_LINK(F, T) (create_hard_link_api(F, T, 0) != 0) #define BOOST_MOVE_FILE(OLD, NEW) (::MoveFileExW(OLD, NEW, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) -#define BOOST_RESIZE_FILE(P, SZ) (resize_file_api(P, SZ) != 0) +#define BOOST_RESIZE_FILE(P, SZ) (resize_file_impl(P, SZ) != 0) #endif @@ -316,6 +239,11 @@ namespace detail { void init_fill_random_impl(unsigned int major_ver, unsigned int minor_ver, unsigned int patch_ver); #endif // defined(linux) || defined(__linux) || defined(__linux__) +#if defined(BOOST_WINDOWS_API) && !defined(UNDER_CE) +//! Initializes directory iterator implementation. Implemented in directory.cpp. +void init_directory_iterator_impl() BOOST_NOEXCEPT; +#endif // defined(BOOST_WINDOWS_API) && !defined(UNDER_CE) + //--------------------------------------------------------------------------------------// // // // helpers (all operating systems) // @@ -324,6 +252,9 @@ void init_fill_random_impl(unsigned int major_ver, unsigned int minor_ver, unsig namespace { +// The number of retries remove_all should make if it detects that the directory it is about to enter has been replaced with a symlink +BOOST_CONSTEXPR_OR_CONST unsigned int remove_all_directory_replaced_retry_count = 5u; + #if defined(BOOST_POSIX_API) // Size of a small buffer for a path that can be placed on stack, in character code units @@ -945,7 +876,7 @@ inline bool remove_impl(path const& p, error_code* ec) //! remove_all() implementation uintmax_t remove_all_impl(path const& p, error_code* ec) { - for (unsigned int attempt = 0u; attempt < 5u; ++attempt) + for (unsigned int attempt = 0u; attempt < remove_all_directory_replaced_retry_count; ++attempt) { fs::file_type type; { @@ -966,7 +897,6 @@ uintmax_t remove_all_impl(path const& p, error_code* ec) } uintmax_t count = 0u; - if (type == fs::directory_file) // but not a directory symlink { fs::directory_iterator itr; @@ -1022,6 +952,71 @@ uintmax_t remove_all_impl(path const& p, error_code* ec) // // //--------------------------------------------------------------------------------------// +// REPARSE_DATA_BUFFER related definitions are found in ntifs.h, which is part of the +// Windows Device Driver Kit. Since that's inconvenient, the definitions are provided +// here. See http://msdn.microsoft.com/en-us/library/ms791514.aspx +struct reparse_data_buffer +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + /* + * In SymbolicLink and MountPoint reparse points, there are two names. + * SubstituteName is the effective replacement path for the reparse point. + * This is what should be used for path traversal. + * PrintName is intended for presentation to the user and may omit some + * elements of the path or be absent entirely. + * + * Examples of substitute and print names: + * mklink /D ldrive c:\ + * SubstituteName: "\??\c:\" + * PrintName: "c:\" + * + * mklink /J ldrive c:\ + * SubstituteName: "\??\C:\" + * PrintName: "c:\" + * + * junction ldrive c:\ + * SubstituteName: "\??\C:\" + * PrintName: "" + * + * box.com mounted cloud storage + * SubstituteName: "\??\Volume{}\" + * PrintName: "" + */ + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +}; + +// Our convenience type for allocating REPARSE_DATA_BUFFER along with sufficient space after it +union reparse_data_buffer_with_storage +{ + reparse_data_buffer rdb; + unsigned char storage[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; +}; + // Windows CE has no environment variables #if !defined(UNDER_CE) inline std::wstring wgetenv(const wchar_t* name) @@ -1068,53 +1063,40 @@ inline void to_FILETIME(std::time_t t, FILETIME& ft) BOOST_NOEXCEPT ft.dwHighDateTime = static_cast< DWORD >(temp >> 32); } -// Thanks to Jeremy Maitin-Shepard for much help and for permission to -// base the equivalent()implementation on portions of his -// file-equivalence-win32.cpp experimental code. - -struct handle_wrapper +inline bool is_reparse_point_a_symlink(path const& p) { - HANDLE handle; + handle_wrapper h(create_file_handle(p, 0u, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)); + if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE)) + return false; - handle_wrapper() BOOST_NOEXCEPT : handle(INVALID_HANDLE_VALUE) {} - explicit handle_wrapper(HANDLE h) BOOST_NOEXCEPT : handle(h) {} - ~handle_wrapper() BOOST_NOEXCEPT + ULONG reparse_point_tag; + GetFileInformationByHandleEx_t* get_file_information_by_handle_ex = filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api); + if (BOOST_LIKELY(get_file_information_by_handle_ex != NULL)) { - if (handle != INVALID_HANDLE_VALUE) - ::CloseHandle(handle); + file_attribute_tag_info info; + BOOL result = get_file_information_by_handle_ex(h.handle, file_attribute_tag_info_class, &info, sizeof(info)); + if (BOOST_UNLIKELY(!result)) + return false; + + if ((info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0u) + return false; + + reparse_point_tag = info.ReparseTag; } - BOOST_DELETED_FUNCTION(handle_wrapper(handle_wrapper const&)) - BOOST_DELETED_FUNCTION(handle_wrapper& operator=(handle_wrapper const&)) -}; + else + { + boost::scoped_ptr< reparse_data_buffer_with_storage > buf(new reparse_data_buffer_with_storage); -inline HANDLE create_file_handle(path const& p, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) -{ - return ::CreateFileW(p.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); -} + // Query the reparse data + DWORD dwRetLen = 0u; + BOOL result = ::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buf.get(), sizeof(*buf), &dwRetLen, NULL); + if (BOOST_UNLIKELY(!result)) + return false; -bool is_reparse_point_a_symlink(path const& p) -{ - handle_wrapper h(create_file_handle(p, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); - if (h.handle == INVALID_HANDLE_VALUE) - return false; + reparse_point_tag = buf->rdb.ReparseTag; + } - boost::scoped_ptr< reparse_data_buffer > buf(new reparse_data_buffer); - - // Query the reparse data - DWORD dwRetLen = 0u; - BOOL result = ::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buf.get(), sizeof(*buf), &dwRetLen, NULL); - if (!result) - return false; - - return buf->rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK - // Issue 9016 asked that NTFS directory junctions be recognized as directories. - // That is equivalent to recognizing them as symlinks, and then the normal symlink - // mechanism will take care of recognizing them as directories. - // - // Directory junctions are very similar to symlinks, but have some performance - // and other advantages over symlinks. They can be created from the command line - // with "mklink /j junction-name target-path". - || buf->rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT; // aka "directory junction" or "junction" + return is_reparse_point_tag_a_symlink(reparse_point_tag); } inline std::size_t get_full_path_name(path const& src, std::size_t len, wchar_t* buf, wchar_t** p) @@ -1238,66 +1220,85 @@ inline bool remove_impl(path const& p, error_code* ec) //! remove_all() implementation uintmax_t remove_all_impl(path const& p, error_code* ec) { - const DWORD attrs = ::GetFileAttributesW(p.c_str()); - bool recurse; - if (BOOST_UNLIKELY(attrs == INVALID_FILE_ATTRIBUTES)) + for (unsigned int attempt = 0u; attempt < remove_all_directory_replaced_retry_count; ++attempt) { - error_code local_ec; - file_type type = process_status_failure(p, &local_ec).type(); - - if (type == fs::file_not_found) - return 0u; - - if (BOOST_UNLIKELY(type == fs::status_error)) + const DWORD attrs = ::GetFileAttributesW(p.c_str()); + bool recurse; + if (BOOST_UNLIKELY(attrs == INVALID_FILE_ATTRIBUTES)) { - if (!ec) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, local_ec)); + error_code local_ec; + file_type type = process_status_failure(p, &local_ec).type(); - *ec = local_ec; - return static_cast< uintmax_t >(-1); + if (type == fs::file_not_found) + return 0u; + + if (BOOST_UNLIKELY(type == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, local_ec)); + + *ec = local_ec; + return static_cast< uintmax_t >(-1); + } + + // Some unknown file type + recurse = false; + } + else + { + // Recurse into directories, but not into junctions or directory symlinks + recurse = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && (attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0; } - // Some unknown file type - recurse = false; - } - else - { - // Recurse into directories, but not into junctions or directory symlinks - recurse = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && (attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0; - } + uintmax_t count = 0u; + if (recurse) + { + fs::directory_iterator itr; + error_code local_ec; + fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::_detail_no_follow), &local_ec); + if (BOOST_UNLIKELY(!!local_ec)) + { + if (local_ec == make_error_condition(system::errc::too_many_symbolic_link_levels)) + continue; - uintmax_t count = 0u; + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, local_ec)); - if (recurse) - { - fs::directory_iterator itr; - fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::none), ec); + *ec = local_ec; + return static_cast< uintmax_t >(-1); + } + + const fs::directory_iterator end_dit; + while (itr != end_dit) + { + count += remove_all_impl(itr->path(), ec); + if (ec && *ec) + return static_cast< uintmax_t >(-1); + + fs::detail::directory_iterator_increment(itr, ec); + if (ec && *ec) + return static_cast< uintmax_t >(-1); + } + } + + count += remove_impl(p, attrs, ec); if (ec && *ec) return static_cast< uintmax_t >(-1); - const fs::directory_iterator end_dit; - while (itr != end_dit) - { - count += remove_all_impl(itr->path(), ec); - if (ec && *ec) - return static_cast< uintmax_t >(-1); - - fs::detail::directory_iterator_increment(itr, ec); - if (ec && *ec) - return static_cast< uintmax_t >(-1); - } + return count; } - count += remove_impl(p, attrs, ec); - if (ec && *ec) - return static_cast< uintmax_t >(-1); + error_code local_ec(make_error_code(system::errc::too_many_symbolic_link_levels)); + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all: path cannot be opened as a directory", p, local_ec)); - return count; + *ec = local_ec; + return static_cast< uintmax_t >(-1); } -inline BOOL resize_file_api(const wchar_t* p, uintmax_t size) +inline BOOL resize_file_impl(const wchar_t* p, uintmax_t size) { - handle_wrapper h(CreateFileW(p, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + handle_wrapper h(CreateFileW(p, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); LARGE_INTEGER sz; sz.QuadPart = size; return h.handle != INVALID_HANDLE_VALUE && ::SetFilePointerEx(h.handle, sz, 0, FILE_BEGIN) && ::SetEndOfFile(h.handle); @@ -1452,19 +1453,29 @@ done: // Windows kernel32.dll functions that may or may not be present // must be accessed through pointers -typedef BOOL (WINAPI* PtrCreateHardLinkW)( +typedef BOOL (WINAPI CreateHardLinkW_t)( /*__in*/ LPCWSTR lpFileName, /*__in*/ LPCWSTR lpExistingFileName, /*__reserved*/ LPSECURITY_ATTRIBUTES lpSecurityAttributes); -PtrCreateHardLinkW create_hard_link_api = NULL; +CreateHardLinkW_t* create_hard_link_api = NULL; -typedef BOOLEAN (WINAPI* PtrCreateSymbolicLinkW)( +typedef BOOLEAN (WINAPI CreateSymbolicLinkW_t)( /*__in*/ LPCWSTR lpSymlinkFileName, /*__in*/ LPCWSTR lpTargetFileName, /*__in*/ DWORD dwFlags); -PtrCreateSymbolicLinkW create_symbolic_link_api = NULL; +CreateSymbolicLinkW_t* create_symbolic_link_api = NULL; + +} // unnamed namespace + +GetFileInformationByHandleEx_t* get_file_information_by_handle_ex_api = NULL; + +#if !defined(UNDER_CE) +NtQueryDirectoryFile_t* nt_query_directory_file_api = NULL; +#endif // !defined(UNDER_CE) + +namespace { //! Initializes WinAPI function pointers BOOST_FILESYSTEM_INIT_FUNC init_winapi_func_ptrs() @@ -1472,9 +1483,21 @@ BOOST_FILESYSTEM_INIT_FUNC init_winapi_func_ptrs() boost::winapi::HMODULE_ h = boost::winapi::GetModuleHandleW(L"kernel32.dll"); if (BOOST_LIKELY(!!h)) { - create_hard_link_api = (PtrCreateHardLinkW)boost::winapi::get_proc_address(h, "CreateHardLinkW"); - create_symbolic_link_api = (PtrCreateSymbolicLinkW)boost::winapi::get_proc_address(h, "CreateSymbolicLinkW"); + filesystem::detail::atomic_store_relaxed(get_file_information_by_handle_ex_api, (GetFileInformationByHandleEx_t*)boost::winapi::get_proc_address(h, "GetFileInformationByHandleEx")); + filesystem::detail::atomic_store_relaxed(create_hard_link_api, (CreateHardLinkW_t*)boost::winapi::get_proc_address(h, "CreateHardLinkW")); + filesystem::detail::atomic_store_relaxed(create_symbolic_link_api, (CreateSymbolicLinkW_t*)boost::winapi::get_proc_address(h, "CreateSymbolicLinkW")); } + +#if !defined(UNDER_CE) + h = boost::winapi::GetModuleHandleW(L"ntdll.dll"); + if (BOOST_LIKELY(!!h)) + { + filesystem::detail::atomic_store_relaxed(nt_query_directory_file_api, (NtQueryDirectoryFile_t*)boost::winapi::get_proc_address(h, "NtQueryDirectoryFile")); + } + + init_directory_iterator_impl(); +#endif // !defined(UNDER_CE) + return BOOST_FILESYSTEM_INITRETSUCCESS_V; } @@ -2155,7 +2178,7 @@ bool copy_file(path const& from, path const& to, unsigned int options, error_cod // Create handle_wrappers here so that CloseHandle calls don't clobber error code returned by GetLastError handle_wrapper hw_from, hw_to; - hw_from.handle = create_file_handle(from.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + hw_from.handle = create_file_handle(from.c_str(), 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS); FILETIME lwt_from; if (hw_from.handle == INVALID_HANDLE_VALUE) @@ -2169,7 +2192,7 @@ bool copy_file(path const& from, path const& to, unsigned int options, error_cod if (!::GetFileTime(hw_from.handle, 0, 0, &lwt_from)) goto fail_last_error; - hw_to.handle = create_file_handle(to.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + hw_to.handle = create_file_handle(to.c_str(), 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS); if (hw_to.handle != INVALID_HANDLE_VALUE) { @@ -2484,13 +2507,30 @@ void create_directory_symlink(path const& to, path const& from, system::error_co BOOST_FILESYSTEM_DECL void create_hard_link(path const& to, path const& from, error_code* ec) { -#if defined(BOOST_WINDOWS_API) - // see if actually supported by Windows runtime dll - if (error(!create_hard_link_api ? BOOST_ERROR_NOT_SUPPORTED : 0, to, from, ec, "boost::filesystem::create_hard_link")) - return; -#endif + if (ec) + ec->clear(); - error(!BOOST_CREATE_HARD_LINK(from.c_str(), to.c_str()) ? BOOST_ERRNO : 0, to, from, ec, "boost::filesystem::create_hard_link"); +#if defined(BOOST_POSIX_API) + int err = ::link(to.c_str(), from.c_str()); + if (BOOST_UNLIKELY(err < 0)) + { + err = errno; + emit_error(err, to, from, ec, "boost::filesystem::create_hard_link"); + } +#else + // see if actually supported by Windows runtime dll + CreateHardLinkW_t* chl_api = filesystem::detail::atomic_load_relaxed(create_hard_link_api); + if (BOOST_UNLIKELY(!chl_api)) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, to, from, ec, "boost::filesystem::create_hard_link"); + return; + } + + if (BOOST_UNLIKELY(!chl_api(from.c_str(), to.c_str(), NULL))) + { + emit_error(BOOST_ERRNO, to, from, ec, "boost::filesystem::create_hard_link"); + } +#endif } BOOST_FILESYSTEM_DECL @@ -2508,13 +2548,14 @@ void create_symlink(path const& to, path const& from, error_code* ec) } #else // see if actually supported by Windows runtime dll - if (!create_symbolic_link_api) + CreateSymbolicLinkW_t* csl_api = filesystem::detail::atomic_load_relaxed(create_symbolic_link_api); + if (BOOST_UNLIKELY(!csl_api)) { emit_error(BOOST_ERROR_NOT_SUPPORTED, to, from, ec, "boost::filesystem::create_symlink"); return; } - if (!create_symbolic_link_api(from.c_str(), to.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) + if (BOOST_UNLIKELY(!csl_api(from.c_str(), to.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))) { emit_error(BOOST_ERRNO, to, from, ec, "boost::filesystem::create_symlink"); } @@ -2647,6 +2688,10 @@ bool equivalent(path const& p1, path const& p2, system::error_code* ec) #else // Windows + // Thanks to Jeremy Maitin-Shepard for much help and for permission to + // base the equivalent() implementation on portions of his + // file-equivalence-win32.cpp experimental code. + // Note well: Physical location on external media is part of the // equivalence criteria. If there are no open handles, physical location // can change due to defragmentation or other relocations. Thus handles @@ -2657,22 +2702,20 @@ bool equivalent(path const& p1, path const& p2, system::error_code* ec) handle_wrapper h2( create_file_handle( p2.c_str(), - 0, + 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, + NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - 0)); + FILE_FLAG_BACKUP_SEMANTICS)); handle_wrapper h1( create_file_handle( p1.c_str(), - 0, + 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, + NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - 0)); + FILE_FLAG_BACKUP_SEMANTICS)); if (BOOST_UNLIKELY(h1.handle == INVALID_HANDLE_VALUE || h2.handle == INVALID_HANDLE_VALUE)) { @@ -2807,7 +2850,7 @@ uintmax_t hard_link_count(path const& p, system::error_code* ec) #else // defined(BOOST_POSIX_API) handle_wrapper h( - create_file_handle(p.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + create_file_handle(p.c_str(), 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS)); if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE)) { @@ -2929,7 +2972,7 @@ std::time_t creation_time(path const& p, system::error_code* ec) #else // defined(BOOST_POSIX_API) handle_wrapper hw( - create_file_handle(p.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + create_file_handle(p.c_str(), 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS)); if (BOOST_UNLIKELY(hw.handle == INVALID_HANDLE_VALUE)) { @@ -2982,7 +3025,7 @@ std::time_t last_write_time(path const& p, system::error_code* ec) #else // defined(BOOST_POSIX_API) handle_wrapper hw( - create_file_handle(p.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + create_file_handle(p.c_str(), 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS)); if (BOOST_UNLIKELY(hw.handle == INVALID_HANDLE_VALUE)) { @@ -3044,7 +3087,7 @@ void last_write_time(path const& p, const std::time_t new_time, system::error_co #else // defined(BOOST_POSIX_API) handle_wrapper hw( - create_file_handle(p.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + create_file_handle(p.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS)); if (BOOST_UNLIKELY(hw.handle == INVALID_HANDLE_VALUE)) { @@ -3156,6 +3199,9 @@ void permissions(path const& p, perms prms, system::error_code* ec) BOOST_FILESYSTEM_DECL path read_symlink(path const& p, system::error_code* ec) { + if (ec) + ec->clear(); + path symlink_path; #ifdef BOOST_POSIX_API @@ -3174,8 +3220,6 @@ path read_symlink(path const& p, system::error_code* ec) else if (BOOST_LIKELY(static_cast< std::size_t >(result) < sizeof(small_buf))) { symlink_path.assign(small_buf, small_buf + result); - if (ec) - ec->clear(); } else { @@ -3199,8 +3243,6 @@ path read_symlink(path const& p, system::error_code* ec) else if (BOOST_LIKELY(static_cast< std::size_t >(result) < path_max)) { symlink_path.assign(buf.get(), buf.get() + result); - if (ec) - ec->clear(); break; } } @@ -3209,40 +3251,50 @@ path read_symlink(path const& p, system::error_code* ec) #else handle_wrapper h( - create_file_handle(p.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0)); + create_file_handle(p.c_str(), 0u, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)); - if (error(h.handle == INVALID_HANDLE_VALUE ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::read_symlink")) - return symlink_path; - - boost::scoped_ptr< reparse_data_buffer > buf(new reparse_data_buffer); - DWORD sz = 0u; - if (!error(::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, 0, 0, buf.get(), sizeof(*buf), &sz, 0) == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::read_symlink")) + DWORD error; + if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE)) { - const wchar_t* buffer; - std::size_t offset, len; - switch (buf->rdb.ReparseTag) - { - case IO_REPARSE_TAG_MOUNT_POINT: - buffer = buf->rdb.MountPointReparseBuffer.PathBuffer; - offset = buf->rdb.MountPointReparseBuffer.SubstituteNameOffset; - len = buf->rdb.MountPointReparseBuffer.SubstituteNameLength; - break; + error = ::GetLastError(); - case IO_REPARSE_TAG_SYMLINK: - buffer = buf->rdb.SymbolicLinkReparseBuffer.PathBuffer; - offset = buf->rdb.SymbolicLinkReparseBuffer.SubstituteNameOffset; - len = buf->rdb.SymbolicLinkReparseBuffer.SubstituteNameLength; - // Note: iff info.rdb.SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE - // -> resulting path is relative to the source - break; - - default: - emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "Unknown ReparseTag in boost::filesystem::read_symlink"); - return symlink_path; - } - - symlink_path = convert_nt_path_to_win32_path(buffer + offset / sizeof(wchar_t), len / sizeof(wchar_t)); + return_error: + emit_error(error, p, ec, "boost::filesystem::read_symlink"); + return symlink_path; } + + boost::scoped_ptr< reparse_data_buffer_with_storage > buf(new reparse_data_buffer_with_storage); + DWORD sz = 0u; + if (BOOST_UNLIKELY(!::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, 0, 0, buf.get(), sizeof(*buf), &sz, 0))) + { + error = ::GetLastError(); + goto return_error; + } + + const wchar_t* buffer; + std::size_t offset, len; + switch (buf->rdb.ReparseTag) + { + case IO_REPARSE_TAG_MOUNT_POINT: + buffer = buf->rdb.MountPointReparseBuffer.PathBuffer; + offset = buf->rdb.MountPointReparseBuffer.SubstituteNameOffset; + len = buf->rdb.MountPointReparseBuffer.SubstituteNameLength; + break; + + case IO_REPARSE_TAG_SYMLINK: + buffer = buf->rdb.SymbolicLinkReparseBuffer.PathBuffer; + offset = buf->rdb.SymbolicLinkReparseBuffer.SubstituteNameOffset; + len = buf->rdb.SymbolicLinkReparseBuffer.SubstituteNameLength; + // Note: iff info.rdb.SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE + // -> resulting path is relative to the source + break; + + default: + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "Unknown ReparseTag in boost::filesystem::read_symlink"); + return symlink_path; + } + + symlink_path = convert_nt_path_to_win32_path(buffer + offset / sizeof(wchar_t), len / sizeof(wchar_t)); #endif return symlink_path; @@ -3479,12 +3531,11 @@ file_status status(path const& p, error_code* ec) handle_wrapper h( create_file_handle( p.c_str(), - 0, // dwDesiredAccess; attributes only + 0u, // dwDesiredAccess; attributes only FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, // lpSecurityAttributes + NULL, // lpSecurityAttributes OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - 0)); // hTemplateFile + FILE_FLAG_BACKUP_SEMANTICS)); if (h.handle == INVALID_HANDLE_VALUE) { diff --git a/src/unique_path.cpp b/src/unique_path.cpp index d48f8ed..4815ca4 100644 --- a/src/unique_path.cpp +++ b/src/unique_path.cpp @@ -108,27 +108,6 @@ namespace detail { namespace { -#if defined(BOOST_FILESYSTEM_HAS_BCRYPT) -//! Converts NTSTATUS error codes to Win32 error codes for reporting -inline boost::winapi::DWORD_ translate_ntstatus(boost::winapi::NTSTATUS_ status) -{ - // Note: Legacy MinGW doesn't have ntstatus.h and doesn't define NTSTATUS error codes other than STATUS_SUCCESS. - // Because of this we have to use hardcoded integer literals here. Also, we have to cast to unsigned - // integral type to avoid signed overflow and narrowing conversion in the constants. - switch (static_cast< boost::winapi::ULONG_ >(status)) - { - case 0xC0000017ul: // STATUS_NO_MEMORY - return boost::winapi::ERROR_OUTOFMEMORY_; - case 0xC0000008ul: // STATUS_INVALID_HANDLE - return boost::winapi::ERROR_INVALID_HANDLE_; - case 0xC000000Dul: // STATUS_INVALID_PARAMETER - return boost::winapi::ERROR_INVALID_PARAMETER_; - default: - return boost::winapi::ERROR_NOT_SUPPORTED_; - } -} -#endif // defined(BOOST_FILESYSTEM_HAS_BCRYPT) - #if defined(BOOST_POSIX_API) && !defined(BOOST_FILESYSTEM_HAS_ARC4RANDOM) //! Fills buffer with cryptographically random data obtained from /dev/(u)random diff --git a/src/windows_tools.hpp b/src/windows_tools.hpp index b1fe3c3..d68f999 100644 --- a/src/windows_tools.hpp +++ b/src/windows_tools.hpp @@ -13,12 +13,22 @@ #ifndef BOOST_FILESYSTEM_SRC_WINDOWS_TOOLS_HPP_ #define BOOST_FILESYSTEM_SRC_WINDOWS_TOOLS_HPP_ +#include #include #include #include +#include // NTSTATUS_ #include +#ifndef IO_REPARSE_TAG_MOUNT_POINT +#define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) +#endif + +#ifndef IO_REPARSE_TAG_SYMLINK +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif + namespace boost { namespace filesystem { namespace detail { @@ -52,6 +62,121 @@ inline boost::filesystem::perms make_permissions(boost::filesystem::path const& return prms; } +inline bool is_reparse_point_tag_a_symlink(ULONG reparse_point_tag) +{ + return reparse_point_tag == IO_REPARSE_TAG_SYMLINK + // Issue 9016 asked that NTFS directory junctions be recognized as directories. + // That is equivalent to recognizing them as symlinks, and then the normal symlink + // mechanism will take care of recognizing them as directories. + // + // Directory junctions are very similar to symlinks, but have some performance + // and other advantages over symlinks. They can be created from the command line + // with "mklink /J junction-name target-path". + // + // Note that mounted filesystems also have the same repartse point tag, which makes + // them look like directory symlinks in terms of Boost.Filesystem. read_symlink() + // may return a volume path or NT path for such symlinks. + || reparse_point_tag == IO_REPARSE_TAG_MOUNT_POINT; // aka "directory junction" or "junction" +} + +#if !defined(UNDER_CE) + +//! IO_STATUS_BLOCK definition from Windows SDK. +struct io_status_block +{ + union + { + boost::winapi::NTSTATUS_ Status; + PVOID Pointer; + }; + ULONG_PTR Information; +}; + +//! UNICODE_STRING definition from Windows SDK +struct unicode_string +{ + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +}; + +//! PIO_APC_ROUTINE definition from Windows SDK +typedef VOID (NTAPI* pio_apc_routine) (PVOID ApcContext, io_status_block* IoStatusBlock, ULONG Reserved); + +//! FILE_INFORMATION_CLASS enum entries +enum file_information_class +{ + file_directory_information_class = 1 +}; + +//! NtQueryDirectoryFile signature. Available since Windows NT 4.0 (probably). +typedef boost::winapi::NTSTATUS_ (NTAPI NtQueryDirectoryFile_t)( + /*in*/ HANDLE FileHandle, + /*in, optional*/ HANDLE Event, + /*in, optional*/ pio_apc_routine ApcRoutine, + /*in, optional*/ PVOID ApcContext, + /*out*/ io_status_block* IoStatusBlock, + /*out*/ PVOID FileInformation, + /*in*/ ULONG Length, + /*in*/ file_information_class FileInformationClass, + /*in*/ BOOLEAN ReturnSingleEntry, + /*in, optional*/ unicode_string* FileName, + /*in*/ BOOLEAN RestartScan); + +extern NtQueryDirectoryFile_t* nt_query_directory_file_api; + +#endif // !defined(UNDER_CE) + +//! FILE_INFO_BY_HANDLE_CLASS enum entries +enum file_info_by_handle_class +{ + file_attribute_tag_info_class = 9, + file_id_both_directory_info_class = 10, + file_id_both_directory_restart_info_class = 11, + file_full_directory_info_class = 14, + file_full_directory_restart_info_class = 15, + file_id_extd_directory_info_class = 19, + file_id_extd_directory_restart_info_class = 20 +}; + +//! FILE_ATTRIBUTE_TAG_INFO definition from Windows SDK +struct file_attribute_tag_info +{ + DWORD FileAttributes; + DWORD ReparseTag; +}; + +//! GetFileInformationByHandleEx signature. Available since Windows Vista. +typedef BOOL (WINAPI GetFileInformationByHandleEx_t)( + /*__in*/ HANDLE hFile, + /*__in*/ file_info_by_handle_class FileInformationClass, // the actual type is FILE_INFO_BY_HANDLE_CLASS enum + /*__out_bcount(dwBufferSize)*/ LPVOID lpFileInformation, + /*__in*/ DWORD dwBufferSize); + +extern GetFileInformationByHandleEx_t* get_file_information_by_handle_ex_api; + +//! HANDLE wrapper that automatically closes the handle +struct handle_wrapper +{ + HANDLE handle; + + handle_wrapper() BOOST_NOEXCEPT : handle(INVALID_HANDLE_VALUE) {} + explicit handle_wrapper(HANDLE h) BOOST_NOEXCEPT : handle(h) {} + ~handle_wrapper() BOOST_NOEXCEPT + { + if (handle != INVALID_HANDLE_VALUE) + ::CloseHandle(handle); + } + BOOST_DELETED_FUNCTION(handle_wrapper(handle_wrapper const&)) + BOOST_DELETED_FUNCTION(handle_wrapper& operator=(handle_wrapper const&)) +}; + +//! Creates a file handle +inline HANDLE create_file_handle(boost::filesystem::path const& p, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile = NULL) +{ + return ::CreateFileW(p.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); +} + } // namespace detail } // namespace filesystem } // namespace boost