diff options
author | robot-contrib <[email protected]> | 2022-08-26 11:41:38 +0300 |
---|---|---|
committer | robot-contrib <[email protected]> | 2022-08-26 11:41:38 +0300 |
commit | 76a1e556daf2542e3b3d55d7971cc1ed0abb1f50 (patch) | |
tree | c9603488e24fef3c6715a36b7962b27cb43f0c60 /contrib/restricted/boost/filesystem/src/operations.cpp | |
parent | 559b3483fff94291f600c59ad6782cc6338d7dad (diff) |
Update contrib/restricted/boost/filesystem and contrib/restricted/boost/io to 1.79.0
Diffstat (limited to 'contrib/restricted/boost/filesystem/src/operations.cpp')
-rw-r--r-- | contrib/restricted/boost/filesystem/src/operations.cpp | 5768 |
1 files changed, 3754 insertions, 2014 deletions
diff --git a/contrib/restricted/boost/filesystem/src/operations.cpp b/contrib/restricted/boost/filesystem/src/operations.cpp index 6a72f891962..8d2f0adf963 100644 --- a/contrib/restricted/boost/filesystem/src/operations.cpp +++ b/contrib/restricted/boost/filesystem/src/operations.cpp @@ -2,224 +2,210 @@ // Copyright 2002-2009, 2014 Beman Dawes // Copyright 2001 Dietmar Kuehl +// Copyright 2018-2022 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 -//--------------------------------------------------------------------------------------// - -// define 64-bit offset macros BEFORE including boost/config.hpp (see ticket #5355) -#if defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ < 24 -// Android fully supports 64-bit file offsets only for API 24 and above. -// -// Trying to define _FILE_OFFSET_BITS=64 for APIs below 24 -// leads to compilation failure for one or another reason, -// depending on target Android API level, Android NDK version, -// used STL, order of include paths and more. -// For more information, please see: -// - https://github.com/boostorg/filesystem/issues/65 -// - https://github.com/boostorg/filesystem/pull/69 -// -// Android NDK developers consider it the expected behavior. -// See their official position here: -// - https://github.com/android-ndk/ndk/issues/501#issuecomment-326447479 -// - https://android.googlesource.com/platform/bionic/+/a34817457feee026e8702a1d2dffe9e92b51d7d1/docs/32-bit-abi.md#32_bit-abi-bugs -// -// Thus we do not define _FILE_OFFSET_BITS in such case. -#else -// Defining _FILE_OFFSET_BITS=64 should kick in 64-bit off_t's -// (and thus st_size) on 32-bit systems that provide the Large File -// Support (LFS) interface, such as Linux, Solaris, and IRIX. -// -// At the time of this comment writing (March 2018), on most systems -// _FILE_OFFSET_BITS=64 definition is harmless: -// either the definition is supported and enables 64-bit off_t, -// or the definition is not supported and is ignored, in which case -// off_t does not change its default size for the target system -// (which may be 32-bit or 64-bit already). -// Thus it makes sense to have _FILE_OFFSET_BITS=64 defined by default, -// instead of listing every system that supports the definition. -// Those few systems, on which _FILE_OFFSET_BITS=64 is harmful, -// for example this definition causes compilation failure on those systems, -// should be exempt from defining _FILE_OFFSET_BITS by adding -// an appropriate #elif block above with the appropriate comment. -// -// _FILE_OFFSET_BITS must be defined before any headers are included -// to ensure that the definition is available to all included headers. -// That is required at least on Solaris, and possibly on other -// systems as well. -#define _FILE_OFFSET_BITS 64 -#endif - -// define BOOST_FILESYSTEM_SOURCE so that <boost/filesystem/config.hpp> knows -// the library is being built (possibly exporting rather than importing code) -#define BOOST_FILESYSTEM_SOURCE - -#ifndef BOOST_SYSTEM_NO_DEPRECATED -# define BOOST_SYSTEM_NO_DEPRECATED -#endif - -#ifndef _POSIX_PTHREAD_SEMANTICS -# define _POSIX_PTHREAD_SEMANTICS // Sun readdir_r()needs this -#endif +//--------------------------------------------------------------------------------------// +#include "platform_config.hpp" + +#include <boost/predef/os/bsd/open.h> +#include <boost/filesystem/config.hpp> #include <boost/filesystem/operations.hpp> -#include <boost/scoped_array.hpp> +#include <boost/filesystem/file_status.hpp> +#include <boost/filesystem/exception.hpp> +#include <boost/filesystem/directory.hpp> +#include <boost/system/error_code.hpp> +#include <boost/smart_ptr/scoped_ptr.hpp> +#include <boost/smart_ptr/scoped_array.hpp> #include <boost/detail/workaround.hpp> +#include <boost/core/bit.hpp> +#include <boost/cstdint.hpp> +#include <boost/assert.hpp> +#include <new> // std::bad_alloc, std::nothrow #include <limits> -#include <vector> -#include <cstdlib> // for malloc, free +#include <string> +#include <cstddef> +#include <cstdlib> // for malloc, free #include <cstring> -#include <cstdio> // for remove, rename -#if defined(__QNXNTO__) // see ticket #5355 -# include <stdio.h> +#include <cstdio> // for remove, rename +#if defined(__QNXNTO__) // see ticket #5355 +#include <stdio.h> #endif #include <cerrno> -#ifdef BOOST_FILEYSTEM_INCLUDE_IOSTREAM -# include <iostream> +#ifdef BOOST_POSIX_API + +#include <sys/types.h> +#include <sys/stat.h> + +#if defined(__wasm) +// WASI does not have statfs or statvfs. +#elif !defined(__APPLE__) && \ + (!defined(__OpenBSD__) || BOOST_OS_BSD_OPEN >= BOOST_VERSION_NUMBER(4, 4, 0)) && \ + !defined(__ANDROID__) && \ + !defined(__VXWORKS__) +#include <sys/statvfs.h> +#define BOOST_STATVFS statvfs +#define BOOST_STATVFS_F_FRSIZE vfs.f_frsize +#else +#ifdef __OpenBSD__ +#include <sys/param.h> +#elif defined(__ANDROID__) +#include <sys/vfs.h> +#endif +#if !defined(__VXWORKS__) +#include <sys/mount.h> +#endif +#define BOOST_STATVFS statfs +#define BOOST_STATVFS_F_FRSIZE static_cast< uintmax_t >(vfs.f_bsize) +#endif // BOOST_STATVFS definition + +#include <unistd.h> +#include <fcntl.h> +#if _POSIX_C_SOURCE < 200809L +#include <utime.h> +#endif +#include <limits.h> + +#if defined(linux) || defined(__linux) || defined(__linux__) + +#include <sys/vfs.h> +#include <sys/utsname.h> +#include <sys/syscall.h> +#include <sys/sysmacros.h> +#if !defined(BOOST_FILESYSTEM_DISABLE_SENDFILE) +#include <sys/sendfile.h> +#define BOOST_FILESYSTEM_USE_SENDFILE +#endif // !defined(BOOST_FILESYSTEM_DISABLE_SENDFILE) +#if !defined(BOOST_FILESYSTEM_DISABLE_COPY_FILE_RANGE) && defined(__NR_copy_file_range) +#define BOOST_FILESYSTEM_USE_COPY_FILE_RANGE +#endif // !defined(BOOST_FILESYSTEM_DISABLE_COPY_FILE_RANGE) && defined(__NR_copy_file_range) +#if !defined(BOOST_FILESYSTEM_DISABLE_STATX) && (defined(BOOST_FILESYSTEM_HAS_STATX) || defined(BOOST_FILESYSTEM_HAS_STATX_SYSCALL)) +#if !defined(BOOST_FILESYSTEM_HAS_STATX) && defined(BOOST_FILESYSTEM_HAS_STATX_SYSCALL) +#include <linux/stat.h> +#endif +#define BOOST_FILESYSTEM_USE_STATX +#endif // !defined(BOOST_FILESYSTEM_DISABLE_STATX) && (defined(BOOST_FILESYSTEM_HAS_STATX) || defined(BOOST_FILESYSTEM_HAS_STATX_SYSCALL)) + +#if defined(__has_include) +#if __has_include(<linux/magic.h>) +// This header was introduced in Linux kernel 2.6.19 +#include <linux/magic.h> +#endif +#endif + +// Some filesystem type magic constants are not defined in older kernel headers +#ifndef PROC_SUPER_MAGIC +#define PROC_SUPER_MAGIC 0x9fa0 +#endif +#ifndef SYSFS_MAGIC +#define SYSFS_MAGIC 0x62656572 +#endif +#ifndef TRACEFS_MAGIC +#define TRACEFS_MAGIC 0x74726163 +#endif +#ifndef DEBUGFS_MAGIC +#define DEBUGFS_MAGIC 0x64626720 +#endif + +#endif // defined(linux) || defined(__linux) || defined(__linux__) + +#if defined(POSIX_FADV_SEQUENTIAL) && (!defined(__ANDROID__) || __ANDROID_API__ >= 21) +#define BOOST_FILESYSTEM_HAS_POSIX_FADVISE #endif +#if defined(BOOST_FILESYSTEM_HAS_STAT_ST_MTIM) +#define BOOST_FILESYSTEM_STAT_ST_MTIMENSEC st_mtim.tv_nsec +#elif defined(BOOST_FILESYSTEM_HAS_STAT_ST_MTIMESPEC) +#define BOOST_FILESYSTEM_STAT_ST_MTIMENSEC st_mtimespec.tv_nsec +#elif defined(BOOST_FILESYSTEM_HAS_STAT_ST_MTIMENSEC) +#define BOOST_FILESYSTEM_STAT_ST_MTIMENSEC st_mtimensec +#endif + +#if defined(BOOST_FILESYSTEM_HAS_STAT_ST_BIRTHTIM) +#define BOOST_FILESYSTEM_STAT_ST_BIRTHTIME st_birthtim.tv_sec +#define BOOST_FILESYSTEM_STAT_ST_BIRTHTIMENSEC st_birthtim.tv_nsec +#elif defined(BOOST_FILESYSTEM_HAS_STAT_ST_BIRTHTIMESPEC) +#define BOOST_FILESYSTEM_STAT_ST_BIRTHTIME st_birthtimespec.tv_sec +#define BOOST_FILESYSTEM_STAT_ST_BIRTHTIMENSEC st_birthtimespec.tv_nsec +#elif defined(BOOST_FILESYSTEM_HAS_STAT_ST_BIRTHTIMENSEC) +#define BOOST_FILESYSTEM_STAT_ST_BIRTHTIME st_birthtime +#define BOOST_FILESYSTEM_STAT_ST_BIRTHTIMENSEC st_birthtimensec +#endif + +#include "posix_tools.hpp" + +#else // BOOST_WINDOWS_API + +#include <boost/winapi/dll.hpp> // get_proc_address, GetModuleHandleW +#include <cwchar> +#include <io.h> +#include <windows.h> +#include <winnt.h> +#if defined(__BORLANDC__) || defined(__MWERKS__) +#if defined(BOOST_BORLANDC) +using std::time_t; +#endif +#include <utime.h> +#else +#include <sys/utime.h> +#endif + +#include "windows_tools.hpp" + +#endif // BOOST_WINDOWS_API + +#include "atomic_tools.hpp" +#include "error_handling.hpp" +#include "private_config.hpp" + namespace fs = boost::filesystem; using boost::filesystem::path; using boost::filesystem::filesystem_error; using boost::filesystem::perms; using boost::system::error_code; -using boost::system::error_category; using boost::system::system_category; -using std::string; -using std::wstring; - -# ifdef BOOST_POSIX_API - -# include <sys/types.h> -# include <sys/stat.h> -# if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__ANDROID__) \ - && !defined(__VXWORKS__) -# include <sys/statvfs.h> -# define BOOST_STATVFS statvfs -# define BOOST_STATVFS_F_FRSIZE vfs.f_frsize -# else -# ifdef __OpenBSD__ -# include <sys/param.h> -# elif defined(__ANDROID__) -# include <sys/vfs.h> -# endif -# if !defined(__VXWORKS__) -# include <sys/mount.h> -# endif -# define BOOST_STATVFS statfs -# define BOOST_STATVFS_F_FRSIZE static_cast<boost::uintmax_t>(vfs.f_bsize) -# endif -# include <dirent.h> -# include <unistd.h> -# include <fcntl.h> -# include <utime.h> -# include "limits.h" - -# else // BOOST_WINDOW_API - -# if (defined(__MINGW32__) || defined(__CYGWIN__)) && !defined(WINVER) - // Versions of MinGW or Cygwin that support Filesystem V3 support at least WINVER 0x501. - // See MinGW's windef.h -# define WINVER 0x501 -# endif -# include <cwchar> -# include <io.h> -# include <windows.h> -# include <winnt.h> -# if !defined(_WIN32_WINNT) -# define _WIN32_WINNT 0x0500 -# endif -# if defined(__BORLANDC__) || defined(__MWERKS__) -# if defined(__BORLANDC__) - using std::time_t; -# endif -# include <utime.h> -# else -# include <sys/utime.h> -# endif - -// 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 +#if defined(BOOST_POSIX_API) -typedef struct _REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - /* Example of distinction between substitute and print names: - mklink /d ldrive c:\ - SubstituteName: c:\\??\ - PrintName: c:\ - */ - } 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) +// At least Mac OS X 10.6 and older doesn't support O_CLOEXEC +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0 +#define BOOST_FILESYSTEM_HAS_FDATASYNC #endif +#else // defined(BOOST_POSIX_API) + #ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE -#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) #endif -# ifndef FSCTL_GET_REPARSE_POINT -# define FSCTL_GET_REPARSE_POINT 0x900a8 -# endif - -# ifndef IO_REPARSE_TAG_SYMLINK -# define IO_REPARSE_TAG_SYMLINK (0xA000000CL) -# endif +#ifndef FSCTL_GET_REPARSE_POINT +#define FSCTL_GET_REPARSE_POINT 0x900a8 +#endif -inline std::wstring wgetenv(const wchar_t* name) -{ - // use vector since for C++03 basic_string is not required to be contiguous - std::vector<wchar_t> buf(::GetEnvironmentVariableW(name, NULL, 0)); +#ifndef SYMLINK_FLAG_RELATIVE +#define SYMLINK_FLAG_RELATIVE 1 +#endif - // C++03 vector does not have data() so use &buf[0] - return (buf.empty() - || ::GetEnvironmentVariableW(name, &buf[0], static_cast<DWORD>(buf.size())) == 0) - ? std::wstring() : std::wstring(&buf[0]); -} +// Fallback for MinGW/Cygwin +#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY +#define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1 +#endif -# endif // BOOST_WINDOWS_API +#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE +#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 +#endif -// 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 +#endif // defined(BOOST_POSIX_API) // POSIX/Windows macros ----------------------------------------------------// @@ -230,49 +216,33 @@ inline std::wstring wgetenv(const wchar_t* name) // order of arguments, and meaning of return were followed initially, but // found to be less clear and cause more coding errors.] -# if defined(BOOST_POSIX_API) - -typedef int err_t; - -// POSIX uses a 0 return to indicate success -# define BOOST_ERRNO errno -# define BOOST_SET_CURRENT_DIRECTORY(P)(::chdir(P)== 0) -# define BOOST_CREATE_DIRECTORY(P)(::mkdir(P, S_IRWXU|S_IRWXG|S_IRWXO)== 0) -# define BOOST_CREATE_HARD_LINK(F,T)(::link(T, F)== 0) -# define BOOST_CREATE_SYMBOLIC_LINK(F,T,Flag)(::symlink(T, F)== 0) -# define BOOST_REMOVE_DIRECTORY(P)(::rmdir(P)== 0) -# define BOOST_DELETE_FILE(P)(::unlink(P)== 0) -# define BOOST_COPY_DIRECTORY(F,T)(!(::stat(from.c_str(), &from_stat)!= 0\ - || ::mkdir(to.c_str(),from_stat.st_mode)!= 0)) -# define BOOST_COPY_FILE(F,T,FailIfExistsBool)copy_file_api(F, T, FailIfExistsBool) -# define BOOST_MOVE_FILE(OLD,NEW)(::rename(OLD, NEW)== 0) -# define BOOST_RESIZE_FILE(P,SZ)(::truncate(P, SZ)== 0) - -# define BOOST_ERROR_NOT_SUPPORTED ENOSYS -# define BOOST_ERROR_ALREADY_EXISTS EEXIST - -# else // BOOST_WINDOWS_API - -typedef DWORD err_t; - -// Windows uses a non-0 return to indicate success -# define BOOST_ERRNO ::GetLastError() -# define BOOST_SET_CURRENT_DIRECTORY(P)(::SetCurrentDirectoryW(P)!= 0) -# define BOOST_CREATE_DIRECTORY(P)(::CreateDirectoryW(P, 0)!= 0) -# define BOOST_CREATE_HARD_LINK(F,T)(create_hard_link_api(F, T, 0)!= 0) -# define BOOST_CREATE_SYMBOLIC_LINK(F,T,Flag)(create_symbolic_link_api(F, T, Flag)!= 0) -# define BOOST_REMOVE_DIRECTORY(P)(::RemoveDirectoryW(P)!= 0) -# define BOOST_DELETE_FILE(P)(::DeleteFileW(P)!= 0) -# define BOOST_COPY_DIRECTORY(F,T)(::CreateDirectoryExW(F, T, 0)!= 0) -# define BOOST_COPY_FILE(F,T,FailIfExistsBool)(::CopyFileW(F, T, FailIfExistsBool)!= 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_READ_SYMLINK(P,T) - -# define BOOST_ERROR_ALREADY_EXISTS ERROR_ALREADY_EXISTS -# define BOOST_ERROR_NOT_SUPPORTED ERROR_NOT_SUPPORTED - -# endif +#if defined(BOOST_POSIX_API) + +#define BOOST_SET_CURRENT_DIRECTORY(P) (::chdir(P) == 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_MOVE_FILE(OLD, NEW) (::MoveFileExW(OLD, NEW, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) +#define BOOST_RESIZE_FILE(P, SZ) (resize_file_impl(P, SZ) != 0) + +#endif + +namespace boost { +namespace filesystem { +namespace detail { + +#if defined(linux) || defined(__linux) || defined(__linux__) +//! Initializes fill_random implementation pointer. Implemented in unique_path.cpp. +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) //--------------------------------------------------------------------------------------// // // @@ -280,247 +250,707 @@ typedef DWORD err_t; // // //--------------------------------------------------------------------------------------// -namespace -{ - - fs::file_type query_file_type(const path& p, error_code* ec); - - boost::filesystem::directory_iterator end_dir_itr; - - // error handling helpers ----------------------------------------------------------// - - bool error(err_t error_num, error_code* ec, const char* message); - bool error(err_t error_num, const path& p, error_code* ec, const char* message); - bool error(err_t error_num, const path& p1, const path& p2, error_code* ec, - const char* message); - - const error_code ok; - - // error_num is value of errno on POSIX, error code (from ::GetLastError()) on Windows. - // Interface changed 30 Jan 15 to have caller supply error_num as ::SetLastError() - // values were apparently getting cleared before they could be retrieved by error(). - - bool error(err_t error_num, error_code* ec, const char* message) - { - if (!error_num) - { - if (ec != 0) ec->clear(); - } - else - { // error - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error(message, - error_code(error_num, system_category()))); - else - ec->assign(error_num, system_category()); - } - return error_num != 0; - } - - bool error(err_t error_num, const path& p, error_code* ec, const char* message) - { - if (!error_num) - { - if (ec != 0) ec->clear(); - } - else - { // error - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error(message, - p, error_code(error_num, system_category()))); - else - ec->assign(error_num, system_category()); - } - return error_num != 0; - } - - bool error(err_t error_num, const path& p1, const path& p2, error_code* ec, - const char* message) - { - if (!error_num) - { - if (ec != 0) ec->clear(); - } - else - { // error - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error(message, - p1, p2, error_code(error_num, system_category()))); - else - ec->assign(error_num, system_category()); - } - return error_num != 0; - } - - // general helpers -----------------------------------------------------------------// - - bool is_empty_directory(const path& p, error_code* ec) - { - return (ec != 0 ? fs::directory_iterator(p, *ec) : fs::directory_iterator(p)) - == end_dir_itr; - } - - bool not_found_error(int errval); // forward declaration - - // only called if directory exists - bool remove_directory(const path& p) // true if succeeds or not found - { - return BOOST_REMOVE_DIRECTORY(p.c_str()) - || not_found_error(BOOST_ERRNO); // mitigate possible file system race. See #11166 - } - - // only called if file exists - bool remove_file(const path& p) // true if succeeds or not found - { - return BOOST_DELETE_FILE(p.c_str()) - || not_found_error(BOOST_ERRNO); // mitigate possible file system race. See #11166 - } - - // called by remove and remove_all_aux - bool remove_file_or_directory(const path& p, fs::file_type type, error_code* ec) - // return true if file removed, false if not removed - { - if (type == fs::file_not_found) +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 or a regular file +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 +BOOST_CONSTEXPR_OR_CONST std::size_t small_path_size = 1024u; + +// Absolute maximum path length, in character code units, that we're willing to accept from various system calls. +// This value is arbitrary, it is supposed to be a hard limit to avoid memory exhaustion +// in some of the algorithms below in case of some corrupted or maliciously broken filesystem. +// A few examples of path size limits: +// - Windows: 32767 UTF-16 code units or 260 bytes for legacy multibyte APIs. +// - Linux: 4096 bytes +// - IRIX, HP-UX, Mac OS, QNX, FreeBSD, OpenBSD: 1024 bytes +// - GNU/Hurd: no hard limit +BOOST_CONSTEXPR_OR_CONST std::size_t absolute_path_max = 32u * 1024u; + +#endif // defined(BOOST_POSIX_API) + +// Maximum number of resolved symlinks before we register a loop +BOOST_CONSTEXPR_OR_CONST unsigned int symloop_max = +#if defined(SYMLOOP_MAX) + SYMLOOP_MAX < 40 ? 40 : SYMLOOP_MAX +#else + 40 +#endif +; + +// general helpers -----------------------------------------------------------------// + +bool is_empty_directory(path const& p, error_code* ec) +{ + fs::directory_iterator itr; + detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::none), ec); + return itr == fs::directory_iterator(); +} + +bool not_found_error(int errval) BOOST_NOEXCEPT; // forward declaration + +#ifdef BOOST_POSIX_API + +//--------------------------------------------------------------------------------------// +// // +// POSIX-specific helpers // +// // +//--------------------------------------------------------------------------------------// + +struct fd_wrapper +{ + int fd; + + fd_wrapper() BOOST_NOEXCEPT : fd(-1) {} + explicit fd_wrapper(int fd) BOOST_NOEXCEPT : fd(fd) {} + ~fd_wrapper() BOOST_NOEXCEPT + { + if (fd >= 0) + close_fd(fd); + } + BOOST_DELETED_FUNCTION(fd_wrapper(fd_wrapper const&)) + BOOST_DELETED_FUNCTION(fd_wrapper& operator=(fd_wrapper const&)) +}; + +inline bool not_found_error(int errval) BOOST_NOEXCEPT +{ + return errval == ENOENT || errval == ENOTDIR; +} + +#if defined(BOOST_FILESYSTEM_HAS_STATX) + +//! A wrapper for statx libc function. Disable MSAN since at least on clang 10 it doesn't +//! know which fields of struct statx are initialized by the syscall and misdetects errors. +BOOST_FILESYSTEM_NO_SANITIZE_MEMORY +BOOST_FORCEINLINE int invoke_statx(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* stx) +{ + return ::statx(dirfd, path, flags, mask, stx); +} + +#elif defined(BOOST_FILESYSTEM_HAS_STATX_SYSCALL) + +//! statx emulation through fstatat +int statx_fstatat(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* stx) +{ + struct ::stat st; + flags &= AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW; + int res = ::fstatat(dirfd, path, &st, flags); + if (BOOST_LIKELY(res == 0)) + { + std::memset(stx, 0, sizeof(*stx)); + stx->stx_mask = STATX_BASIC_STATS; + stx->stx_blksize = st.st_blksize; + stx->stx_nlink = st.st_nlink; + stx->stx_uid = st.st_uid; + stx->stx_gid = st.st_gid; + stx->stx_mode = st.st_mode; + stx->stx_ino = st.st_ino; + stx->stx_size = st.st_size; + stx->stx_blocks = st.st_blocks; + stx->stx_atime.tv_sec = st.st_atim.tv_sec; + stx->stx_atime.tv_nsec = st.st_atim.tv_nsec; + stx->stx_ctime.tv_sec = st.st_ctim.tv_sec; + stx->stx_ctime.tv_nsec = st.st_ctim.tv_nsec; + stx->stx_mtime.tv_sec = st.st_mtim.tv_sec; + stx->stx_mtime.tv_nsec = st.st_mtim.tv_nsec; + stx->stx_rdev_major = major(st.st_rdev); + stx->stx_rdev_minor = minor(st.st_rdev); + stx->stx_dev_major = major(st.st_dev); + stx->stx_dev_minor = minor(st.st_dev); + } + + return res; +} + +typedef int statx_t(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* stx); + +//! Pointer to the actual implementation of the statx implementation +statx_t* statx_ptr = &statx_fstatat; + +inline int invoke_statx(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* stx) BOOST_NOEXCEPT +{ + return filesystem::detail::atomic_load_relaxed(statx_ptr)(dirfd, path, flags, mask, stx); +} + +//! A wrapper for the statx syscall. Disable MSAN since at least on clang 10 it doesn't +//! know which fields of struct statx are initialized by the syscall and misdetects errors. +BOOST_FILESYSTEM_NO_SANITIZE_MEMORY +int statx_syscall(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* stx) +{ + int res = ::syscall(__NR_statx, dirfd, path, flags, mask, stx); + if (res < 0) + { + const int err = errno; + if (BOOST_UNLIKELY(err == ENOSYS)) + { + filesystem::detail::atomic_store_relaxed(statx_ptr, &statx_fstatat); + return statx_fstatat(dirfd, path, flags, mask, stx); + } + } + + return res; +} + +#endif // defined(BOOST_FILESYSTEM_HAS_STATX) + +#if defined(linux) || defined(__linux) || defined(__linux__) + +//! Initializes statx implementation pointer +inline void init_statx_impl(unsigned int major_ver, unsigned int minor_ver, unsigned int patch_ver) +{ +#if !defined(BOOST_FILESYSTEM_HAS_STATX) && defined(BOOST_FILESYSTEM_HAS_STATX_SYSCALL) + statx_t* stx = &statx_fstatat; + if (major_ver > 4u || (major_ver == 4u && minor_ver >= 11u)) + stx = &statx_syscall; + + filesystem::detail::atomic_store_relaxed(statx_ptr, stx); +#endif // !defined(BOOST_FILESYSTEM_HAS_STATX) && defined(BOOST_FILESYSTEM_HAS_STATX_SYSCALL) +} + +#endif // defined(linux) || defined(__linux) || defined(__linux__) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + +//! Returns \c true if the two \c statx structures refer to the same file +inline bool equivalent_stat(struct ::statx const& s1, struct ::statx const& s2) BOOST_NOEXCEPT +{ + return s1.stx_dev_major == s2.stx_dev_major && s1.stx_dev_minor == s2.stx_dev_minor && s1.stx_ino == s2.stx_ino; +} + +//! Returns file type/access mode from \c statx structure +inline mode_t get_mode(struct ::statx const& st) BOOST_NOEXCEPT +{ + return st.stx_mode; +} + +//! Returns file size from \c statx structure +inline uintmax_t get_size(struct ::statx const& st) BOOST_NOEXCEPT +{ + return st.stx_size; +} + +//! Returns optimal block size from \c statx structure +inline std::size_t get_blksize(struct ::statx const& st) BOOST_NOEXCEPT +{ + return st.stx_blksize; +} + +#else // defined(BOOST_FILESYSTEM_USE_STATX) + +//! Returns \c true if the two \c stat structures refer to the same file +inline bool equivalent_stat(struct ::stat const& s1, struct ::stat const& s2) BOOST_NOEXCEPT +{ + // According to the POSIX stat specs, "The st_ino and st_dev fields + // taken together uniquely identify the file within the system." + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino; +} + +//! Returns file type/access mode from \c stat structure +inline mode_t get_mode(struct ::stat const& st) BOOST_NOEXCEPT +{ + return st.st_mode; +} + +//! Returns file size from \c stat structure +inline uintmax_t get_size(struct ::stat const& st) BOOST_NOEXCEPT +{ + return st.st_size; +} + +//! Returns optimal block size from \c stat structure +inline std::size_t get_blksize(struct ::stat const& st) BOOST_NOEXCEPT +{ +#if defined(BOOST_FILESYSTEM_HAS_STAT_ST_BLKSIZE) + return st.st_blksize; +#else + return 4096u; // a suitable default used on most modern SSDs/HDDs +#endif +} + +#endif // defined(BOOST_FILESYSTEM_USE_STATX) + +//! Flushes buffered data and attributes written to the file to permanent storage +inline int full_sync(int fd) +{ + while (true) + { +#if defined(__APPLE__) && defined(__MACH__) && defined(F_FULLFSYNC) + // Mac OS does not flush data to physical storage with fsync() + int err = ::fcntl(fd, F_FULLFSYNC); +#else + int err = ::fsync(fd); +#endif + if (BOOST_UNLIKELY(err < 0)) + { + err = errno; + // POSIX says fsync can return EINTR (https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html). + // fcntl(F_FULLFSYNC) isn't documented to return EINTR, but it doesn't hurt to check. + if (err == EINTR) + continue; + + return err; + } + + break; + } + + return 0; +} + +//! Flushes buffered data written to the file to permanent storage +inline int data_sync(int fd) +{ +#if defined(BOOST_FILESYSTEM_HAS_FDATASYNC) && !(defined(__APPLE__) && defined(__MACH__) && defined(F_FULLFSYNC)) + while (true) + { + int err = ::fdatasync(fd); + if (BOOST_UNLIKELY(err != 0)) + { + err = errno; + // POSIX says fsync can return EINTR (https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html). + // It doesn't say so for fdatasync, but it is reasonable to expect it as well. + if (err == EINTR) + continue; + + return err; + } + + break; + } + + return 0; +#else + return full_sync(fd); +#endif +} + +// Min and max buffer sizes are selected to minimize the overhead from system calls. +// The values are picked based on coreutils cp(1) benchmarking data described here: +// https://github.com/coreutils/coreutils/blob/d1b0257077c0b0f0ee25087efd46270345d1dd1f/src/ioblksize.h#L23-L72 +BOOST_CONSTEXPR_OR_CONST uint_least32_t min_read_write_buf_size = 8u * 1024u; +BOOST_CONSTEXPR_OR_CONST uint_least32_t max_read_write_buf_size = 256u * 1024u; + +//! copy_file read/write loop implementation +int copy_file_data_read_write_impl(int infile, int outfile, char* buf, std::size_t buf_size) +{ +#if defined(BOOST_FILESYSTEM_HAS_POSIX_FADVISE) + ::posix_fadvise(infile, 0, 0, POSIX_FADV_SEQUENTIAL); +#endif + + // Don't use file size to limit the amount of data to copy since some filesystems, like procfs or sysfs, + // provide files with generated content and indicate that their size is zero or 4096. Just copy as much data + // as we can read from the input file. + while (true) + { + ssize_t sz_read = ::read(infile, buf, buf_size); + if (sz_read == 0) + break; + if (BOOST_UNLIKELY(sz_read < 0)) + { + int err = errno; + if (err == EINTR) + continue; + return err; + } + + // Allow for partial writes - see Advanced Unix Programming (2nd Ed.), + // Marc Rochkind, Addison-Wesley, 2004, page 94 + for (ssize_t sz_wrote = 0; sz_wrote < sz_read;) + { + ssize_t sz = ::write(outfile, buf + sz_wrote, static_cast< std::size_t >(sz_read - sz_wrote)); + if (BOOST_UNLIKELY(sz < 0)) + { + int err = errno; + if (err == EINTR) + continue; + return err; + } + + sz_wrote += sz; + } + } + + return 0; +} + +//! copy_file implementation that uses read/write loop (fallback using a stack buffer) +int copy_file_data_read_write_stack_buf(int infile, int outfile) +{ + char stack_buf[min_read_write_buf_size]; + return copy_file_data_read_write_impl(infile, outfile, stack_buf, sizeof(stack_buf)); +} + +//! copy_file implementation that uses read/write loop +int copy_file_data_read_write(int infile, int outfile, uintmax_t size, std::size_t blksize) +{ { - if (ec != 0) ec->clear(); - return false; + uintmax_t buf_sz = size; + // Prefer the buffer to be larger than the file size so that we don't have + // to perform an extra read if the file fits in the buffer exactly. + buf_sz += (buf_sz < ~static_cast< uintmax_t >(0u)); + if (buf_sz < blksize) + buf_sz = blksize; + if (buf_sz < min_read_write_buf_size) + buf_sz = min_read_write_buf_size; + if (buf_sz > max_read_write_buf_size) + buf_sz = max_read_write_buf_size; + const std::size_t buf_size = static_cast< std::size_t >(boost::core::bit_ceil(static_cast< uint_least32_t >(buf_sz))); + boost::scoped_array< char > buf(new (std::nothrow) char[buf_size]); + if (BOOST_LIKELY(!!buf.get())) + return copy_file_data_read_write_impl(infile, outfile, buf.get(), buf_size); } - if (type == fs::directory_file -# ifdef BOOST_WINDOWS_API - || type == fs::_detail_directory_symlink -# endif - ) + return copy_file_data_read_write_stack_buf(infile, outfile); +} + +typedef int copy_file_data_t(int infile, int outfile, uintmax_t size, std::size_t blksize); + +//! Pointer to the actual implementation of the copy_file_data implementation +copy_file_data_t* copy_file_data = ©_file_data_read_write; + +#if defined(BOOST_FILESYSTEM_USE_SENDFILE) + +//! copy_file implementation that uses sendfile loop. Requires sendfile to support file descriptors. +int copy_file_data_sendfile(int infile, int outfile, uintmax_t size, std::size_t blksize) +{ + // sendfile will not send more than this amount of data in one call + BOOST_CONSTEXPR_OR_CONST std::size_t max_batch_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (offset < size) + { + uintmax_t size_left = size - offset; + std::size_t size_to_copy = max_batch_size; + if (size_left < static_cast< uintmax_t >(max_batch_size)) + size_to_copy = static_cast< std::size_t >(size_left); + ssize_t sz = ::sendfile(outfile, infile, NULL, size_to_copy); + if (BOOST_UNLIKELY(sz < 0)) + { + int err = errno; + if (err == EINTR) + continue; + + if (offset == 0u) + { + // sendfile may fail with EINVAL if the underlying filesystem does not support it + if (err == EINVAL) + { + fallback_to_read_write: + return copy_file_data_read_write(infile, outfile, size, blksize); + } + + if (err == ENOSYS) + { + filesystem::detail::atomic_store_relaxed(copy_file_data, ©_file_data_read_write); + goto fallback_to_read_write; + } + } + + return err; + } + + offset += sz; + } + + return 0; +} + +#endif // defined(BOOST_FILESYSTEM_USE_SENDFILE) + +#if defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) + +//! copy_file implementation that uses copy_file_range loop. Requires copy_file_range to support cross-filesystem copying. +int copy_file_data_copy_file_range(int infile, int outfile, uintmax_t size, std::size_t blksize) +{ + // Although copy_file_range does not document any particular upper limit of one transfer, still use some upper bound to guarantee + // that size_t is not overflown in case if off_t is larger and the file size does not fit in size_t. + BOOST_CONSTEXPR_OR_CONST std::size_t max_batch_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (offset < size) + { + uintmax_t size_left = size - offset; + std::size_t size_to_copy = max_batch_size; + if (size_left < static_cast< uintmax_t >(max_batch_size)) + size_to_copy = static_cast< std::size_t >(size_left); + // Note: Use syscall directly to avoid depending on libc version. copy_file_range is added in glibc 2.27. + // uClibc-ng does not have copy_file_range as of the time of this writing (the latest uClibc-ng release is 1.0.33). + loff_t sz = ::syscall(__NR_copy_file_range, infile, (loff_t*)NULL, outfile, (loff_t*)NULL, size_to_copy, (unsigned int)0u); + if (BOOST_UNLIKELY(sz < 0)) + { + int err = errno; + if (err == EINTR) + continue; + + if (offset == 0u) + { + // copy_file_range may fail with EINVAL if the underlying filesystem does not support it. + // In some RHEL/CentOS 7.7-7.8 kernel versions, copy_file_range on NFSv4 is also known to return EOPNOTSUPP + // if the remote server does not support COPY, despite that it is not a documented error code. + // See https://patchwork.kernel.org/project/linux-nfs/patch/[email protected]/ + // and https://bugzilla.redhat.com/show_bug.cgi?id=1783554. + if (err == EINVAL || err == EOPNOTSUPP) + { +#if !defined(BOOST_FILESYSTEM_USE_SENDFILE) + fallback_to_read_write: +#endif + return copy_file_data_read_write(infile, outfile, size, blksize); + } + + if (err == EXDEV) + { +#if defined(BOOST_FILESYSTEM_USE_SENDFILE) + fallback_to_sendfile: + return copy_file_data_sendfile(infile, outfile, size, blksize); +#else + goto fallback_to_read_write; +#endif + } + + if (err == ENOSYS) + { +#if defined(BOOST_FILESYSTEM_USE_SENDFILE) + filesystem::detail::atomic_store_relaxed(copy_file_data, ©_file_data_sendfile); + goto fallback_to_sendfile; +#else + filesystem::detail::atomic_store_relaxed(copy_file_data, ©_file_data_read_write); + goto fallback_to_read_write; +#endif + } + } + + return err; + } + + offset += sz; + } + + return 0; +} + +#endif // defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) + +#if defined(BOOST_FILESYSTEM_USE_SENDFILE) || defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) + +//! copy_file_data wrapper that tests if a read/write loop must be used for a given filesystem +template< copy_file_data_t* CopyFileData > +int check_fs_type(int infile, int outfile, uintmax_t size, std::size_t blksize) +{ + { + // Some filesystems have regular files with generated content. Such files have arbitrary size, including zero, + // but have actual content. Linux system calls sendfile or copy_file_range will not copy contents of such files, + // so we must use a read/write loop to handle them. + // https://lore.kernel.org/linux-fsdevel/[email protected]/T/ + struct statfs sfs; + while (true) + { + int err = ::fstatfs(infile, &sfs); + if (BOOST_UNLIKELY(err < 0)) + { + err = errno; + if (err == EINTR) + continue; + + goto fallback_to_read_write; + } + + break; + } + + if (BOOST_UNLIKELY(sfs.f_type == PROC_SUPER_MAGIC || + sfs.f_type == SYSFS_MAGIC || + sfs.f_type == TRACEFS_MAGIC || + sfs.f_type == DEBUGFS_MAGIC)) + { + fallback_to_read_write: + return copy_file_data_read_write(infile, outfile, size, blksize); + } + } + + return CopyFileData(infile, outfile, size, blksize); +} + +#endif // defined(BOOST_FILESYSTEM_USE_SENDFILE) || defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) + +#if defined(linux) || defined(__linux) || defined(__linux__) + +//! Initializes copy_file_data implementation pointer +inline void init_copy_file_data_impl(unsigned int major_ver, unsigned int minor_ver, unsigned int patch_ver) +{ +#if defined(BOOST_FILESYSTEM_USE_SENDFILE) || defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) + copy_file_data_t* cfd = ©_file_data_read_write; + +#if defined(BOOST_FILESYSTEM_USE_SENDFILE) + // sendfile started accepting file descriptors as the target in Linux 2.6.33 + if (major_ver > 2u || (major_ver == 2u && (minor_ver > 6u || (minor_ver == 6u && patch_ver >= 33u)))) + cfd = &check_fs_type< ©_file_data_sendfile >; +#endif + +#if defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) + // Although copy_file_range appeared in Linux 4.5, it did not support cross-filesystem copying until 5.3. + // copy_file_data_copy_file_range will fallback to copy_file_data_sendfile if copy_file_range returns EXDEV. + if (major_ver > 4u || (major_ver == 4u && minor_ver >= 5u)) + cfd = &check_fs_type< ©_file_data_copy_file_range >; +#endif + + filesystem::detail::atomic_store_relaxed(copy_file_data, cfd); +#endif // defined(BOOST_FILESYSTEM_USE_SENDFILE) || defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE) +} + +#endif // defined(linux) || defined(__linux) || defined(__linux__) + +#if defined(linux) || defined(__linux) || defined(__linux__) + +struct syscall_initializer +{ + syscall_initializer() { - if (error(!remove_directory(p) ? BOOST_ERRNO : 0, p, ec, - "boost::filesystem::remove")) - return false; + struct ::utsname system_info; + if (BOOST_UNLIKELY(::uname(&system_info) < 0)) + return; + + unsigned int major_ver = 0u, minor_ver = 0u, patch_ver = 0u; + int count = std::sscanf(system_info.release, "%u.%u.%u", &major_ver, &minor_ver, &patch_ver); + if (BOOST_UNLIKELY(count < 3)) + return; + + init_statx_impl(major_ver, minor_ver, patch_ver); + init_copy_file_data_impl(major_ver, minor_ver, patch_ver); + init_fill_random_impl(major_ver, minor_ver, patch_ver); } +}; + +BOOST_FILESYSTEM_INIT_PRIORITY(BOOST_FILESYSTEM_FUNC_PTR_INIT_PRIORITY) BOOST_ATTRIBUTE_UNUSED BOOST_FILESYSTEM_ATTRIBUTE_RETAIN +const syscall_initializer syscall_init; + +#endif // defined(linux) || defined(__linux) || defined(__linux__) + +//! remove() implementation +inline bool remove_impl(path const& p, fs::file_type type, error_code* ec) +{ + if (type == fs::file_not_found) + return false; + + int res; + if (type == fs::directory_file) + res = ::rmdir(p.c_str()); else + res = ::unlink(p.c_str()); + + if (res != 0) { - if (error(!remove_file(p) ? BOOST_ERRNO : 0, p, ec, - "boost::filesystem::remove")) - return false; + int err = errno; + if (BOOST_UNLIKELY(!not_found_error(err))) + emit_error(err, p, ec, "boost::filesystem::remove"); + + return false; } + return true; - } - - boost::uintmax_t remove_all_aux(const path& p, fs::file_type type, - error_code* ec) - { - boost::uintmax_t count = 1; - if (type == fs::directory_file) // but not a directory symlink - { - fs::directory_iterator itr; - if (ec != 0) - { - itr = fs::directory_iterator(p, *ec); - if (*ec) - return count; - } - else - itr = fs::directory_iterator(p); - for (; itr != end_dir_itr; ++itr) - { - fs::file_type tmp_type = query_file_type(itr->path(), ec); - if (ec != 0 && *ec) - return count; - count += remove_all_aux(itr->path(), tmp_type, ec); - if (ec != 0 && *ec) - return count; - } - } - remove_file_or_directory(p, type, ec); - return count; - } +} -#ifdef BOOST_POSIX_API +//! remove() implementation +inline bool remove_impl(path const& p, error_code* ec) +{ + // Since POSIX remove() is specified to work with either files or directories, in a + // perfect world it could just be called. But some important real-world operating + // systems (Windows, Mac OS, for example) don't implement the POSIX spec. So + // we have to distinguish between files and directories and call corresponding APIs + // to remove them. -//--------------------------------------------------------------------------------------// -// // -// POSIX-specific helpers // -// // -//--------------------------------------------------------------------------------------// + error_code local_ec; + fs::file_type type = fs::detail::symlink_status(p, &local_ec).type(); + if (BOOST_UNLIKELY(type == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove", p, local_ec)); - const char dot = '.'; + *ec = local_ec; + return false; + } - bool not_found_error(int errval) - { - return errval == ENOENT || errval == ENOTDIR; - } - - bool // true if ok - copy_file_api(const std::string& from_p, - const std::string& to_p, bool fail_if_exists) - { - const std::size_t buf_sz = 32768; - boost::scoped_array<char> buf(new char [buf_sz]); - int infile=-1, outfile=-1; // -1 means not open - - // bug fixed: code previously did a stat()on the from_file first, but that - // introduced a gratuitous race condition; the stat()is now done after the open() - - if ((infile = ::open(from_p.c_str(), O_RDONLY))< 0) - { return false; } - - struct stat from_stat; - if (::stat(from_p.c_str(), &from_stat)!= 0) - { - ::close(infile); - return false; - } - - int oflag = O_CREAT | O_WRONLY | O_TRUNC; - if (fail_if_exists) - oflag |= O_EXCL; - if ((outfile = ::open(to_p.c_str(), oflag, from_stat.st_mode))< 0) - { - int open_errno = errno; - BOOST_ASSERT(infile >= 0); - ::close(infile); - errno = open_errno; - return false; - } - - ssize_t sz, sz_read=1, sz_write; - while (sz_read > 0 - && (sz_read = ::read(infile, buf.get(), buf_sz)) > 0) - { - // Allow for partial writes - see Advanced Unix Programming (2nd Ed.), - // Marc Rochkind, Addison-Wesley, 2004, page 94 - sz_write = 0; - do - { - BOOST_ASSERT(sz_read - sz_write > 0); // #1 - // ticket 4438 claimed possible infinite loop if write returns 0. My analysis - // is that POSIX specifies 0 return only if 3rd arg is 0, and that will never - // happen due to loop entry and coninuation conditions. BOOST_ASSERT #1 above - // and #2 below added to verify that analysis. - if ((sz = ::write(outfile, buf.get() + sz_write, - sz_read - sz_write)) < 0) - { - sz_read = sz; // cause read loop termination - break; // and error reported after closes - } - BOOST_ASSERT(sz > 0); // #2 - sz_write += sz; - } while (sz_write < sz_read); - } - - if (::close(infile)< 0) - sz_read = -1; - if (::close(outfile)< 0) - sz_read = -1; - - return sz_read >= 0; - } - - inline fs::file_type query_file_type(const path& p, error_code* ec) - { - return fs::detail::symlink_status(p, ec).type(); - } - -# else + return fs::detail::remove_impl(p, type, ec); +} + +//! remove_all() implementation +uintmax_t remove_all_impl(path const& p, error_code* ec) +{ + error_code dit_create_ec; + for (unsigned int attempt = 0u; attempt < remove_all_directory_replaced_retry_count; ++attempt) + { + fs::file_type type; + { + error_code local_ec; + type = fs::detail::symlink_status(p, &local_ec).type(); + + 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); + } + } + + uintmax_t count = 0u; + if (type == fs::directory_file) // but not a directory symlink + { + fs::directory_iterator itr; + fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::_detail_no_follow), &dit_create_ec); + if (BOOST_UNLIKELY(!!dit_create_ec)) + { + if (dit_create_ec == error_code(ENOTDIR, system_category())) + continue; + +#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) + // If open(2) with O_NOFOLLOW fails with ELOOP, this means that either the path contains a loop + // of symbolic links, or the last element of the path is a symbolic link. Given that lstat(2) above + // did not fail, most likely it is the latter case. I.e. between the lstat above and this open call + // the filesystem was modified so that the path no longer refers to a directory file (as opposed to a symlink). + if (dit_create_ec == error_code(ELOOP, system_category())) + continue; +#endif // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, dit_create_ec)); + + *ec = dit_create_ec; + return static_cast< uintmax_t >(-1); + } + + const fs::directory_iterator end_dit; + while (itr != end_dit) + { + count += fs::detail::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 += fs::detail::remove_impl(p, type, ec); + if (ec && *ec) + return static_cast< uintmax_t >(-1); + + return count; + } + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all: path cannot be opened as a directory", p, dit_create_ec)); + + *ec = dit_create_ec; + return static_cast< uintmax_t >(-1); +} + +#else // defined(BOOST_POSIX_API) //--------------------------------------------------------------------------------------// // // @@ -528,674 +958,1934 @@ namespace // // //--------------------------------------------------------------------------------------// - const std::size_t buf_size=128; - - const wchar_t dot = L'.'; - - bool not_found_error(int errval) - { - return errval == ERROR_FILE_NOT_FOUND - || errval == ERROR_PATH_NOT_FOUND - || errval == ERROR_INVALID_NAME // "tools/jam/src/:sys:stat.h", "//foo" - || errval == ERROR_INVALID_DRIVE // USB card reader with no card inserted - || errval == ERROR_NOT_READY // CD/DVD drive with no disc inserted - || errval == ERROR_INVALID_PARAMETER // ":sys:stat.h" - || errval == ERROR_BAD_PATHNAME // "//nosuch" on Win64 - || errval == ERROR_BAD_NETPATH; // "//nosuch" on Win32 - } - - static bool equal_extension( wchar_t const* p, wchar_t const (&x1)[ 5 ], wchar_t const (&x2)[ 5 ] ) - { - return - (p[0] == x1[0] || p[0] == x2[0]) && - (p[1] == x1[1] || p[1] == x2[1]) && - (p[2] == x1[2] || p[2] == x2[2]) && - (p[3] == x1[3] || p[3] == x2[3]) && - p[4] == 0; - } - - perms make_permissions(const path& p, DWORD attr) - { - perms prms = fs::owner_read | fs::group_read | fs::others_read; - if ((attr & FILE_ATTRIBUTE_READONLY) == 0) - prms |= fs::owner_write | fs::group_write | fs::others_write; - path ext = p.extension(); - wchar_t const* q = ext.c_str(); - if (equal_extension(q, L".exe", L".EXE") - || equal_extension(q, L".com", L".COM") - || equal_extension(q, L".bat", L".BAT") - || equal_extension(q, L".cmd", L".CMD")) - prms |= fs::owner_exe | fs::group_exe | fs::others_exe; - return prms; - } - - // these constants come from inspecting some Microsoft sample code - std::time_t to_time_t(const FILETIME & ft) - { - __int64 t = (static_cast<__int64>(ft.dwHighDateTime)<< 32) - + ft.dwLowDateTime; -# if !defined(BOOST_MSVC) || BOOST_MSVC > 1300 // > VC++ 7.0 - t -= 116444736000000000LL; -# else - t -= 116444736000000000; -# endif - t /= 10000000; - return static_cast<std::time_t>(t); - } - - void to_FILETIME(std::time_t t, FILETIME & ft) - { - __int64 temp = t; - temp *= 10000000; -# if !defined(BOOST_MSVC) || BOOST_MSVC > 1300 // > VC++ 7.0 - temp += 116444736000000000LL; -# else - temp += 116444736000000000; -# endif - ft.dwLowDateTime = static_cast<DWORD>(temp); - 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 - { - HANDLE handle; - handle_wrapper(HANDLE h) - : handle(h){} - ~handle_wrapper() - { - if (handle != INVALID_HANDLE_VALUE) - ::CloseHandle(handle); - } - }; - - HANDLE create_file_handle(const path& 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); - } - - bool is_reparse_point_a_symlink(const path& p) - { - handle_wrapper h(create_file_handle(p, FILE_READ_EA, - 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; +//! FILE_BASIC_INFO definition from Windows SDK +struct file_basic_info +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + DWORD FileAttributes; +}; + +//! FILE_DISPOSITION_INFO definition from Windows SDK +struct file_disposition_info +{ + BOOLEAN DeleteFile; +}; + +//! FILE_DISPOSITION_INFO_EX definition from Windows SDK +struct file_disposition_info_ex +{ + DWORD Flags; +}; + +#ifndef FILE_DISPOSITION_FLAG_DELETE +#define FILE_DISPOSITION_FLAG_DELETE 0x00000001 +#endif +// Available since Windows 10 1709 +#ifndef FILE_DISPOSITION_FLAG_POSIX_SEMANTICS +#define FILE_DISPOSITION_FLAG_POSIX_SEMANTICS 0x00000002 +#endif +// Available since Windows 10 1809 +#ifndef FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE +#define FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE 0x00000010 +#endif + +// 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{<UUID>}\" + * 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 kernel32.dll functions that may or may not be present +// must be accessed through pointers + +typedef BOOL (WINAPI CreateHardLinkW_t)( + /*__in*/ LPCWSTR lpFileName, + /*__in*/ LPCWSTR lpExistingFileName, + /*__reserved*/ LPSECURITY_ATTRIBUTES lpSecurityAttributes); + +CreateHardLinkW_t* create_hard_link_api = NULL; + +typedef BOOLEAN (WINAPI CreateSymbolicLinkW_t)( + /*__in*/ LPCWSTR lpSymlinkFileName, + /*__in*/ LPCWSTR lpTargetFileName, + /*__in*/ DWORD dwFlags); + +CreateSymbolicLinkW_t* create_symbolic_link_api = NULL; + +//! SetFileInformationByHandle signature. Available since Windows Vista. +typedef BOOL (WINAPI SetFileInformationByHandle_t)( + /*_In_*/ HANDLE hFile, + /*_In_*/ file_info_by_handle_class FileInformationClass, // the actual type is FILE_INFO_BY_HANDLE_CLASS enum + /*_In_reads_bytes_(dwBufferSize)*/ LPVOID lpFileInformation, + /*_In_*/ DWORD dwBufferSize); + +SetFileInformationByHandle_t* set_file_information_by_handle_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 { + +//! remove() implementation type +enum remove_impl_type +{ + remove_nt5, //!< Use Windows XP API + remove_disp, //!< Use FILE_DISPOSITION_INFO (Windows Vista and later) + remove_disp_ex_flag_posix_semantics, //!< Use FILE_DISPOSITION_INFO_EX with FILE_DISPOSITION_FLAG_POSIX_SEMANTICS + remove_disp_ex_flag_ignore_readonly //!< Use FILE_DISPOSITION_INFO_EX with FILE_DISPOSITION_FLAG_POSIX_SEMANTICS | FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE +}; + +remove_impl_type g_remove_impl_type = remove_nt5; + +//! Initializes WinAPI function pointers +BOOST_FILESYSTEM_INIT_FUNC init_winapi_func_ptrs() +{ + boost::winapi::HMODULE_ h = boost::winapi::GetModuleHandleW(L"kernel32.dll"); + if (BOOST_LIKELY(!!h)) + { + GetFileInformationByHandleEx_t* get_file_information_by_handle_ex = (GetFileInformationByHandleEx_t*)boost::winapi::get_proc_address(h, "GetFileInformationByHandleEx"); + filesystem::detail::atomic_store_relaxed(get_file_information_by_handle_ex_api, get_file_information_by_handle_ex); + SetFileInformationByHandle_t* set_file_information_by_handle = (SetFileInformationByHandle_t*)boost::winapi::get_proc_address(h, "SetFileInformationByHandle"); + filesystem::detail::atomic_store_relaxed(set_file_information_by_handle_api, set_file_information_by_handle); + 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 (get_file_information_by_handle_ex && set_file_information_by_handle) + { + // Enable the most advanced implementation based on GetFileInformationByHandleEx/SetFileInformationByHandle. + // If certain flags are not supported by the OS, the remove() implementation will downgrade accordingly. + filesystem::detail::atomic_store_relaxed(g_remove_impl_type, remove_disp_ex_flag_ignore_readonly); + } + } + +#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; +} + +#if defined(_MSC_VER) + +#if _MSC_VER >= 1400 + +#pragma section(".CRT$XCL", long, read) +__declspec(allocate(".CRT$XCL")) BOOST_ATTRIBUTE_UNUSED BOOST_FILESYSTEM_ATTRIBUTE_RETAIN +extern const init_func_ptr_t p_init_winapi_func_ptrs = &init_winapi_func_ptrs; + +#else // _MSC_VER >= 1400 + +#if (_MSC_VER >= 1300) // 1300 == VC++ 7.0 +#pragma data_seg(push, old_seg) +#endif +#pragma data_seg(".CRT$XCL") +BOOST_ATTRIBUTE_UNUSED BOOST_FILESYSTEM_ATTRIBUTE_RETAIN +extern const init_func_ptr_t p_init_winapi_func_ptrs = &init_winapi_func_ptrs; +#pragma data_seg() +#if (_MSC_VER >= 1300) // 1300 == VC++ 7.0 +#pragma data_seg(pop, old_seg) +#endif + +#endif // _MSC_VER >= 1400 + +#if defined(BOOST_FILESYSTEM_NO_ATTRIBUTE_RETAIN) +//! Makes sure the global initializer pointers are referenced and not removed by linker +struct globals_retainer +{ + const init_func_ptr_t* volatile m_p_init_winapi_func_ptrs; + + globals_retainer() { m_p_init_winapi_func_ptrs = &p_init_winapi_func_ptrs; } +}; +BOOST_ATTRIBUTE_UNUSED +const globals_retainer g_globals_retainer; +#endif // defined(BOOST_FILESYSTEM_NO_ATTRIBUTE_RETAIN) + +#else // defined(_MSC_VER) + +//! Invokes WinAPI function pointers initialization +struct winapi_func_ptrs_initializer +{ + winapi_func_ptrs_initializer() { init_winapi_func_ptrs(); } +}; + +BOOST_FILESYSTEM_INIT_PRIORITY(BOOST_FILESYSTEM_FUNC_PTR_INIT_PRIORITY) BOOST_ATTRIBUTE_UNUSED BOOST_FILESYSTEM_ATTRIBUTE_RETAIN +const winapi_func_ptrs_initializer winapi_func_ptrs_init; + +#endif // defined(_MSC_VER) + + +// Windows CE has no environment variables +#if !defined(UNDER_CE) +inline std::wstring wgetenv(const wchar_t* name) +{ + // use a separate buffer since C++03 basic_string is not required to be contiguous + const DWORD size = ::GetEnvironmentVariableW(name, NULL, 0); + if (size > 0) + { + boost::scoped_array< wchar_t > buf(new wchar_t[size]); + if (BOOST_LIKELY(::GetEnvironmentVariableW(name, buf.get(), size) > 0)) + return std::wstring(buf.get()); + } + + return std::wstring(); +} +#endif // !defined(UNDER_CE) + +inline bool not_found_error(int errval) BOOST_NOEXCEPT +{ + return errval == ERROR_FILE_NOT_FOUND || errval == ERROR_PATH_NOT_FOUND || errval == ERROR_INVALID_NAME // "tools/jam/src/:sys:stat.h", "//foo" + || errval == ERROR_INVALID_DRIVE // USB card reader with no card inserted + || errval == ERROR_NOT_READY // CD/DVD drive with no disc inserted + || errval == ERROR_INVALID_PARAMETER // ":sys:stat.h" + || errval == ERROR_BAD_PATHNAME // "//no-host" on Win64 + || errval == ERROR_BAD_NETPATH // "//no-host" on Win32 + || errval == ERROR_BAD_NET_NAME; // "//no-host/no-share" on Win10 x64 +} + +// these constants come from inspecting some Microsoft sample code +inline std::time_t to_time_t(FILETIME const& ft) BOOST_NOEXCEPT +{ + uint64_t t = (static_cast< uint64_t >(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + t -= 116444736000000000ull; + t /= 10000000u; + return static_cast< std::time_t >(t); +} + +inline void to_FILETIME(std::time_t t, FILETIME& ft) BOOST_NOEXCEPT +{ + uint64_t temp = t; + temp *= 10000000u; + temp += 116444736000000000ull; + ft.dwLowDateTime = static_cast< DWORD >(temp); + ft.dwHighDateTime = static_cast< DWORD >(temp >> 32); +} + +} // unnamed namespace + +bool is_reparse_point_a_symlink_ioctl(HANDLE h) +{ + boost::scoped_ptr< reparse_data_buffer_with_storage > buf(new reparse_data_buffer_with_storage); - boost::scoped_array<char> buf(new char [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]); - // Query the reparse data - DWORD dwRetLen; - BOOL result = ::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buf.get(), - MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwRetLen, NULL); - if (!result) return false; - - return reinterpret_cast<const REPARSE_DATA_BUFFER*>(buf.get())->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". - || reinterpret_cast<const REPARSE_DATA_BUFFER*>(buf.get())->ReparseTag - == IO_REPARSE_TAG_MOUNT_POINT; // aka "directory junction" or "junction" - } - - inline std::size_t get_full_path_name( - const path& src, std::size_t len, wchar_t* buf, wchar_t** p) - { - return static_cast<std::size_t>( - ::GetFullPathNameW(src.c_str(), static_cast<DWORD>(len), buf, p)); - } - - fs::file_status process_status_failure(const path& p, error_code* ec) - { - int errval(::GetLastError()); - if (ec != 0) // always report errval, even though some - ec->assign(errval, system_category()); // errval values are not status_errors + DWORD dwRetLen = 0u; + BOOL result = ::DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, buf.get(), sizeof(*buf), &dwRetLen, NULL); + if (BOOST_UNLIKELY(!result)) + return false; + + return is_reparse_point_tag_a_symlink(buf->rdb.ReparseTag); +} + +namespace { + +inline bool is_reparse_point_a_symlink(path const& p) +{ + 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; + + 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 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; + + return is_reparse_point_tag_a_symlink(info.ReparseTag); + } + + return is_reparse_point_a_symlink_ioctl(h.handle); +} + +inline std::size_t get_full_path_name(path const& src, std::size_t len, wchar_t* buf, wchar_t** p) +{ + return static_cast< std::size_t >(::GetFullPathNameW(src.c_str(), static_cast< DWORD >(len), buf, p)); +} + +inline fs::file_status process_status_failure(path const& p, error_code* ec) +{ + DWORD errval = ::GetLastError(); + if (ec) // always report errval, even though some + ec->assign(errval, system_category()); // errval values are not status_errors if (not_found_error(errval)) { - return fs::file_status(fs::file_not_found, fs::no_perms); + return fs::file_status(fs::file_not_found, fs::no_perms); } else if (errval == ERROR_SHARING_VIOLATION) { - return fs::file_status(fs::type_unknown); + return fs::file_status(fs::type_unknown); } - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", - p, error_code(errval, system_category()))); + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", p, error_code(errval, system_category()))); + return fs::file_status(fs::status_error); - } +} - // differs from symlink_status() in that directory symlinks are reported as - // _detail_directory_symlink, as required on Windows by remove() and its helpers. - fs::file_type query_file_type(const path& p, error_code* ec) - { - DWORD attr(::GetFileAttributesW(p.c_str())); - if (attr == 0xFFFFFFFF) +//! remove() implementation for Windows XP and older +bool remove_nt5_impl(path const& p, DWORD attrs, error_code* ec) +{ + const bool is_directory = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; + const bool is_read_only = (attrs & FILE_ATTRIBUTE_READONLY) != 0; + if (is_read_only) { - return process_status_failure(p, ec).type(); + // RemoveDirectoryW and DeleteFileW do not allow to remove a read-only file, so we have to drop the attribute + DWORD new_attrs = attrs & ~FILE_ATTRIBUTE_READONLY; + BOOL res = ::SetFileAttributesW(p.c_str(), new_attrs); + if (BOOST_UNLIKELY(!res)) + { + DWORD err = ::GetLastError(); + if (!not_found_error(err)) + emit_error(err, p, ec, "boost::filesystem::remove"); + + return false; + } } - if (ec != 0) ec->clear(); + BOOL res; + if (!is_directory) + { + // DeleteFileW works for file symlinks by removing the symlink, not the target. + res = ::DeleteFileW(p.c_str()); + } + else + { + // RemoveDirectoryW works for symlinks and junctions by removing the symlink, not the target, + // even if the target directory is not empty. + // Note that unlike opening the directory with FILE_FLAG_DELETE_ON_CLOSE flag, RemoveDirectoryW + // will fail if the directory is not empty. + res = ::RemoveDirectoryW(p.c_str()); + } + + if (BOOST_UNLIKELY(!res)) + { + DWORD err = ::GetLastError(); + if (!not_found_error(err)) + { + if (is_read_only) + { + // Try to restore the read-only attribute + ::SetFileAttributesW(p.c_str(), attrs); + } + + emit_error(err, p, ec, "boost::filesystem::remove"); + } + + return false; + } + + return true; +} + +//! remove() implementation for Windows Vista and newer +bool remove_nt6_impl(path const& p, remove_impl_type impl, error_code* ec) +{ + handle_wrapper h(create_file_handle(p, DELETE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)); + DWORD err; + if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE)) + { + err = ::GetLastError(); + + return_error: + if (!not_found_error(err)) + emit_error(err, p, ec, "boost::filesystem::remove"); + + return false; + } - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) + GetFileInformationByHandleEx_t* get_file_information_by_handle_ex = filesystem::detail::atomic_load_relaxed(get_file_information_by_handle_ex_api); + SetFileInformationByHandle_t* set_file_information_by_handle = filesystem::detail::atomic_load_relaxed(set_file_information_by_handle_api); + switch (impl) { - if (is_reparse_point_a_symlink(p)) - return (attr & FILE_ATTRIBUTE_DIRECTORY) - ? fs::_detail_directory_symlink - : fs::symlink_file; - return fs::reparse_file; + case remove_disp_ex_flag_ignore_readonly: + { + file_disposition_info_ex info; + info.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS | FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE; + BOOL res = set_file_information_by_handle(h.handle, file_disposition_info_ex_class, &info, sizeof(info)); + if (BOOST_LIKELY(!!res)) + break; + + err = ::GetLastError(); + if (BOOST_UNLIKELY(err == ERROR_INVALID_PARAMETER || err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED)) + { + // Downgrade to the older implementation + impl = remove_disp_ex_flag_posix_semantics; + filesystem::detail::atomic_store_relaxed(g_remove_impl_type, impl); + } + else + { + goto return_error; + } + } + BOOST_FALLTHROUGH; + + case remove_disp_ex_flag_posix_semantics: + { + file_disposition_info_ex info; + info.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS; + BOOL res = set_file_information_by_handle(h.handle, file_disposition_info_ex_class, &info, sizeof(info)); + if (BOOST_LIKELY(!!res)) + break; + + err = ::GetLastError(); + if (err == ERROR_ACCESS_DENIED) + { + // Check if the file is read-only and reset the attribute + file_basic_info basic_info; + res = get_file_information_by_handle_ex(h.handle, file_basic_info_class, &basic_info, sizeof(basic_info)); + if (BOOST_UNLIKELY(!res || (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0)) + goto return_error; // return ERROR_ACCESS_DENIED + + basic_info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + + res = set_file_information_by_handle(h.handle, file_basic_info_class, &basic_info, sizeof(basic_info)); + if (BOOST_UNLIKELY(!res)) + { + err = ::GetLastError(); + goto return_error; + } + + // Try to set the flag again + res = set_file_information_by_handle(h.handle, file_disposition_info_ex_class, &info, sizeof(info)); + if (BOOST_LIKELY(!!res)) + break; + + err = ::GetLastError(); + + // Try to restore the read-only flag + basic_info.FileAttributes |= FILE_ATTRIBUTE_READONLY; + set_file_information_by_handle(h.handle, file_basic_info_class, &basic_info, sizeof(basic_info)); + + goto return_error; + } + else if (BOOST_UNLIKELY(err == ERROR_INVALID_PARAMETER || err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED)) + { + // Downgrade to the older implementation + impl = remove_disp; + filesystem::detail::atomic_store_relaxed(g_remove_impl_type, impl); + } + else + { + goto return_error; + } + } + BOOST_FALLTHROUGH; + + default: + { + file_disposition_info info; + info.DeleteFile = true; + BOOL res = set_file_information_by_handle(h.handle, file_disposition_info_class, &info, sizeof(info)); + if (BOOST_LIKELY(!!res)) + break; + + err = ::GetLastError(); + if (err == ERROR_ACCESS_DENIED) + { + // Check if the file is read-only and reset the attribute + file_basic_info basic_info; + res = get_file_information_by_handle_ex(h.handle, file_basic_info_class, &basic_info, sizeof(basic_info)); + if (BOOST_UNLIKELY(!res || (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0)) + goto return_error; // return ERROR_ACCESS_DENIED + + basic_info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + + res = set_file_information_by_handle(h.handle, file_basic_info_class, &basic_info, sizeof(basic_info)); + if (BOOST_UNLIKELY(!res)) + { + err = ::GetLastError(); + goto return_error; + } + + // Try to set the flag again + res = set_file_information_by_handle(h.handle, file_disposition_info_class, &info, sizeof(info)); + if (BOOST_LIKELY(!!res)) + break; + + err = ::GetLastError(); + + // Try to restore the read-only flag + basic_info.FileAttributes |= FILE_ATTRIBUTE_READONLY; + set_file_information_by_handle(h.handle, file_basic_info_class, &basic_info, sizeof(basic_info)); + } + + goto return_error; + } } - return (attr & FILE_ATTRIBUTE_DIRECTORY) - ? fs::directory_file - : fs::regular_file; - } + return true; +} - BOOL resize_file_api(const wchar_t* p, boost::uintmax_t size) - { - handle_wrapper h(CreateFileW(p, GENERIC_WRITE, 0, 0, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, 0)); +//! remove() implementation +inline bool remove_impl(path const& p, error_code* ec) +{ + remove_impl_type impl = filesystem::detail::atomic_load_relaxed(g_remove_impl_type); + if (BOOST_LIKELY(impl != remove_nt5)) + { + return remove_nt6_impl(p, impl, ec); + } + else + { + const DWORD attrs = ::GetFileAttributesW(p.c_str()); + if (BOOST_UNLIKELY(attrs == INVALID_FILE_ATTRIBUTES)) + { + DWORD err = ::GetLastError(); + if (!not_found_error(err)) + emit_error(err, p, ec, "boost::filesystem::remove"); + + return false; + } + + return remove_nt5_impl(p, attrs, ec); + } +} + +//! remove_all() implementation +uintmax_t remove_all_impl(path const& p, error_code* ec) +{ + remove_impl_type impl = filesystem::detail::atomic_load_relaxed(g_remove_impl_type); + error_code dit_create_ec; + for (unsigned int attempt = 0u; attempt < remove_all_directory_replaced_retry_count; ++attempt) + { + const DWORD attrs = ::GetFileAttributesW(p.c_str()); + if (BOOST_UNLIKELY(attrs == INVALID_FILE_ATTRIBUTES)) + { + DWORD err = ::GetLastError(); + if (not_found_error(err)) + return 0u; + + emit_error(err, p, ec, "boost::filesystem::remove_all"); + return static_cast< uintmax_t >(-1); + } + + // Recurse into directories, but not into junctions or directory symlinks + const bool recurse = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && (attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0; + uintmax_t count = 0u; + if (recurse) + { + fs::directory_iterator itr; + fs::detail::directory_iterator_construct(itr, p, static_cast< unsigned int >(directory_options::_detail_no_follow), &dit_create_ec); + if (BOOST_UNLIKELY(!!dit_create_ec)) + { + if (dit_create_ec == make_error_condition(system::errc::not_a_directory) || + dit_create_ec == make_error_condition(system::errc::too_many_symbolic_link_levels)) + { + continue; + } + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all", p, dit_create_ec)); + + *ec = dit_create_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); + } + } + + bool removed; + if (BOOST_LIKELY(impl != remove_nt5)) + removed = remove_nt6_impl(p, impl, ec); + else + removed = remove_nt5_impl(p, attrs, ec); + if (ec && *ec) + return static_cast< uintmax_t >(-1); + + count += removed; + return count; + } + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::remove_all: path cannot be opened as a directory", p, dit_create_ec)); + + *ec = dit_create_ec; + return static_cast< uintmax_t >(-1); +} + +inline BOOL resize_file_impl(const wchar_t* p, uintmax_t size) +{ + 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); - } + return h.handle != INVALID_HANDLE_VALUE && ::SetFilePointerEx(h.handle, sz, 0, FILE_BEGIN) && ::SetEndOfFile(h.handle); +} - // Windows kernel32.dll functions that may or may not be present - // must be accessed through pointers +//! Converts NT path to a Win32 path +inline path convert_nt_path_to_win32_path(const wchar_t* nt_path, std::size_t size) +{ + // https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html + // https://stackoverflow.com/questions/23041983/path-prefixes-and + // + // NT paths can be used to identify practically any named objects, devices, files, local and remote shares, etc. + // The path starts with a leading backslash and consists of one or more path elements separated with backslashes. + // The set of characters allowed in NT path elements is significantly larger than that of Win32 paths - basically, + // any character except the backslash is allowed. Path elements are case-insensitive. + // + // NT paths that start with the "\??\" prefix are used to indicate the current user's session namespace. The prefix + // indicates to the NT object manager to lookup the object relative to "\Sessions\0\DosDevices\[Logon Authentication ID]". + // + // There is also a special "\Global??\" prefix that refers to the system logon. User's session directory shadows + // the system logon directory, so that when the referenced object is not found in the user's namespace, + // system logon is looked up instead. + // + // There is a symlink "Global" in the user's session namespace that refers to the global namespace, so "\??\Global" + // effectively resolves to "\Global??". This allows Win32 applications to directly refer to the system objects, + // even if shadowed by the current user's logon object. + // + // NT paths can be used to reference not only local filesystems, but also devices and remote shares identifiable via + // UNC paths. For this, there is a special "UNC" device (which is a symlink to "\Device\Mup") in the system logon + // namespace, so "\??\UNC\host\share" (or "\??\Global\UNC\host\share", or "\Global??\UNC\host\share") is equivalent + // to "\\host\share". + // + // NT paths are not universally accepted by Win32 applications and APIs. For example, Far supports paths starting + // with "\??\" and "\??\Global\" but not with "\Global??\". As of Win10 21H1, File Explorer, cmd.exe and PowerShell + // don't support any of these. Given this, and that NT paths have a different set of allowed characters from Win32 paths, + // we should normally avoid exposing NT paths to users that expect Win32 paths. + // + // In Boost.Filesystem we only deal with NT paths that come from reparse points, such as symlinks and mount points, + // including directory junctions. It was observed that reparse points created by junction.exe and mklink use the "\??\" + // prefix for directory junctions and absolute symlink and unqualified relative path for relative symlinks. + // Absolute paths are using drive letters for mounted drives (e.g. "\??\C:\directory"), although it is possible + // to create a junction to an directory using a different way of identifying the filesystem (e.g. + // "\??\Volume{00000000-0000-0000-0000-000000000000}\directory"). + // mklink does not support creating junctions pointing to a UNC path. junction.exe does create a junction that + // uses a seemingly invalid syntax like "\??\\\host\share", i.e. it basically does not expect an UNC path. It is not known + // if reparse points that refer to a UNC path are considered valid. + // There are reparse points created as mount points for local and remote filsystems (for example, a cloud storage mounted + // in the local filesystem). Such mount points have the form of "\??\Volume{00000000-0000-0000-0000-000000000000}\", + // "\??\Harddisk0Partition1\" or "\??\HarddiskVolume1\". + // Reparse points that refer directly to a global namespace (through "\??\Global\" or "\Global??\" prefixes) or + // devices (e.g. "\Device\HarddiskVolume1") have not been observed so far. + + path win32_path; + std::size_t pos = 0u; + bool global_namespace = false; + + // Check for the "\??\" prefix + if (size >= 4u && + nt_path[0] == path::preferred_separator && + nt_path[1] == questionmark && + nt_path[2] == questionmark && + nt_path[3] == path::preferred_separator) + { + pos = 4u; + + // Check "Global" + if ((size - pos) >= 6u && + (nt_path[pos] == L'G' || nt_path[pos] == L'g') && + (nt_path[pos + 1] == L'l' || nt_path[pos + 1] == L'L') && + (nt_path[pos + 2] == L'o' || nt_path[pos + 2] == L'O') && + (nt_path[pos + 3] == L'b' || nt_path[pos + 3] == L'B') && + (nt_path[pos + 4] == L'a' || nt_path[pos + 4] == L'A') && + (nt_path[pos + 5] == L'l' || nt_path[pos + 5] == L'L')) + { + if ((size - pos) == 6u) + { + pos += 6u; + global_namespace = true; + } + else if (detail::is_directory_separator(nt_path[pos + 6u])) + { + pos += 7u; + global_namespace = true; + } + } + } + // Check for the "\Global??\" prefix + else if (size >= 10u && + nt_path[0] == path::preferred_separator && + (nt_path[1] == L'G' || nt_path[1] == L'g') && + (nt_path[2] == L'l' || nt_path[2] == L'L') && + (nt_path[3] == L'o' || nt_path[3] == L'O') && + (nt_path[4] == L'b' || nt_path[4] == L'B') && + (nt_path[5] == L'a' || nt_path[5] == L'A') && + (nt_path[6] == L'l' || nt_path[6] == L'L') && + nt_path[7] == questionmark && + nt_path[8] == questionmark && + nt_path[9] == path::preferred_separator) + { + pos = 10u; + global_namespace = true; + } - typedef BOOL (WINAPI *PtrCreateHardLinkW)( - /*__in*/ LPCWSTR lpFileName, - /*__in*/ LPCWSTR lpExistingFileName, - /*__reserved*/ LPSECURITY_ATTRIBUTES lpSecurityAttributes - ); + if (pos > 0u) + { + if ((size - pos) >= 2u && + ( + // Check if the following is a drive letter + ( + detail::is_letter(nt_path[pos]) && nt_path[pos + 1u] == colon && + ((size - pos) == 2u || detail::is_directory_separator(nt_path[pos + 2u])) + ) || + // Check for an "incorrect" syntax for UNC path junction points + ( + detail::is_directory_separator(nt_path[pos]) && detail::is_directory_separator(nt_path[pos + 1u]) && + ((size - pos) == 2u || !detail::is_directory_separator(nt_path[pos + 2u])) + ) + )) + { + // Strip the NT path prefix + goto done; + } - PtrCreateHardLinkW create_hard_link_api = PtrCreateHardLinkW( - ::GetProcAddress( - ::GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); + static const wchar_t win32_path_prefix[4u] = { path::preferred_separator, path::preferred_separator, questionmark, path::preferred_separator }; - typedef BOOLEAN (WINAPI *PtrCreateSymbolicLinkW)( - /*__in*/ LPCWSTR lpSymlinkFileName, - /*__in*/ LPCWSTR lpTargetFileName, - /*__in*/ DWORD dwFlags - ); + // Check for a UNC path + if ((size - pos) >= 4u && + (nt_path[pos] == L'U' || nt_path[pos] == L'u') && + (nt_path[pos + 1] == L'N' || nt_path[pos + 1] == L'n') && + (nt_path[pos + 2] == L'C' || nt_path[pos + 2] == L'c') && + nt_path[pos + 3] == path::preferred_separator) + { + win32_path.assign(win32_path_prefix, win32_path_prefix + 2); + pos += 4u; + goto done; + } - PtrCreateSymbolicLinkW create_symbolic_link_api = PtrCreateSymbolicLinkW( - ::GetProcAddress( - ::GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); + // This is some other NT path, possibly a volume mount point. Replace the NT prefix with a Win32 filesystem prefix "\\?\". + win32_path.assign(win32_path_prefix, win32_path_prefix + 4); + if (global_namespace) + { + static const wchar_t win32_path_global_prefix[7u] = { L'G', L'l', L'o', L'b', L'a', L'l', path::preferred_separator }; + win32_path.concat(win32_path_global_prefix, win32_path_global_prefix + 7); + } + } -#endif +done: + win32_path.concat(nt_path + pos, nt_path + size); + return win32_path; +} -//#ifdef BOOST_WINDOWS_API -// -// -// inline bool get_free_disk_space(const std::wstring& ph, -// PULARGE_INTEGER avail, PULARGE_INTEGER total, PULARGE_INTEGER free) -// { return ::GetDiskFreeSpaceExW(ph.c_str(), avail, total, free)!= 0; } -// -//#endif +#endif // defined(BOOST_POSIX_API) } // unnamed namespace +} // namespace detail //--------------------------------------------------------------------------------------// // // // operations functions declared in operations.hpp // -// in alphabetic order // // // //--------------------------------------------------------------------------------------// -namespace boost -{ -namespace filesystem -{ - - BOOST_FILESYSTEM_DECL - path absolute(const path& p, const path& base) - { -// if ( p.empty() || p.is_absolute() ) -// return p; -// // recursively calling absolute is sub-optimal, but is simple -// path abs_base(base.is_absolute() ? base : absolute(base)); -//# ifdef BOOST_WINDOWS_API -// if (p.has_root_directory()) -// return abs_base.root_name() / p; -// // !p.has_root_directory -// if (p.has_root_name()) -// return p.root_name() -// / abs_base.root_directory() / abs_base.relative_path() / p.relative_path(); -// // !p.has_root_name() -//# endif -// return abs_base / p; +namespace detail { - // recursively calling absolute is sub-optimal, but is sure and simple - path abs_base(base.is_absolute() ? base : absolute(base)); +BOOST_FILESYSTEM_DECL bool possible_large_file_size_support() +{ +#ifdef BOOST_POSIX_API + typedef struct stat struct_stat; + return sizeof(struct_stat().st_size) > 4; +#else + return true; +#endif +} - // store expensive to compute values that are needed multiple times - path p_root_name (p.root_name()); - path base_root_name (abs_base.root_name()); - path p_root_directory (p.root_directory()); +BOOST_FILESYSTEM_DECL +path absolute(path const& p, path const& base, system::error_code* ec) +{ + if (ec) + ec->clear(); - if (p.empty()) - return abs_base; + if (p.is_absolute()) + return p; - if (!p_root_name.empty()) // p.has_root_name() + // recursively calling absolute is sub-optimal, but is sure and simple + path abs_base = base; + if (!base.is_absolute()) { - if (p_root_directory.empty()) // !p.has_root_directory() - return p_root_name / abs_base.root_directory() - / abs_base.relative_path() / p.relative_path(); - // p is absolute, so fall through to return p at end of block + if (ec) + { + abs_base = absolute(base, *ec); + if (*ec) + return path(); + } + else + { + abs_base = absolute(base); + } } - else if (!p_root_directory.empty()) // p.has_root_directory() + if (p.empty()) + return abs_base; + + path res; + if (p.has_root_name()) + res = p.root_name(); + else + res = abs_base.root_name(); + + if (p.has_root_directory()) { -# ifdef BOOST_POSIX_API - // POSIX can have root name it it is a network path - if (base_root_name.empty()) // !abs_base.has_root_name() - return p; -# endif - return base_root_name / p; + res.concat(p.root_directory()); } - else { - return abs_base / p; + res.concat(abs_base.root_directory()); + res /= abs_base.relative_path(); } - return p; // p.is_absolute() is true - } + path p_relative_path(p.relative_path()); + if (!p_relative_path.empty()) + res /= p_relative_path; + + return res; +} -namespace detail +BOOST_FILESYSTEM_DECL +path canonical(path const& p, path const& base, system::error_code* ec) { - BOOST_FILESYSTEM_DECL bool possible_large_file_size_support() - { -# ifdef BOOST_POSIX_API - struct stat lcl_stat; - return sizeof(lcl_stat.st_size)> 4; -# else - return true; -# endif - } + if (ec) + ec->clear(); - BOOST_FILESYSTEM_DECL - path canonical(const path& p, const path& base, system::error_code* ec) - { - path source (p.is_absolute() ? p : absolute(p, base)); - path root(source.root_path()); - path result; + path source(p); + if (!p.is_absolute()) + { + source = detail::absolute(p, base, ec); + if (ec && *ec) + { + return_empty_path: + return path(); + } + } system::error_code local_ec; - file_status stat (status(source, local_ec)); + file_status st(detail::status(source, &local_ec)); - if (stat.type() == fs::file_not_found) + if (st.type() == fs::file_not_found) { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error( - "boost::filesystem::canonical", source, - error_code(system::errc::no_such_file_or_directory, system::generic_category()))); - ec->assign(system::errc::no_such_file_or_directory, system::generic_category()); - return result; + local_ec = system::errc::make_error_code(system::errc::no_such_file_or_directory); + goto fail_local_ec; } else if (local_ec) { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error( - "boost::filesystem::canonical", source, local_ec)); - *ec = local_ec; - return result; + fail_local_ec: + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::canonical", source, local_ec)); + + *ec = local_ec; + goto return_empty_path; } - bool scan (true); - while (scan) + path root(source.root_path()); + path const& dot_p = dot_path(); + path const& dot_dot_p = dot_dot_path(); + unsigned int symlinks_allowed = symloop_max; + path result; + while (true) { - scan = false; - result.clear(); - for (path::iterator itr = source.begin(); itr != source.end(); ++itr) - { - if (*itr == dot_path()) - continue; - if (*itr == dot_dot_path()) + for (path::iterator itr(source.begin()), end(source.end()); itr != end; ++itr) { - if (result != root) - result.remove_filename(); - continue; + if (*itr == dot_p) + continue; + if (*itr == dot_dot_p) + { + if (result != root) + result.remove_filename(); + continue; + } + + if (itr->size() == 1u && detail::is_directory_separator(itr->native()[0])) + { + // Convert generic separator returned by the iterator for the root directory to + // the preferred separator. This is important on Windows, as in some cases, + // like paths for network shares and cloud storage mount points GetFileAttributesW + // will return "file not found" if the path contains forward slashes. + result += path::preferred_separator; + // We don't need to check for a symlink after adding a separator. + continue; + } + + result /= *itr; + + // If we don't have an absolute path yet then don't check symlink status. + // This avoids checking "C:" which is "the current directory on drive C" + // and hence not what we want to check/resolve here. + if (!result.is_absolute()) + continue; + + st = detail::symlink_status(result, ec); + if (ec && *ec) + goto return_empty_path; + + if (is_symlink(st)) + { + if (symlinks_allowed == 0) + { + local_ec = system::errc::make_error_code(system::errc::too_many_symbolic_link_levels); + goto fail_local_ec; + } + + --symlinks_allowed; + + path link(detail::read_symlink(result, ec)); + if (ec && *ec) + goto return_empty_path; + result.remove_filename(); + + if (link.is_absolute()) + { + for (++itr; itr != end; ++itr) + { + if (*itr != dot_p) + link /= *itr; + } + source = link; + root = source.root_path(); + } + else // link is relative + { + link.remove_trailing_separator(); + if (link == dot_p) + continue; + + path new_source(result); + new_source /= link; + for (++itr; itr != end; ++itr) + { + if (*itr != dot_p) + new_source /= *itr; + } + source = new_source; + } + + // symlink causes scan to be restarted + goto restart_scan; + } } - result /= *itr; + break; - bool is_sym (is_symlink(detail::symlink_status(result, ec))); - if (ec && *ec) - return path(); + restart_scan: + result.clear(); + } - if (is_sym) - { - path link(detail::read_symlink(result, ec)); - if (ec && *ec) - return path(); - result.remove_filename(); - - if (link.is_absolute()) - { - for (++itr; itr != source.end(); ++itr) - link /= *itr; - source = link; - } - else // link is relative - { - path new_source(result); - new_source /= link; - for (++itr; itr != source.end(); ++itr) - new_source /= *itr; - source = new_source; - } - scan = true; // symlink causes scan to be restarted - break; - } - } - } - if (ec != 0) - ec->clear(); BOOST_ASSERT_MSG(result.is_absolute(), "canonical() implementation error; please report"); return result; - } +} - BOOST_FILESYSTEM_DECL - void copy(const path& from, const path& to, system::error_code* ec) - { - file_status s(detail::symlink_status(from, ec)); - if (ec != 0 && *ec) return; +BOOST_FILESYSTEM_DECL +void copy(path const& from, path const& to, unsigned int options, system::error_code* ec) +{ + BOOST_ASSERT((((options & static_cast< unsigned int >(copy_options::overwrite_existing)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u)) <= 1); - if(is_symlink(s)) + BOOST_ASSERT((((options & static_cast< unsigned int >(copy_options::copy_symlinks)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::skip_symlinks)) != 0u)) <= 1); + + BOOST_ASSERT((((options & static_cast< unsigned int >(copy_options::directories_only)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::create_symlinks)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::create_hard_links)) != 0u)) <= 1); + + file_status from_stat; + if ((options & (static_cast< unsigned int >(copy_options::copy_symlinks) | + static_cast< unsigned int >(copy_options::skip_symlinks) | + static_cast< unsigned int >(copy_options::create_symlinks))) != 0u) { - detail::copy_symlink(from, to, ec); + from_stat = detail::symlink_status(from, ec); } - else if(is_directory(s)) + else { - detail::copy_directory(from, to, ec); + from_stat = detail::status(from, ec); } - else if(is_regular_file(s)) + + if (ec && *ec) + return; + + if (!exists(from_stat)) { - detail::copy_file(from, to, detail::fail_if_exists, ec); + emit_error(BOOST_ERROR_FILE_NOT_FOUND, from, to, ec, "boost::filesystem::copy"); + return; } - else + + if (is_symlink(from_stat)) { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::copy", - from, to, error_code(BOOST_ERROR_NOT_SUPPORTED, system_category()))); - ec->assign(BOOST_ERROR_NOT_SUPPORTED, system_category()); - } - } - - BOOST_FILESYSTEM_DECL - void copy_directory(const path& from, const path& to, system::error_code* ec) - { -# ifdef BOOST_POSIX_API - struct stat from_stat; -# endif - error(!BOOST_COPY_DIRECTORY(from.c_str(), to.c_str()) ? BOOST_ERRNO : 0, - from, to, ec, "boost::filesystem::copy_directory"); - } - - BOOST_FILESYSTEM_DECL - void copy_file(const path& from, const path& to, copy_option option, error_code* ec) - { - error(!BOOST_COPY_FILE(from.c_str(), to.c_str(), - option == fail_if_exists) ? BOOST_ERRNO : 0, - from, to, ec, "boost::filesystem::copy_file"); - } - - BOOST_FILESYSTEM_DECL - void copy_symlink(const path& existing_symlink, const path& new_symlink, - system::error_code* ec) - { -# if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600 - error(BOOST_ERROR_NOT_SUPPORTED, new_symlink, existing_symlink, ec, - "boost::filesystem::copy_symlink"); - -# else // modern Windows or BOOST_POSIX_API - path p(read_symlink(existing_symlink, ec)); - if (ec != 0 && *ec) return; - create_symlink(p, new_symlink, ec); + if ((options & static_cast< unsigned int >(copy_options::skip_symlinks)) != 0u) + return; -# endif - } - - BOOST_FILESYSTEM_DECL - bool create_directories(const path& p, system::error_code* ec) - { - if (p.empty()) - { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error( - "boost::filesystem::create_directories", p, - system::errc::make_error_code(system::errc::invalid_argument))); - else - ec->assign(system::errc::invalid_argument, system::generic_category()); - return false; - } - - if (p.filename_is_dot() || p.filename_is_dot_dot()) - return create_directories(p.parent_path(), ec); - - error_code local_ec; - file_status p_status = status(p, local_ec); + if ((options & static_cast< unsigned int >(copy_options::copy_symlinks)) == 0u) + goto fail; - if (p_status.type() == directory_file) + detail::copy_symlink(from, to, ec); + } + else if (is_regular_file(from_stat)) { - if (ec != 0) + if ((options & static_cast< unsigned int >(copy_options::directories_only)) != 0u) + return; + + if ((options & static_cast< unsigned int >(copy_options::create_symlinks)) != 0u) + { + const path* pfrom = &from; + path relative_from; + if (!from.is_absolute()) + { + // Try to generate a relative path from the target location to the original file + path cur_dir = detail::current_path(ec); + if (ec && *ec) + return; + path abs_from = detail::absolute(from.parent_path(), cur_dir, ec); + if (ec && *ec) + return; + path abs_to = to.parent_path(); + if (!abs_to.is_absolute()) + { + abs_to = detail::absolute(abs_to, cur_dir, ec); + if (ec && *ec) + return; + } + relative_from = detail::relative(abs_from, abs_to, ec); + if (ec && *ec) + return; + if (relative_from != dot_path()) + relative_from /= from.filename(); + else + relative_from = from.filename(); + pfrom = &relative_from; + } + detail::create_symlink(*pfrom, to, ec); + return; + } + + if ((options & static_cast< unsigned int >(copy_options::create_hard_links)) != 0u) + { + detail::create_hard_link(from, to, ec); + return; + } + + error_code local_ec; + file_status to_stat; + if ((options & (static_cast< unsigned int >(copy_options::skip_symlinks) | + static_cast< unsigned int >(copy_options::create_symlinks))) != 0u) + { + to_stat = detail::symlink_status(to, &local_ec); + } + else + { + to_stat = detail::status(to, &local_ec); + } + + // Note: local_ec may be set by (symlink_)status() even in some non-fatal situations, e.g. when the file does not exist. + // OTOH, when it returns status_error, then a real error have happened and it must have set local_ec. + if (to_stat.type() == fs::status_error) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::copy", from, to, local_ec)); + *ec = local_ec; + return; + } + + if (is_directory(to_stat)) + detail::copy_file(from, to / from.filename(), options, ec); + else + detail::copy_file(from, to, options, ec); + } + else if (is_directory(from_stat)) + { + error_code local_ec; + if ((options & static_cast< unsigned int >(copy_options::create_symlinks)) != 0u) + { + local_ec = make_error_code(system::errc::is_a_directory); + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::copy", from, to, local_ec)); + *ec = local_ec; + return; + } + + file_status to_stat; + if ((options & (static_cast< unsigned int >(copy_options::skip_symlinks) | + static_cast< unsigned int >(copy_options::create_symlinks))) != 0u) + { + to_stat = detail::symlink_status(to, &local_ec); + } + else + { + to_stat = detail::status(to, &local_ec); + } + + // Note: ec may be set by (symlink_)status() even in some non-fatal situations, e.g. when the file does not exist. + // OTOH, when it returns status_error, then a real error have happened and it must have set local_ec. + if (to_stat.type() == fs::status_error) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::copy", from, to, local_ec)); + *ec = local_ec; + return; + } + + if (!exists(to_stat)) + { + detail::create_directory(to, &from, ec); + if (ec && *ec) + return; + } + + if ((options & static_cast< unsigned int >(copy_options::recursive)) != 0u || options == 0u) + { + fs::directory_iterator itr; + detail::directory_iterator_construct(itr, from, static_cast< unsigned int >(directory_options::none), ec); + if (ec && *ec) + return; + + const fs::directory_iterator end_dit; + while (itr != end_dit) + { + path const& p = itr->path(); + // Set _detail_recursing flag so that we don't recurse more than for one level deeper into the directory if options are copy_options::none + detail::copy(p, to / p.filename(), options | static_cast< unsigned int >(copy_options::_detail_recursing), ec); + if (ec && *ec) + return; + + detail::directory_iterator_increment(itr, ec); + if (ec && *ec) + return; + } + } + } + else + { + fail: + emit_error(BOOST_ERROR_NOT_SUPPORTED, from, to, ec, "boost::filesystem::copy"); + } +} + +BOOST_FILESYSTEM_DECL +bool copy_file(path const& from, path const& to, unsigned int options, error_code* ec) +{ + BOOST_ASSERT((((options & static_cast< unsigned int >(copy_options::overwrite_existing)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) + + ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u)) <= 1); + + if (ec) ec->clear(); - return false; + +#if defined(BOOST_POSIX_API) + + int err = 0; + + // Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors + fd_wrapper infile, outfile; + + while (true) + { + infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC); + if (BOOST_UNLIKELY(infile.fd < 0)) + { + err = errno; + if (err == EINTR) + continue; + + fail: + emit_error(err, from, to, ec, "boost::filesystem::copy_file"); + return false; + } + + break; + } + +#if defined(BOOST_FILESYSTEM_USE_STATX) + unsigned int statx_data_mask = STATX_TYPE | STATX_MODE | STATX_INO | STATX_SIZE; + if ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u) + statx_data_mask |= STATX_MTIME; + + struct ::statx from_stat; + if (BOOST_UNLIKELY(invoke_statx(infile.fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, statx_data_mask, &from_stat) < 0)) + { + fail_errno: + err = errno; + goto fail; + } + + if (BOOST_UNLIKELY((from_stat.stx_mask & statx_data_mask) != statx_data_mask)) + { + err = ENOSYS; + goto fail; + } +#else + struct ::stat from_stat; + if (BOOST_UNLIKELY(::fstat(infile.fd, &from_stat) != 0)) + { + fail_errno: + err = errno; + goto fail; + } +#endif + + const mode_t from_mode = get_mode(from_stat); + if (BOOST_UNLIKELY(!S_ISREG(from_mode))) + { + err = ENOSYS; + goto fail; + } + + mode_t to_mode = from_mode; +#if !defined(__wasm) + // Enable writing for the newly created files. Having write permission set is important e.g. for NFS, + // which checks the file permission on the server, even if the client's file descriptor supports writing. + to_mode |= S_IWUSR; +#endif + int oflag = O_WRONLY | O_CLOEXEC; + + if ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u) + { + // Try opening the existing file without truncation to test the modification time later + while (true) + { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) + { + err = errno; + if (err == EINTR) + continue; + + if (err == ENOENT) + goto create_outfile; + + goto fail; + } + + break; + } + } + else + { + create_outfile: + oflag |= O_CREAT | O_TRUNC; + if (((options & static_cast< unsigned int >(copy_options::overwrite_existing)) == 0u || + (options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) && + (options & static_cast< unsigned int >(copy_options::update_existing)) == 0u) + { + oflag |= O_EXCL; + } + + while (true) + { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) + { + err = errno; + if (err == EINTR) + continue; + + if (err == EEXIST && (options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) + return false; + + goto fail; + } + + break; + } + } + +#if defined(BOOST_FILESYSTEM_USE_STATX) + statx_data_mask = STATX_TYPE | STATX_MODE | STATX_INO; + if ((oflag & O_TRUNC) == 0) + { + // O_TRUNC is not set if copy_options::update_existing is set and an existing file was opened. + statx_data_mask |= STATX_MTIME; + } + + struct ::statx to_stat; + if (BOOST_UNLIKELY(invoke_statx(outfile.fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, statx_data_mask, &to_stat) < 0)) + goto fail_errno; + + if (BOOST_UNLIKELY((to_stat.stx_mask & statx_data_mask) != statx_data_mask)) + { + err = ENOSYS; + goto fail; + } +#else + struct ::stat to_stat; + if (BOOST_UNLIKELY(::fstat(outfile.fd, &to_stat) != 0)) + goto fail_errno; +#endif + + to_mode = get_mode(to_stat); + if (BOOST_UNLIKELY(!S_ISREG(to_mode))) + { + err = ENOSYS; + goto fail; + } + + if (BOOST_UNLIKELY(detail::equivalent_stat(from_stat, to_stat))) + { + err = EEXIST; + goto fail; + } + + if ((oflag & O_TRUNC) == 0) + { + // O_TRUNC is not set if copy_options::update_existing is set and an existing file was opened. + // We need to check the last write times. +#if defined(BOOST_FILESYSTEM_USE_STATX) + if (from_stat.stx_mtime.tv_sec < to_stat.stx_mtime.tv_sec || (from_stat.stx_mtime.tv_sec == to_stat.stx_mtime.tv_sec && from_stat.stx_mtime.tv_nsec <= to_stat.stx_mtime.tv_nsec)) + return false; +#elif defined(BOOST_FILESYSTEM_STAT_ST_MTIMENSEC) + // Modify time is available with nanosecond precision. + if (from_stat.st_mtime < to_stat.st_mtime || (from_stat.st_mtime == to_stat.st_mtime && from_stat.BOOST_FILESYSTEM_STAT_ST_MTIMENSEC <= to_stat.BOOST_FILESYSTEM_STAT_ST_MTIMENSEC)) + return false; +#else + if (from_stat.st_mtime <= to_stat.st_mtime) + return false; +#endif + + if (BOOST_UNLIKELY(::ftruncate(outfile.fd, 0) != 0)) + goto fail_errno; + } + + // Note: Use block size of the target file since it is most important for writing performance. + err = filesystem::detail::atomic_load_relaxed(filesystem::detail::copy_file_data)(infile.fd, outfile.fd, get_size(from_stat), get_blksize(to_stat)); + if (BOOST_UNLIKELY(err != 0)) + goto fail; // err already contains the error code + +#if !defined(__wasm) + // If we created a new file with an explicitly added S_IWUSR permission, + // we may need to update its mode bits to match the source file. + if (to_mode != from_mode) + { + if (BOOST_UNLIKELY(::fchmod(outfile.fd, from_mode) != 0)) + goto fail_errno; + } +#endif + + if ((options & (static_cast< unsigned int >(copy_options::synchronize_data) | static_cast< unsigned int >(copy_options::synchronize))) != 0u) + { + if ((options & static_cast< unsigned int >(copy_options::synchronize)) != 0u) + err = full_sync(outfile.fd); + else + err = data_sync(outfile.fd); + + if (BOOST_UNLIKELY(err != 0)) + goto fail; + } + + // We have to explicitly close the output file descriptor in order to handle a possible error returned from it. The error may indicate + // a failure of a prior write operation. + err = close_fd(outfile.fd); + outfile.fd = -1; + if (BOOST_UNLIKELY(err < 0)) + { + err = errno; + // EINPROGRESS is an allowed error code in future POSIX revisions, according to https://www.austingroupbugs.net/view.php?id=529#c1200. + if (err != EINTR && err != EINPROGRESS) + goto fail; + } + + return true; + +#else // defined(BOOST_POSIX_API) + + DWORD copy_flags = 0u; + if ((options & static_cast< unsigned int >(copy_options::overwrite_existing)) == 0u || + (options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) + { + copy_flags |= COPY_FILE_FAIL_IF_EXISTS; } - path parent = p.parent_path(); - BOOST_ASSERT_MSG(parent != p, "internal error: p == p.parent_path()"); - if (!parent.empty()) + if ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u) { - // determine if the parent exists - file_status parent_status = status(parent, local_ec); + // 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(), 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) + { + fail_last_error: + DWORD err = ::GetLastError(); + emit_error(err, from, to, ec, "boost::filesystem::copy_file"); + return false; + } + + if (!::GetFileTime(hw_from.handle, 0, 0, &lwt_from)) + goto fail_last_error; + + 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 the parent does not exist, create the parent - if (parent_status.type() == file_not_found) - { - create_directories(parent, local_ec); - if (local_ec) + if (hw_to.handle != INVALID_HANDLE_VALUE) + { + FILETIME lwt_to; + if (!::GetFileTime(hw_to.handle, 0, 0, &lwt_to)) + goto fail_last_error; + + ULONGLONG tfrom = (static_cast< ULONGLONG >(lwt_from.dwHighDateTime) << 32) | static_cast< ULONGLONG >(lwt_from.dwLowDateTime); + ULONGLONG tto = (static_cast< ULONGLONG >(lwt_to.dwHighDateTime) << 32) | static_cast< ULONGLONG >(lwt_to.dwLowDateTime); + if (tfrom <= tto) + return false; + } + + copy_flags &= ~static_cast< DWORD >(COPY_FILE_FAIL_IF_EXISTS); + } + + struct callback_context + { + DWORD flush_error; + }; + + struct local + { + //! Callback that is called to report progress of \c CopyFileExW + static DWORD WINAPI on_copy_file_progress( + LARGE_INTEGER total_file_size, + LARGE_INTEGER total_bytes_transferred, + LARGE_INTEGER stream_size, + LARGE_INTEGER stream_bytes_transferred, + DWORD stream_number, + DWORD callback_reason, + HANDLE from_handle, + HANDLE to_handle, + LPVOID ctx) + { + // For each stream, CopyFileExW will open a separate pair of file handles, so we need to flush each stream separately. + if (stream_bytes_transferred.QuadPart == stream_size.QuadPart) + { + BOOL res = ::FlushFileBuffers(to_handle); + if (BOOST_UNLIKELY(!res)) + { + callback_context* context = static_cast< callback_context* >(ctx); + if (BOOST_LIKELY(context->flush_error == 0u)) + context->flush_error = ::GetLastError(); + } + } + + return PROGRESS_CONTINUE; + } + }; + + callback_context cb_context = {}; + LPPROGRESS_ROUTINE cb = NULL; + LPVOID cb_ctx = NULL; + + if ((options & (static_cast< unsigned int >(copy_options::synchronize_data) | static_cast< unsigned int >(copy_options::synchronize))) != 0u) + { + cb = &local::on_copy_file_progress; + cb_ctx = &cb_context; + } + + BOOL cancelled = FALSE; + BOOL res = ::CopyFileExW(from.c_str(), to.c_str(), cb, cb_ctx, &cancelled, copy_flags); + DWORD err; + if (BOOST_UNLIKELY(!res)) + { + err = ::GetLastError(); + if ((err == ERROR_FILE_EXISTS || err == ERROR_ALREADY_EXISTS) && (options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) + return false; + + copy_failed: + emit_error(err, from, to, ec, "boost::filesystem::copy_file"); + return false; + } + + if (BOOST_UNLIKELY(cb_context.flush_error != 0u)) + { + err = cb_context.flush_error; + goto copy_failed; + } + + return true; + +#endif // defined(BOOST_POSIX_API) +} + +BOOST_FILESYSTEM_DECL +void copy_symlink(path const& existing_symlink, path const& new_symlink, system::error_code* ec) +{ + path p(read_symlink(existing_symlink, ec)); + if (ec && *ec) + return; + create_symlink(p, new_symlink, ec); +} + +BOOST_FILESYSTEM_DECL +bool create_directories(path const& p, system::error_code* ec) +{ + if (p.empty()) + { + if (!ec) { - if (ec == 0) BOOST_FILESYSTEM_THROW(filesystem_error( - "boost::filesystem::create_directories", parent, local_ec)); - else - *ec = local_ec; - return false; + "boost::filesystem::create_directories", p, + system::errc::make_error_code(system::errc::invalid_argument))); } - } + ec->assign(system::errc::invalid_argument, system::generic_category()); + return false; } - // create the directory - return create_directory(p, ec); - } + if (ec) + ec->clear(); + + path::const_iterator e(p.end()), it(e); + path parent(p); + path const& dot_p = dot_path(); + path const& dot_dot_p = dot_dot_path(); + error_code local_ec; + + // Find the initial part of the path that exists + for (path fname = parent.filename(); parent.has_relative_path(); fname = parent.filename()) + { + if (!fname.empty() && fname != dot_p && fname != dot_dot_p) + { + file_status existing_status = detail::status(parent, &local_ec); + + if (existing_status.type() == directory_file) + { + break; + } + else if (BOOST_UNLIKELY(existing_status.type() == status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::create_directories", p, parent, local_ec)); + *ec = local_ec; + return false; + } + } + + --it; + parent.remove_filename(); + } - BOOST_FILESYSTEM_DECL - bool create_directory(const path& p, error_code* ec) - { - if (BOOST_CREATE_DIRECTORY(p.c_str())) + // Create missing directories + bool created = false; + for (; it != e; ++it) { - if (ec != 0) + path const& fname = *it; + parent /= fname; + if (!fname.empty() && fname != dot_p && fname != dot_dot_p) + { + created = create_directory(parent, NULL, &local_ec); + if (BOOST_UNLIKELY(!!local_ec)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::create_directories", p, parent, local_ec)); + *ec = local_ec; + return false; + } + } + } + + return created; +} + +BOOST_FILESYSTEM_DECL +bool create_directory(path const& p, const path* existing, error_code* ec) +{ + if (ec) ec->clear(); - return true; + +#if defined(BOOST_POSIX_API) + + mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; + if (existing) + { +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx existing_stat; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, existing->c_str(), AT_NO_AUTOMOUNT, STATX_TYPE | STATX_MODE, &existing_stat) < 0)) + { + emit_error(errno, p, *existing, ec, "boost::filesystem::create_directory"); + return false; + } + + if (BOOST_UNLIKELY((existing_stat.stx_mask & (STATX_TYPE | STATX_MODE)) != (STATX_TYPE | STATX_MODE))) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, *existing, ec, "boost::filesystem::create_directory"); + return false; + } +#else + struct ::stat existing_stat; + if (::stat(existing->c_str(), &existing_stat) < 0) + { + emit_error(errno, p, *existing, ec, "boost::filesystem::create_directory"); + return false; + } +#endif + + const mode_t existing_mode = get_mode(existing_stat); + if (!S_ISDIR(existing_mode)) + { + emit_error(ENOTDIR, p, *existing, ec, "boost::filesystem::create_directory"); + return false; + } + + mode = existing_mode; } + if (::mkdir(p.c_str(), mode) == 0) + return true; + +#else // defined(BOOST_POSIX_API) + + BOOL res; + if (existing) + res = ::CreateDirectoryExW(existing->c_str(), p.c_str(), NULL); + else + res = ::CreateDirectoryW(p.c_str(), NULL); + + if (res) + return true; + +#endif // defined(BOOST_POSIX_API) + // attempt to create directory failed - int errval(BOOST_ERRNO); // save reason for failure + err_t errval = BOOST_ERRNO; // save reason for failure error_code dummy; if (is_directory(p, dummy)) + return false; + + // attempt to create directory failed && it doesn't already exist + emit_error(errval, p, ec, "boost::filesystem::create_directory"); + return false; +} + +// Deprecated, to be removed in a future release +BOOST_FILESYSTEM_DECL +void copy_directory(path const& from, path const& to, system::error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + int err; + struct ::statx from_stat; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, from.c_str(), AT_NO_AUTOMOUNT, STATX_TYPE | STATX_MODE, &from_stat) < 0)) + { + fail_errno: + err = errno; + fail: + emit_error(err, from, to, ec, "boost::filesystem::copy_directory"); + return; + } + + if (BOOST_UNLIKELY((from_stat.stx_mask & (STATX_TYPE | STATX_MODE)) != (STATX_TYPE | STATX_MODE))) + { + err = BOOST_ERROR_NOT_SUPPORTED; + goto fail; + } +#else + struct ::stat from_stat; + if (BOOST_UNLIKELY(::stat(from.c_str(), &from_stat) < 0)) { - if (ec != 0) + fail_errno: + emit_error(errno, from, to, ec, "boost::filesystem::copy_directory"); + return; + } +#endif + + if (BOOST_UNLIKELY(::mkdir(to.c_str(), get_mode(from_stat)) < 0)) + goto fail_errno; + +#else // defined(BOOST_POSIX_API) + + if (BOOST_UNLIKELY(!::CreateDirectoryExW(from.c_str(), to.c_str(), 0))) + emit_error(BOOST_ERRNO, from, to, ec, "boost::filesystem::copy_directory"); + +#endif // defined(BOOST_POSIX_API) +} + +BOOST_FILESYSTEM_DECL +void create_directory_symlink(path const& to, path const& from, system::error_code* ec) +{ + if (ec) ec->clear(); - return false; + +#if defined(BOOST_POSIX_API) + int err = ::symlink(to.c_str(), from.c_str()); + if (BOOST_UNLIKELY(err < 0)) + { + err = errno; + emit_error(err, to, from, ec, "boost::filesystem::create_directory_symlink"); + } +#else + // see if actually supported by Windows runtime dll + if (!create_symbolic_link_api) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, to, from, ec, "boost::filesystem::create_directory_symlink"); + return; } - // attempt to create directory failed && it doesn't already exist - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::create_directory", - p, error_code(errval, system_category()))); - else - ec->assign(errval, system_category()); + if (!create_symbolic_link_api(from.c_str(), to.c_str(), SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) + { + emit_error(BOOST_ERRNO, to, from, ec, "boost::filesystem::create_directory_symlink"); + } +#endif +} + +BOOST_FILESYSTEM_DECL +void create_hard_link(path const& to, path const& from, error_code* ec) +{ + if (ec) + ec->clear(); + +#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 +void create_symlink(path const& to, path const& from, error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + int err = ::symlink(to.c_str(), from.c_str()); + if (BOOST_UNLIKELY(err < 0)) + { + err = errno; + emit_error(err, to, from, ec, "boost::filesystem::create_symlink"); + } +#else + // see if actually supported by Windows runtime dll + 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 (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"); + } +#endif +} + +BOOST_FILESYSTEM_DECL +path current_path(error_code* ec) +{ +#if defined(UNDER_CE) || defined(__wasm) + // Windows CE has no current directory, so everything's relative to the root of the directory tree. + // WASI also does not support current path. + emit_error(BOOST_ERROR_NOT_SUPPORTED, ec, "boost::filesystem::current_path"); + return path(); +#elif defined(BOOST_POSIX_API) + struct local + { + static bool getcwd_error(error_code* ec) + { + const int err = errno; + return error((err != ERANGE +#if defined(__MSL__) && (defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) + // bug in some versions of the Metrowerks C lib on the Mac: wrong errno set + && err != 0 +#endif + ) ? err : 0, + ec, "boost::filesystem::current_path"); + } + }; - return false; - } - - BOOST_FILESYSTEM_DECL - void create_directory_symlink(const path& to, const path& from, - system::error_code* ec) - { -# if defined(BOOST_WINDOWS_API) && _WIN32_WINNT < 0x0600 // SDK earlier than Vista and Server 2008 - - error(BOOST_ERROR_NOT_SUPPORTED, to, from, ec, - "boost::filesystem::create_directory_symlink"); -# else - -# if defined(BOOST_WINDOWS_API) && _WIN32_WINNT >= 0x0600 - // see if actually supported by Windows runtime dll - if (error(!create_symbolic_link_api ? BOOST_ERROR_NOT_SUPPORTED : 0, to, from, ec, - "boost::filesystem::create_directory_symlink")) - return; -# endif - - error(!BOOST_CREATE_SYMBOLIC_LINK(from.c_str(), to.c_str(), - SYMBOLIC_LINK_FLAG_DIRECTORY) ? BOOST_ERRNO : 0, - to, from, ec, "boost::filesystem::create_directory_symlink"); -# endif - } - - BOOST_FILESYSTEM_DECL - void create_hard_link(const path& to, const path& from, error_code* ec) - { - -# if defined(BOOST_WINDOWS_API) && _WIN32_WINNT < 0x0500 // SDK earlier than Win 2K - - error(BOOST_ERROR_NOT_SUPPORTED, to, from, ec, - "boost::filesystem::create_hard_link"); -# else - -# if defined(BOOST_WINDOWS_API) && _WIN32_WINNT >= 0x0500 - // 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 - - error(!BOOST_CREATE_HARD_LINK(from.c_str(), to.c_str()) ? BOOST_ERRNO : 0, to, from, ec, - "boost::filesystem::create_hard_link"); -# endif - } - - BOOST_FILESYSTEM_DECL - void create_symlink(const path& to, const path& from, error_code* ec) - { -# if defined(BOOST_WINDOWS_API) && _WIN32_WINNT < 0x0600 // SDK earlier than Vista and Server 2008 - error(BOOST_ERROR_NOT_SUPPORTED, to, from, ec, - "boost::filesystem::create_directory_symlink"); -# else - -# if defined(BOOST_WINDOWS_API) && _WIN32_WINNT >= 0x0600 - // see if actually supported by Windows runtime dll - if (error(!create_symbolic_link_api ? BOOST_ERROR_NOT_SUPPORTED : 0, to, from, ec, - "boost::filesystem::create_symlink")) - return; -# endif - - error(!BOOST_CREATE_SYMBOLIC_LINK(from.c_str(), to.c_str(), 0) ? BOOST_ERRNO : 0, - to, from, ec, "boost::filesystem::create_symlink"); -# endif - } - - BOOST_FILESYSTEM_DECL - path current_path(error_code* ec) - { -# ifdef BOOST_POSIX_API path cur; - for (long path_max = 128;; path_max *=2)// loop 'til buffer large enough - { - boost::scoped_array<char> - buf(new char[static_cast<std::size_t>(path_max)]); - if (::getcwd(buf.get(), static_cast<std::size_t>(path_max))== 0) - { - if (error(errno != ERANGE ? errno : 0 - // bug in some versions of the Metrowerks C lib on the Mac: wrong errno set -# if defined(__MSL__) && (defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) - && errno != 0 -# endif - , ec, "boost::filesystem::current_path")) - { - break; - } - } - else - { - cur = buf.get(); - if (ec != 0) ec->clear(); - break; - } + char small_buf[small_path_size]; + const char* p = ::getcwd(small_buf, sizeof(small_buf)); + if (BOOST_LIKELY(!!p)) + { + cur = p; + if (ec) + ec->clear(); + } + else if (BOOST_LIKELY(!local::getcwd_error(ec))) + { + for (std::size_t path_max = sizeof(small_buf) * 2u;; path_max *= 2u) // loop 'til buffer large enough + { + if (BOOST_UNLIKELY(path_max > absolute_path_max)) + { + emit_error(ENAMETOOLONG, ec, "boost::filesystem::current_path"); + break; + } + + boost::scoped_array< char > buf(new char[path_max]); + p = ::getcwd(buf.get(), path_max); + if (BOOST_LIKELY(!!p)) + { + cur = buf.get(); + if (ec) + ec->clear(); + break; + } + else if (BOOST_UNLIKELY(local::getcwd_error(ec))) + { + break; + } + } } - return cur; -# else + return cur; +#else DWORD sz; - if ((sz = ::GetCurrentDirectoryW(0, NULL)) == 0)sz = 1; - boost::scoped_array<path::value_type> buf(new path::value_type[sz]); - error(::GetCurrentDirectoryW(sz, buf.get()) == 0 ? BOOST_ERRNO : 0, ec, - "boost::filesystem::current_path"); + if ((sz = ::GetCurrentDirectoryW(0, NULL)) == 0) + sz = 1; + boost::scoped_array< path::value_type > buf(new path::value_type[sz]); + error(::GetCurrentDirectoryW(sz, buf.get()) == 0 ? BOOST_ERRNO : 0, ec, "boost::filesystem::current_path"); return path(buf.get()); -# endif - } +#endif +} +BOOST_FILESYSTEM_DECL +void current_path(path const& p, system::error_code* ec) +{ +#if defined(UNDER_CE) || defined(__wasm) + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::current_path"); +#else + error(!BOOST_SET_CURRENT_DIRECTORY(p.c_str()) ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::current_path"); +#endif +} - BOOST_FILESYSTEM_DECL - void current_path(const path& p, system::error_code* ec) - { - error(!BOOST_SET_CURRENT_DIRECTORY(p.c_str()) ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::current_path"); - } +BOOST_FILESYSTEM_DECL +bool equivalent(path const& p1, path const& p2, system::error_code* ec) +{ +#if defined(BOOST_POSIX_API) - BOOST_FILESYSTEM_DECL - bool equivalent(const path& p1, const path& p2, system::error_code* ec) - { -# ifdef BOOST_POSIX_API - struct stat s2; - int e2(::stat(p2.c_str(), &s2)); - struct stat s1; - int e1(::stat(p1.c_str(), &s1)); + // p2 is done first, so any error reported is for p1 +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx s2; + int e2 = invoke_statx(AT_FDCWD, p2.c_str(), AT_NO_AUTOMOUNT, STATX_INO, &s2); + if (BOOST_LIKELY(e2 == 0)) + { + if (BOOST_UNLIKELY((s2.stx_mask & STATX_INO) != STATX_INO)) + { + fail_unsupported: + emit_error(BOOST_ERROR_NOT_SUPPORTED, p1, p2, ec, "boost::filesystem::equivalent"); + return false; + } + } - if (e1 != 0 || e2 != 0) + struct ::statx s1; + int e1 = invoke_statx(AT_FDCWD, p1.c_str(), AT_NO_AUTOMOUNT, STATX_INO, &s1); + if (BOOST_LIKELY(e1 == 0)) { - // if one is invalid and the other isn't then they aren't equivalent, - // but if both are invalid then it is an error - error (e1 != 0 && e2 != 0, p1, p2, ec, "boost::filesystem::equivalent"); - return false; + if (BOOST_UNLIKELY((s1.stx_mask & STATX_INO) != STATX_INO)) + goto fail_unsupported; } +#else + struct ::stat s2; + int e2 = ::stat(p2.c_str(), &s2); + struct ::stat s1; + int e1 = ::stat(p1.c_str(), &s1); +#endif - // both stats now known to be valid - return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino - // According to the POSIX stat specs, "The st_ino and st_dev fields - // taken together uniquely identify the file within the system." - // Just to be sure, size and mod time are also checked. - && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; + if (BOOST_UNLIKELY(e1 != 0 || e2 != 0)) + { + // if one is invalid and the other isn't then they aren't equivalent, + // but if both are invalid then it is an error + if (e1 != 0 && e2 != 0) + emit_error(errno, p1, p2, ec, "boost::filesystem::equivalent"); + return false; + } + + return equivalent_stat(s1, s2); -# else // Windows +#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 @@ -1205,265 +2895,445 @@ namespace detail // p2 is done first, so any error reported is for p1 handle_wrapper h2( - create_file_handle( - p2.c_str(), - 0, - FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - 0)); + create_file_handle( + p2.c_str(), + 0u, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS)); handle_wrapper h1( - create_file_handle( - p1.c_str(), - 0, - FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - 0)); - - if (h1.handle == INVALID_HANDLE_VALUE - || h2.handle == INVALID_HANDLE_VALUE) - { - // if one is invalid and the other isn't, then they aren't equivalent, - // but if both are invalid then it is an error - error((h1.handle == INVALID_HANDLE_VALUE - && h2.handle == INVALID_HANDLE_VALUE) ? BOOST_ERROR_NOT_SUPPORTED : 0, p1, p2, ec, - "boost::filesystem::equivalent"); - return false; + create_file_handle( + p1.c_str(), + 0u, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS)); + + if (BOOST_UNLIKELY(h1.handle == INVALID_HANDLE_VALUE || h2.handle == INVALID_HANDLE_VALUE)) + { + // if one is invalid and the other isn't, then they aren't equivalent, + // but if both are invalid then it is an error + if (h1.handle == INVALID_HANDLE_VALUE && h2.handle == INVALID_HANDLE_VALUE) + error(BOOST_ERRNO, p1, p2, ec, "boost::filesystem::equivalent"); + return false; } // at this point, both handles are known to be valid BY_HANDLE_FILE_INFORMATION info1, info2; - if (error(!::GetFileInformationByHandle(h1.handle, &info1) ? BOOST_ERRNO : 0, - p1, p2, ec, "boost::filesystem::equivalent")) - return false; + if (error(!::GetFileInformationByHandle(h1.handle, &info1) ? BOOST_ERRNO : 0, p1, p2, ec, "boost::filesystem::equivalent")) + return false; - if (error(!::GetFileInformationByHandle(h2.handle, &info2) ? BOOST_ERRNO : 0, - p1, p2, ec, "boost::filesystem::equivalent")) - return false; + if (error(!::GetFileInformationByHandle(h2.handle, &info2) ? BOOST_ERRNO : 0, p1, p2, ec, "boost::filesystem::equivalent")) + return false; // In theory, volume serial numbers are sufficient to distinguish between // devices, but in practice VSN's are sometimes duplicated, so last write // time and file size are also checked. - return - info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber - && info1.nFileIndexHigh == info2.nFileIndexHigh - && info1.nFileIndexLow == info2.nFileIndexLow - && info1.nFileSizeHigh == info2.nFileSizeHigh - && info1.nFileSizeLow == info2.nFileSizeLow - && info1.ftLastWriteTime.dwLowDateTime - == info2.ftLastWriteTime.dwLowDateTime - && info1.ftLastWriteTime.dwHighDateTime - == info2.ftLastWriteTime.dwHighDateTime; - -# endif - } - - BOOST_FILESYSTEM_DECL - boost::uintmax_t file_size(const path& p, error_code* ec) - { -# ifdef BOOST_POSIX_API - - struct stat path_stat; - if (error(::stat(p.c_str(), &path_stat)!= 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::file_size")) - return static_cast<boost::uintmax_t>(-1); - if (error(!S_ISREG(path_stat.st_mode) ? EPERM : 0, - p, ec, "boost::filesystem::file_size")) - return static_cast<boost::uintmax_t>(-1); - - return static_cast<boost::uintmax_t>(path_stat.st_size); - -# else // Windows + return info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber && + info1.nFileIndexHigh == info2.nFileIndexHigh && + info1.nFileIndexLow == info2.nFileIndexLow && + info1.nFileSizeHigh == info2.nFileSizeHigh && + info1.nFileSizeLow == info2.nFileSizeLow && + info1.ftLastWriteTime.dwLowDateTime == info2.ftLastWriteTime.dwLowDateTime && + info1.ftLastWriteTime.dwHighDateTime == info2.ftLastWriteTime.dwHighDateTime; + +#endif +} + +BOOST_FILESYSTEM_DECL +uintmax_t file_size(path const& p, error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx path_stat; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, p.c_str(), AT_NO_AUTOMOUNT, STATX_TYPE | STATX_SIZE, &path_stat) < 0)) + { + emit_error(errno, p, ec, "boost::filesystem::file_size"); + return static_cast< uintmax_t >(-1); + } + + if (BOOST_UNLIKELY((path_stat.stx_mask & (STATX_TYPE | STATX_SIZE)) != (STATX_TYPE | STATX_SIZE) || !S_ISREG(path_stat.stx_mode))) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::file_size"); + return static_cast< uintmax_t >(-1); + } +#else + struct ::stat path_stat; + if (BOOST_UNLIKELY(::stat(p.c_str(), &path_stat) < 0)) + { + emit_error(errno, p, ec, "boost::filesystem::file_size"); + return static_cast< uintmax_t >(-1); + } + + if (BOOST_UNLIKELY(!S_ISREG(path_stat.st_mode))) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::file_size"); + return static_cast< uintmax_t >(-1); + } +#endif + + return get_size(path_stat); + +#else // defined(BOOST_POSIX_API) // assume uintmax_t is 64-bits on all Windows compilers WIN32_FILE_ATTRIBUTE_DATA fad; - if (error(::GetFileAttributesExW(p.c_str(), ::GetFileExInfoStandard, &fad)== 0 - ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::file_size")) - return static_cast<boost::uintmax_t>(-1); + if (BOOST_UNLIKELY(!::GetFileAttributesExW(p.c_str(), ::GetFileExInfoStandard, &fad))) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::file_size"); + return static_cast< uintmax_t >(-1); + } + + if (BOOST_UNLIKELY((fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)) + { + emit_error(ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::file_size"); + return static_cast< uintmax_t >(-1); + } + + return (static_cast< uintmax_t >(fad.nFileSizeHigh) + << (sizeof(fad.nFileSizeLow) * 8u)) | + fad.nFileSizeLow; - if (error((fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!= 0 - ? ERROR_NOT_SUPPORTED : 0, p, ec, "boost::filesystem::file_size")) - return static_cast<boost::uintmax_t>(-1); +#endif // defined(BOOST_POSIX_API) +} - return (static_cast<boost::uintmax_t>(fad.nFileSizeHigh) - << (sizeof(fad.nFileSizeLow)*8)) + fad.nFileSizeLow; -# endif - } +BOOST_FILESYSTEM_DECL +uintmax_t hard_link_count(path const& p, system::error_code* ec) +{ + if (ec) + ec->clear(); - BOOST_FILESYSTEM_DECL - boost::uintmax_t hard_link_count(const path& p, system::error_code* ec) - { -# ifdef BOOST_POSIX_API +#if defined(BOOST_POSIX_API) - struct stat path_stat; - return error(::stat(p.c_str(), &path_stat)!= 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::hard_link_count") - ? 0 - : static_cast<boost::uintmax_t>(path_stat.st_nlink); +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx path_stat; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, p.c_str(), AT_NO_AUTOMOUNT, STATX_NLINK, &path_stat) < 0)) + { + emit_error(errno, p, ec, "boost::filesystem::hard_link_count"); + return static_cast< uintmax_t >(-1); + } -# else // Windows + if (BOOST_UNLIKELY((path_stat.stx_mask & STATX_NLINK) != STATX_NLINK)) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::hard_link_count"); + return static_cast< uintmax_t >(-1); + } + + return static_cast< uintmax_t >(path_stat.stx_nlink); +#else + struct ::stat path_stat; + if (BOOST_UNLIKELY(::stat(p.c_str(), &path_stat) < 0)) + { + emit_error(errno, p, ec, "boost::filesystem::hard_link_count"); + return static_cast< uintmax_t >(-1); + } + + return static_cast< uintmax_t >(path_stat.st_nlink); +#endif + +#else // defined(BOOST_POSIX_API) + + handle_wrapper h( + 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)) + { + fail_errno: + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::hard_link_count"); + return static_cast< uintmax_t >(-1); + } // Link count info is only available through GetFileInformationByHandle BY_HANDLE_FILE_INFORMATION info; - 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)); - return - !error(h.handle == INVALID_HANDLE_VALUE ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::hard_link_count") - && !error(::GetFileInformationByHandle(h.handle, &info)== 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::hard_link_count") - ? info.nNumberOfLinks - : 0; -# endif - } - - BOOST_FILESYSTEM_DECL - path initial_path(error_code* ec) - { - static path init_path; - if (init_path.empty()) + if (BOOST_UNLIKELY(!::GetFileInformationByHandle(h.handle, &info))) + goto fail_errno; + + return static_cast< uintmax_t >(info.nNumberOfLinks); + +#endif // defined(BOOST_POSIX_API) +} + +BOOST_FILESYSTEM_DECL +path initial_path(error_code* ec) +{ + static path init_path; + if (init_path.empty()) init_path = current_path(ec); - else if (ec != 0) ec->clear(); - return init_path; - } - - BOOST_FILESYSTEM_DECL - bool is_empty(const path& p, system::error_code* ec) - { -# ifdef BOOST_POSIX_API - - struct stat path_stat; - if (error(::stat(p.c_str(), &path_stat)!= 0, - p, ec, "boost::filesystem::is_empty")) - return false; - return S_ISDIR(path_stat.st_mode) - ? is_empty_directory(p, ec) - : path_stat.st_size == 0; -# else + else if (ec) + ec->clear(); + return init_path; +} + +BOOST_FILESYSTEM_DECL +bool is_empty(path const& p, system::error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx path_stat; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, p.c_str(), AT_NO_AUTOMOUNT, STATX_TYPE | STATX_SIZE, &path_stat) < 0)) + { + emit_error(errno, p, ec, "boost::filesystem::is_empty"); + return false; + } + + if (BOOST_UNLIKELY((path_stat.stx_mask & STATX_TYPE) != STATX_TYPE)) + { + fail_unsupported: + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::is_empty"); + return false; + } + + if (S_ISDIR(get_mode(path_stat))) + return is_empty_directory(p, ec); + + if (BOOST_UNLIKELY((path_stat.stx_mask & STATX_SIZE) != STATX_SIZE)) + goto fail_unsupported; + + return get_size(path_stat) == 0u; +#else + struct ::stat path_stat; + if (BOOST_UNLIKELY(::stat(p.c_str(), &path_stat) < 0)) + { + emit_error(errno, p, ec, "boost::filesystem::is_empty"); + return false; + } + + return S_ISDIR(get_mode(path_stat)) ? is_empty_directory(p, ec) : get_size(path_stat) == 0u; +#endif + +#else // defined(BOOST_POSIX_API) WIN32_FILE_ATTRIBUTE_DATA fad; - if (error(::GetFileAttributesExW(p.c_str(), ::GetFileExInfoStandard, &fad)== 0 - ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::is_empty")) + if (BOOST_UNLIKELY(!::GetFileAttributesExW(p.c_str(), ::GetFileExInfoStandard, &fad))) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::is_empty"); return false; + } - if (ec != 0) ec->clear(); - return - (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - ? is_empty_directory(p, ec) - : (!fad.nFileSizeHigh && !fad.nFileSizeLow); -# endif - } + return (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? is_empty_directory(p, ec) : (!fad.nFileSizeHigh && !fad.nFileSizeLow); - BOOST_FILESYSTEM_DECL - std::time_t last_write_time(const path& p, system::error_code* ec) - { -# ifdef BOOST_POSIX_API +#endif // defined(BOOST_POSIX_API) +} - struct stat path_stat; - if (error(::stat(p.c_str(), &path_stat)!= 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::last_write_time")) - return std::time_t(-1); - return path_stat.st_mtime; +BOOST_FILESYSTEM_DECL +std::time_t creation_time(path const& p, system::error_code* ec) +{ + if (ec) + ec->clear(); -# else +#if defined(BOOST_POSIX_API) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx stx; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, p.c_str(), AT_NO_AUTOMOUNT, STATX_BTIME, &stx) < 0)) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::creation_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + if (BOOST_UNLIKELY((stx.stx_mask & STATX_BTIME) != STATX_BTIME)) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::creation_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + return stx.stx_btime.tv_sec; +#elif defined(BOOST_FILESYSTEM_STAT_ST_BIRTHTIME) && defined(BOOST_FILESYSTEM_STAT_ST_BIRTHTIMENSEC) + struct ::stat st; + if (BOOST_UNLIKELY(::stat(p.c_str(), &st) < 0)) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::creation_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + return st.BOOST_FILESYSTEM_STAT_ST_BIRTHTIME; +#else + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::creation_time"); + return (std::numeric_limits< std::time_t >::min)(); +#endif + +#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)) + { + fail: + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::creation_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + + FILETIME ct; + + if (BOOST_UNLIKELY(!::GetFileTime(hw.handle, &ct, NULL, NULL))) + goto fail; + + return to_time_t(ct); + +#endif // defined(BOOST_POSIX_API) +} - if (error(hw.handle == INVALID_HANDLE_VALUE ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::last_write_time")) - return std::time_t(-1); +BOOST_FILESYSTEM_DECL +std::time_t last_write_time(path const& p, system::error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx stx; + if (BOOST_UNLIKELY(invoke_statx(AT_FDCWD, p.c_str(), AT_NO_AUTOMOUNT, STATX_MTIME, &stx) < 0)) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + if (BOOST_UNLIKELY((stx.stx_mask & STATX_MTIME) != STATX_MTIME)) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::last_write_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + return stx.stx_mtime.tv_sec; +#else + struct ::stat st; + if (BOOST_UNLIKELY(::stat(p.c_str(), &st) < 0)) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); + return (std::numeric_limits< std::time_t >::min)(); + } + return st.st_mtime; +#endif + +#else // defined(BOOST_POSIX_API) + + handle_wrapper hw( + 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)) + { + fail: + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); + return (std::numeric_limits< std::time_t >::min)(); + } FILETIME lwt; - if (error(::GetFileTime(hw.handle, 0, 0, &lwt)== 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::last_write_time")) - return std::time_t(-1); + if (BOOST_UNLIKELY(!::GetFileTime(hw.handle, NULL, NULL, &lwt))) + goto fail; return to_time_t(lwt); -# endif - } - - BOOST_FILESYSTEM_DECL - void last_write_time(const path& p, const std::time_t new_time, - system::error_code* ec) - { -# ifdef BOOST_POSIX_API - - struct stat path_stat; - if (error(::stat(p.c_str(), &path_stat)!= 0, - p, ec, "boost::filesystem::last_write_time")) + +#endif // defined(BOOST_POSIX_API) +} + +BOOST_FILESYSTEM_DECL +void last_write_time(path const& p, const std::time_t new_time, system::error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + +#if _POSIX_C_SOURCE >= 200809L + + struct timespec times[2] = {}; + + // Keep the last access time unchanged + times[0].tv_nsec = UTIME_OMIT; + + times[1].tv_sec = new_time; + + if (BOOST_UNLIKELY(::utimensat(AT_FDCWD, p.c_str(), times, 0) != 0)) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); return; + } + +#else // _POSIX_C_SOURCE >= 200809L + + struct ::stat st; + if (BOOST_UNLIKELY(::stat(p.c_str(), &st) < 0)) + { + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); + return; + } + ::utimbuf buf; - buf.actime = path_stat.st_atime; // utime()updates access time too:-( + buf.actime = st.st_atime; // utime()updates access time too:-( buf.modtime = new_time; - error(::utime(p.c_str(), &buf)!= 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::last_write_time"); + if (BOOST_UNLIKELY(::utime(p.c_str(), &buf) < 0)) + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); + +#endif // _POSIX_C_SOURCE >= 200809L -# else +#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 (error(hw.handle == INVALID_HANDLE_VALUE ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::last_write_time")) + if (BOOST_UNLIKELY(hw.handle == INVALID_HANDLE_VALUE)) + { + fail: + emit_error(BOOST_ERRNO, p, ec, "boost::filesystem::last_write_time"); return; + } FILETIME lwt; to_FILETIME(new_time, lwt); - error(::SetFileTime(hw.handle, 0, 0, &lwt)== 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::last_write_time"); -# endif - } + if (BOOST_UNLIKELY(!::SetFileTime(hw.handle, 0, 0, &lwt))) + goto fail; -# ifdef BOOST_POSIX_API - const perms active_bits(all_all | set_uid_on_exe | set_gid_on_exe | sticky_bit); - inline mode_t mode_cast(perms prms) { return prms & active_bits; } -# endif +#endif // defined(BOOST_POSIX_API) +} - BOOST_FILESYSTEM_DECL - void permissions(const path& p, perms prms, system::error_code* ec) - { - BOOST_ASSERT_MSG(!((prms & add_perms) && (prms & remove_perms)), - "add_perms and remove_perms are mutually exclusive"); +#ifdef BOOST_POSIX_API +const perms active_bits(all_all | set_uid_on_exe | set_gid_on_exe | sticky_bit); +inline mode_t mode_cast(perms prms) +{ + return prms & active_bits; +} +#endif + +BOOST_FILESYSTEM_DECL +void permissions(path const& p, perms prms, system::error_code* ec) +{ + BOOST_ASSERT_MSG(!((prms & add_perms) && (prms & remove_perms)), "add_perms and remove_perms are mutually exclusive"); - if ((prms & add_perms) && (prms & remove_perms)) // precondition failed - return; + if ((prms & add_perms) && (prms & remove_perms)) // precondition failed + return; -# ifdef BOOST_POSIX_API +#if defined(__wasm) + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::permissions"); +#elif defined(BOOST_POSIX_API) error_code local_ec; - file_status current_status((prms & symlink_perms) - ? fs::symlink_status(p, local_ec) - : fs::status(p, local_ec)); + file_status current_status((prms & symlink_perms) ? fs::symlink_status(p, local_ec) : fs::status(p, local_ec)); if (local_ec) { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error( - "boost::filesystem::permissions", p, local_ec)); - else + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::permissions", p, local_ec)); + *ec = local_ec; - return; + return; } if (prms & add_perms) - prms |= current_status.permissions(); + prms |= current_status.permissions(); else if (prms & remove_perms) - prms = current_status.permissions() & ~prms; + prms = current_status.permissions() & ~prms; // OS X <10.10, iOS <8.0 and some other platforms don't support fchmodat(). // Solaris (SunPro and gcc) only support fchmodat() on Solaris 11 and higher, @@ -1476,925 +3346,795 @@ namespace detail // "http://man7.org/linux/man-pages/man7/symlink.7.html" // - See the fchmodat() Linux man page: // "http://man7.org/linux/man-pages/man2/fchmodat.2.html" -# if defined(AT_FDCWD) && defined(AT_SYMLINK_NOFOLLOW) \ - && !(defined(__SUNPRO_CC) || defined(__sun) || defined(sun)) \ - && !(defined(linux) || defined(__linux) || defined(__linux__)) \ - && !(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \ - && __MAC_OS_X_VERSION_MIN_REQUIRED < 101000) \ - && !(defined(__IPHONE_OS_VERSION_MIN_REQUIRED) \ - && __IPHONE_OS_VERSION_MIN_REQUIRED < 80000) - if (::fchmodat(AT_FDCWD, p.c_str(), mode_cast(prms), - !(prms & symlink_perms) ? 0 : AT_SYMLINK_NOFOLLOW)) -# else // fallback if fchmodat() not supported - if (::chmod(p.c_str(), mode_cast(prms))) -# endif - { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error( - "boost::filesystem::permissions", p, - error_code(errno, system::generic_category()))); - else - ec->assign(errno, system::generic_category()); - } - -# else // Windows +#if defined(AT_FDCWD) && defined(AT_SYMLINK_NOFOLLOW) && \ + !(defined(__SUNPRO_CC) || defined(__sun) || defined(sun)) && \ + !(defined(linux) || defined(__linux) || defined(__linux__)) && \ + !(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101000) && \ + !(defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 80000) && \ + !(defined(__QNX__) && (_NTO_VERSION <= 700)) + if (::fchmodat(AT_FDCWD, p.c_str(), mode_cast(prms), !(prms & symlink_perms) ? 0 : AT_SYMLINK_NOFOLLOW)) +#else // fallback if fchmodat() not supported + if (::chmod(p.c_str(), mode_cast(prms))) +#endif + { + const int err = errno; + if (!ec) + { + BOOST_FILESYSTEM_THROW(filesystem_error( + "boost::filesystem::permissions", p, error_code(err, system::generic_category()))); + } + + ec->assign(err, system::generic_category()); + } + +#else // Windows // if not going to alter FILE_ATTRIBUTE_READONLY, just return - if (!(!((prms & (add_perms | remove_perms))) - || (prms & (owner_write|group_write|others_write)))) - return; + if (!(!((prms & (add_perms | remove_perms))) || (prms & (owner_write | group_write | others_write)))) + return; DWORD attr = ::GetFileAttributesW(p.c_str()); if (error(attr == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::permissions")) - return; + return; if (prms & add_perms) - attr &= ~FILE_ATTRIBUTE_READONLY; + attr &= ~FILE_ATTRIBUTE_READONLY; else if (prms & remove_perms) - attr |= FILE_ATTRIBUTE_READONLY; - else if (prms & (owner_write|group_write|others_write)) - attr &= ~FILE_ATTRIBUTE_READONLY; + attr |= FILE_ATTRIBUTE_READONLY; + else if (prms & (owner_write | group_write | others_write)) + attr &= ~FILE_ATTRIBUTE_READONLY; else - attr |= FILE_ATTRIBUTE_READONLY; + attr |= FILE_ATTRIBUTE_READONLY; - error(::SetFileAttributesW(p.c_str(), attr) == 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::permissions"); -# endif - } + error(::SetFileAttributesW(p.c_str(), attr) == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::permissions"); +#endif +} + +BOOST_FILESYSTEM_DECL +path read_symlink(path const& p, system::error_code* ec) +{ + if (ec) + ec->clear(); - BOOST_FILESYSTEM_DECL - path read_symlink(const path& p, system::error_code* ec) - { path symlink_path; -# ifdef BOOST_POSIX_API +#ifdef BOOST_POSIX_API + const char* const path_str = p.c_str(); + char small_buf[small_path_size]; + ssize_t result = ::readlink(path_str, small_buf, sizeof(small_buf)); + if (BOOST_UNLIKELY(result < 0)) + { + fail: + const int err = errno; + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::read_symlink", p, error_code(err, system_category()))); - for (std::size_t path_max = 64;; path_max *= 2)// loop 'til buffer large enough + ec->assign(err, system_category()); + } + else if (BOOST_LIKELY(static_cast< std::size_t >(result) < sizeof(small_buf))) { - boost::scoped_array<char> buf(new char[path_max]); - ssize_t result; - if ((result=::readlink(p.c_str(), buf.get(), path_max))== -1) - { - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::read_symlink", - p, error_code(errno, system_category()))); - else ec->assign(errno, system_category()); - break; - } - else - { - if(result != static_cast<ssize_t>(path_max)) + symlink_path.assign(small_buf, small_buf + result); + } + else + { + for (std::size_t path_max = sizeof(small_buf) * 2u;; path_max *= 2u) // loop 'til buffer large enough { - symlink_path.assign(buf.get(), buf.get() + result); - if (ec != 0) ec->clear(); - break; + if (BOOST_UNLIKELY(path_max > absolute_path_max)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::read_symlink", p, error_code(ENAMETOOLONG, system_category()))); + + ec->assign(ENAMETOOLONG, system_category()); + break; + } + + boost::scoped_array< char > buf(new char[path_max]); + result = ::readlink(path_str, buf.get(), path_max); + if (BOOST_UNLIKELY(result < 0)) + { + goto fail; + } + else if (BOOST_LIKELY(static_cast< std::size_t >(result) < path_max)) + { + symlink_path.assign(buf.get(), buf.get() + result); + break; + } } - } } -# elif _WIN32_WINNT < 0x0600 // SDK earlier than Vista and Server 2008 - error(BOOST_ERROR_NOT_SUPPORTED, p, ec, - "boost::filesystem::read_symlink"); -# else // Vista and Server 2008 SDK, or later +#else + + handle_wrapper h( + 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)); - union info_t + DWORD error; + if (BOOST_UNLIKELY(h.handle == INVALID_HANDLE_VALUE)) { - char buf[REPARSE_DATA_BUFFER_HEADER_SIZE+MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - REPARSE_DATA_BUFFER rdb; - } info; + error = ::GetLastError(); - handle_wrapper h( - create_file_handle(p.c_str(), GENERIC_READ, 0, 0, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0)); + 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; - if (error(h.handle == INVALID_HANDLE_VALUE ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::read_symlink")) + 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; + } - DWORD sz; + symlink_path = convert_nt_path_to_win32_path(buffer + offset / sizeof(wchar_t), len / sizeof(wchar_t)); +#endif - if (!error(::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, - 0, 0, info.buf, sizeof(info), &sz, 0) == 0 ? BOOST_ERRNO : 0, p, ec, - "boost::filesystem::read_symlink" )) - symlink_path.assign( - static_cast<wchar_t*>(info.rdb.SymbolicLinkReparseBuffer.PathBuffer) - + info.rdb.SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t), - static_cast<wchar_t*>(info.rdb.SymbolicLinkReparseBuffer.PathBuffer) - + info.rdb.SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t) - + info.rdb.SymbolicLinkReparseBuffer.PrintNameLength/sizeof(wchar_t)); -# endif return symlink_path; - } - - BOOST_FILESYSTEM_DECL - path relative(const path& p, const path& base, error_code* ec) - { - error_code tmp_ec; - path wc_base(weakly_canonical(base, &tmp_ec)); - if (error(tmp_ec.value(), base, ec, "boost::filesystem::relative")) - return path(); - path wc_p(weakly_canonical(p, &tmp_ec)); - if (error(tmp_ec.value(), base, ec, "boost::filesystem::relative")) - return path(); +} + +BOOST_FILESYSTEM_DECL +path relative(path const& p, path const& base, error_code* ec) +{ + if (ec) + ec->clear(); + + error_code local_ec; + path cur_path; + if (!p.is_absolute() || !base.is_absolute()) + { + cur_path = detail::current_path(&local_ec); + if (BOOST_UNLIKELY(!!local_ec)) + { + fail_local_ec: + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::relative", p, base, local_ec)); + + *ec = local_ec; + return path(); + } + } + + path wc_base(detail::weakly_canonical(base, cur_path, &local_ec)); + if (BOOST_UNLIKELY(!!local_ec)) + goto fail_local_ec; + path wc_p(detail::weakly_canonical(p, cur_path, &local_ec)); + if (BOOST_UNLIKELY(!!local_ec)) + goto fail_local_ec; return wc_p.lexically_relative(wc_base); - } - - BOOST_FILESYSTEM_DECL - bool remove(const path& p, error_code* ec) - { - error_code tmp_ec; - file_type type = query_file_type(p, &tmp_ec); - if (error(type == status_error ? tmp_ec.value() : 0, p, ec, - "boost::filesystem::remove")) - return false; +} - // Since POSIX remove() is specified to work with either files or directories, in a - // perfect world it could just be called. But some important real-world operating - // systems (Windows, Mac OS X, for example) don't implement the POSIX spec. So - // remove_file_or_directory() is always called to keep it simple. - return remove_file_or_directory(p, type, ec); - } - - BOOST_FILESYSTEM_DECL - boost::uintmax_t remove_all(const path& p, error_code* ec) - { - error_code tmp_ec; - file_type type = query_file_type(p, &tmp_ec); - if (error(type == status_error ? tmp_ec.value() : 0, p, ec, - "boost::filesystem::remove_all")) - return 0; - - return (type != status_error && type != file_not_found) // exists - ? remove_all_aux(p, type, ec) - : 0; - } - - BOOST_FILESYSTEM_DECL - void rename(const path& old_p, const path& new_p, error_code* ec) - { - error(!BOOST_MOVE_FILE(old_p.c_str(), new_p.c_str()) ? BOOST_ERRNO : 0, old_p, new_p, - ec, "boost::filesystem::rename"); - } - - BOOST_FILESYSTEM_DECL - void resize_file(const path& p, uintmax_t size, system::error_code* ec) - { -# if defined(BOOST_POSIX_API) - if (BOOST_UNLIKELY(size > static_cast< uintmax_t >((std::numeric_limits< off_t >::max)()))) { - error(system::errc::file_too_large, p, ec, "boost::filesystem::resize_file"); - return; - } -# endif - error(!BOOST_RESIZE_FILE(p.c_str(), size) ? BOOST_ERRNO : 0, p, ec, - "boost::filesystem::resize_file"); - } - - BOOST_FILESYSTEM_DECL - space_info space(const path& p, error_code* ec) - { -# ifdef BOOST_POSIX_API - struct BOOST_STATVFS vfs; - space_info info; - if (!error(::BOOST_STATVFS(p.c_str(), &vfs) ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::space")) +BOOST_FILESYSTEM_DECL +bool remove(path const& p, error_code* ec) +{ + if (ec) + ec->clear(); + + return detail::remove_impl(p, ec); +} + +BOOST_FILESYSTEM_DECL +uintmax_t remove_all(path const& p, error_code* ec) +{ + if (ec) + ec->clear(); + + return detail::remove_all_impl(p, ec); +} + +BOOST_FILESYSTEM_DECL +void rename(path const& old_p, path const& new_p, error_code* ec) +{ + error(!BOOST_MOVE_FILE(old_p.c_str(), new_p.c_str()) ? BOOST_ERRNO : 0, old_p, new_p, ec, "boost::filesystem::rename"); +} + +BOOST_FILESYSTEM_DECL +void resize_file(path const& p, uintmax_t size, system::error_code* ec) +{ +#if defined(BOOST_POSIX_API) + if (BOOST_UNLIKELY(size > static_cast< uintmax_t >((std::numeric_limits< off_t >::max)()))) { - info.capacity - = static_cast<boost::uintmax_t>(vfs.f_blocks)* BOOST_STATVFS_F_FRSIZE; - info.free - = static_cast<boost::uintmax_t>(vfs.f_bfree)* BOOST_STATVFS_F_FRSIZE; - info.available - = static_cast<boost::uintmax_t>(vfs.f_bavail)* BOOST_STATVFS_F_FRSIZE; + emit_error(system::errc::file_too_large, p, ec, "boost::filesystem::resize_file"); + return; } +#endif + error(!BOOST_RESIZE_FILE(p.c_str(), size) ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::resize_file"); +} -# else - ULARGE_INTEGER avail, total, free; +BOOST_FILESYSTEM_DECL +space_info space(path const& p, error_code* ec) +{ space_info info; + // Initialize members to -1, as required by C++20 [fs.op.space]/1 in case of error + info.capacity = static_cast< uintmax_t >(-1); + info.free = static_cast< uintmax_t >(-1); + info.available = static_cast< uintmax_t >(-1); + + if (ec) + ec->clear(); - if (!error(::GetDiskFreeSpaceExW(p.c_str(), &avail, &total, &free)== 0, - p, ec, "boost::filesystem::space")) +#if defined(__wasm) + + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::space"); + +#elif defined(BOOST_POSIX_API) + + struct BOOST_STATVFS vfs; + if (!error(::BOOST_STATVFS(p.c_str(), &vfs) ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::space")) { - info.capacity - = (static_cast<boost::uintmax_t>(total.HighPart)<< 32) - + total.LowPart; - info.free - = (static_cast<boost::uintmax_t>(free.HighPart)<< 32) - + free.LowPart; - info.available - = (static_cast<boost::uintmax_t>(avail.HighPart)<< 32) - + avail.LowPart; + info.capacity = static_cast< uintmax_t >(vfs.f_blocks) * BOOST_STATVFS_F_FRSIZE; + info.free = static_cast< uintmax_t >(vfs.f_bfree) * BOOST_STATVFS_F_FRSIZE; + info.available = static_cast< uintmax_t >(vfs.f_bavail) * BOOST_STATVFS_F_FRSIZE; } -# endif +#else - else + // GetDiskFreeSpaceExW requires a directory path, which is unlike statvfs, which accepts any file. + // To work around this, test if the path refers to a directory and use the parent directory if not. + error_code local_ec; + file_status status = detail::status(p, &local_ec); + if (status.type() == fs::status_error || status.type() == fs::file_not_found) { - info.capacity = info.free = info.available = 0; + fail_local_ec: + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::space", p, local_ec)); + *ec = local_ec; + return info; } + + path dir_path = p; + if (!is_directory(status)) + { + path cur_path = detail::current_path(ec); + if (ec && *ec) + return info; + + status = detail::symlink_status(p, &local_ec); + if (status.type() == fs::status_error) + goto fail_local_ec; + if (is_symlink(status)) + { + // We need to resolve the symlink so that we report the space for the symlink target + dir_path = detail::canonical(p, cur_path, ec); + if (ec && *ec) + return info; + } + + dir_path = dir_path.parent_path(); + if (dir_path.empty()) + { + // The original path was just a filename, which is a relative path wrt. current directory + dir_path = cur_path; + } + } + + // For UNC names, the path must also include a trailing slash. + path::string_type str = dir_path.native(); + if (str.size() >= 2u && detail::is_directory_separator(str[0]) && detail::is_directory_separator(str[1]) && !detail::is_directory_separator(*(str.end() - 1))) + str.push_back(path::preferred_separator); + + ULARGE_INTEGER avail, total, free; + if (!error(::GetDiskFreeSpaceExW(str.c_str(), &avail, &total, &free) == 0, p, ec, "boost::filesystem::space")) + { + info.capacity = static_cast< uintmax_t >(total.QuadPart); + info.free = static_cast< uintmax_t >(free.QuadPart); + info.available = static_cast< uintmax_t >(avail.QuadPart); + } + +#endif + return info; - } +} + +BOOST_FILESYSTEM_DECL +file_status status(path const& p, error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) - BOOST_FILESYSTEM_DECL - file_status status(const path& p, error_code* ec) - { -# ifdef BOOST_POSIX_API +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx path_stat; + int err = invoke_statx(AT_FDCWD, p.c_str(), AT_NO_AUTOMOUNT, STATX_TYPE | STATX_MODE, &path_stat); +#else + struct ::stat path_stat; + int err = ::stat(p.c_str(), &path_stat); +#endif - struct stat path_stat; - if (::stat(p.c_str(), &path_stat)!= 0) + if (err != 0) { - if (ec != 0) // always report errno, even though some - ec->assign(errno, system_category()); // errno values are not status_errors + err = errno; + if (ec) // always report errno, even though some + ec->assign(err, system_category()); // errno values are not status_errors - if (not_found_error(errno)) - { - return fs::file_status(fs::file_not_found, fs::no_perms); - } - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", - p, error_code(errno, system_category()))); - return fs::file_status(fs::status_error); - } - if (ec != 0) ec->clear();; - if (S_ISDIR(path_stat.st_mode)) - return fs::file_status(fs::directory_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISREG(path_stat.st_mode)) - return fs::file_status(fs::regular_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISBLK(path_stat.st_mode)) - return fs::file_status(fs::block_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISCHR(path_stat.st_mode)) - return fs::file_status(fs::character_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISFIFO(path_stat.st_mode)) - return fs::file_status(fs::fifo_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISSOCK(path_stat.st_mode)) - return fs::file_status(fs::socket_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); + if (not_found_error(err)) + return fs::file_status(fs::file_not_found, fs::no_perms); + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", p, error_code(err, system_category()))); + + return fs::file_status(fs::status_error); + } + +#if defined(BOOST_FILESYSTEM_USE_STATX) + if (BOOST_UNLIKELY((path_stat.stx_mask & (STATX_TYPE | STATX_MODE)) != (STATX_TYPE | STATX_MODE))) + { + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::status"); + return fs::file_status(fs::status_error); + } +#endif + + const mode_t mode = get_mode(path_stat); + if (S_ISDIR(mode)) + return fs::file_status(fs::directory_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISREG(mode)) + return fs::file_status(fs::regular_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISBLK(mode)) + return fs::file_status(fs::block_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISCHR(mode)) + return fs::file_status(fs::character_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISFIFO(mode)) + return fs::file_status(fs::fifo_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISSOCK(mode)) + return fs::file_status(fs::socket_file, static_cast< perms >(mode) & fs::perms_mask); return fs::file_status(fs::type_unknown); -# else // Windows +#else // defined(BOOST_POSIX_API) - DWORD attr(::GetFileAttributesW(p.c_str())); - if (attr == 0xFFFFFFFF) + // We should first test if the file is a symlink or a reparse point. Resolving some reparse + // points by opening the file may fail, and status() should return file_status(reparse_file) in this case. + // Which is what symlink_status() returns. + fs::file_status st(detail::symlink_status(p, ec)); + if (st.type() == symlink_file) { - return process_status_failure(p, ec); + // Resolve the symlink + handle_wrapper h( + create_file_handle( + p.c_str(), + 0u, // dwDesiredAccess; attributes only + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // lpSecurityAttributes + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS)); + + if (h.handle == INVALID_HANDLE_VALUE) + { + return_status_failure: + return process_status_failure(p, ec); + } + + DWORD attrs; + 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_status_failure; + + attrs = info.FileAttributes; + } + else + { + BY_HANDLE_FILE_INFORMATION info; + BOOL res = ::GetFileInformationByHandle(h.handle, &info); + if (BOOST_UNLIKELY(!res)) + goto return_status_failure; + + attrs = info.dwFileAttributes; + } + + st.permissions(make_permissions(p, attrs)); + st.type((attrs & FILE_ATTRIBUTE_DIRECTORY) ? fs::directory_file : fs::regular_file); + } + + return st; + +#endif // defined(BOOST_POSIX_API) +} + +BOOST_FILESYSTEM_DECL +file_status symlink_status(path const& p, error_code* ec) +{ + if (ec) + ec->clear(); + +#if defined(BOOST_POSIX_API) + +#if defined(BOOST_FILESYSTEM_USE_STATX) + struct ::statx path_stat; + int err = invoke_statx(AT_FDCWD, p.c_str(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, STATX_TYPE | STATX_MODE, &path_stat); +#else + struct ::stat path_stat; + int err = ::lstat(p.c_str(), &path_stat); +#endif + + if (err != 0) + { + err = errno; + if (ec) // always report errno, even though some + ec->assign(err, system_category()); // errno values are not status_errors + + if (not_found_error(err)) // these are not errors + return fs::file_status(fs::file_not_found, fs::no_perms); + + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::symlink_status", p, error_code(err, system_category()))); + + return fs::file_status(fs::status_error); } - // reparse point handling; - // since GetFileAttributesW does not resolve symlinks, try to open a file - // handle to discover if the file exists - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) +#if defined(BOOST_FILESYSTEM_USE_STATX) + if (BOOST_UNLIKELY((path_stat.stx_mask & (STATX_TYPE | STATX_MODE)) != (STATX_TYPE | STATX_MODE))) { - handle_wrapper h( + emit_error(BOOST_ERROR_NOT_SUPPORTED, p, ec, "boost::filesystem::symlink_status"); + return fs::file_status(fs::status_error); + } +#endif + + const mode_t mode = get_mode(path_stat); + if (S_ISREG(mode)) + return fs::file_status(fs::regular_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISDIR(mode)) + return fs::file_status(fs::directory_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISLNK(mode)) + return fs::file_status(fs::symlink_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISBLK(mode)) + return fs::file_status(fs::block_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISCHR(mode)) + return fs::file_status(fs::character_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISFIFO(mode)) + return fs::file_status(fs::fifo_file, static_cast< perms >(mode) & fs::perms_mask); + if (S_ISSOCK(mode)) + return fs::file_status(fs::socket_file, static_cast< perms >(mode) & fs::perms_mask); + return fs::file_status(fs::type_unknown); + +#else // defined(BOOST_POSIX_API) + + 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 - if (h.handle == INVALID_HANDLE_VALUE) - { + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)); + + if (h.handle == INVALID_HANDLE_VALUE) + { + return_status_failure: return process_status_failure(p, ec); - } + } + + DWORD attrs; + fs::perms permissions; + 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_status_failure; - if (!is_reparse_point_a_symlink(p)) - return file_status(reparse_file, make_permissions(p, attr)); + attrs = info.FileAttributes; + permissions = make_permissions(p, attrs); + + if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) + return is_reparse_point_tag_a_symlink(info.ReparseTag) ? fs::file_status(fs::symlink_file, permissions) : fs::file_status(fs::reparse_file, permissions); } + else + { + BY_HANDLE_FILE_INFORMATION info; + BOOL res = ::GetFileInformationByHandle(h.handle, &info); + if (BOOST_UNLIKELY(!res)) + goto return_status_failure; - if (ec != 0) ec->clear(); - return (attr & FILE_ATTRIBUTE_DIRECTORY) - ? file_status(directory_file, make_permissions(p, attr)) - : file_status(regular_file, make_permissions(p, attr)); + attrs = info.dwFileAttributes; + permissions = make_permissions(p, attrs); -# endif - } + if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) + return is_reparse_point_a_symlink_ioctl(h.handle) ? fs::file_status(fs::symlink_file, permissions) : fs::file_status(fs::reparse_file, permissions); + } - BOOST_FILESYSTEM_DECL - file_status symlink_status(const path& p, error_code* ec) - { -# ifdef BOOST_POSIX_API + return (attrs & FILE_ATTRIBUTE_DIRECTORY) ? fs::file_status(fs::directory_file, permissions) : fs::file_status(fs::regular_file, permissions); - struct stat path_stat; - if (::lstat(p.c_str(), &path_stat)!= 0) - { - if (ec != 0) // always report errno, even though some - ec->assign(errno, system_category()); // errno values are not status_errors +#endif // defined(BOOST_POSIX_API) +} - if (errno == ENOENT || errno == ENOTDIR) // these are not errors - { - return fs::file_status(fs::file_not_found, fs::no_perms); - } - if (ec == 0) - BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::status", - p, error_code(errno, system_category()))); - return fs::file_status(fs::status_error); - } - if (ec != 0) ec->clear(); - if (S_ISREG(path_stat.st_mode)) - return fs::file_status(fs::regular_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISDIR(path_stat.st_mode)) - return fs::file_status(fs::directory_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISLNK(path_stat.st_mode)) - return fs::file_status(fs::symlink_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISBLK(path_stat.st_mode)) - return fs::file_status(fs::block_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISCHR(path_stat.st_mode)) - return fs::file_status(fs::character_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISFIFO(path_stat.st_mode)) - return fs::file_status(fs::fifo_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - if (S_ISSOCK(path_stat.st_mode)) - return fs::file_status(fs::socket_file, - static_cast<perms>(path_stat.st_mode) & fs::perms_mask); - return fs::file_status(fs::type_unknown); +// contributed by Jeff Flinn +BOOST_FILESYSTEM_DECL +path temp_directory_path(system::error_code* ec) +{ + if (ec) + ec->clear(); + +#ifdef BOOST_POSIX_API + const char* val = NULL; + + (val = std::getenv("TMPDIR")) || + (val = std::getenv("TMP")) || + (val = std::getenv("TEMP")) || + (val = std::getenv("TEMPDIR")); -# else // Windows - - DWORD attr(::GetFileAttributesW(p.c_str())); - if (attr == 0xFFFFFFFF) - { - return process_status_failure(p, ec); - } - - if (ec != 0) ec->clear(); - - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) - return is_reparse_point_a_symlink(p) - ? file_status(symlink_file, make_permissions(p, attr)) - : file_status(reparse_file, make_permissions(p, attr)); - - return (attr & FILE_ATTRIBUTE_DIRECTORY) - ? file_status(directory_file, make_permissions(p, attr)) - : file_status(regular_file, make_permissions(p, attr)); - -# endif - } - - // contributed by Jeff Flinn - BOOST_FILESYSTEM_DECL - path temp_directory_path(system::error_code* ec) - { -# ifdef BOOST_POSIX_API - const char* val = 0; - - (val = std::getenv("TMPDIR" )) || - (val = std::getenv("TMP" )) || - (val = std::getenv("TEMP" )) || - (val = std::getenv("TEMPDIR")); - -# ifdef __ANDROID__ - const char* default_tmp = "/data/local/tmp"; -# else - const char* default_tmp = "/tmp"; -# endif - path p((val!=0) ? val : default_tmp); - - if (p.empty() || (ec&&!is_directory(p, *ec))||(!ec&&!is_directory(p))) - { +#ifdef __ANDROID__ + const char* default_tmp = "/data/local/tmp"; +#else + const char* default_tmp = "/tmp"; +#endif + path p((val != NULL) ? val : default_tmp); + + if (BOOST_UNLIKELY(p.empty())) + { + fail_not_dir: error(ENOTDIR, p, ec, "boost::filesystem::temp_directory_path"); return p; - } - - return p; - -# else // Windows - - const wchar_t* tmp_env = L"TMP"; - const wchar_t* temp_env = L"TEMP"; - const wchar_t* localappdata_env = L"LOCALAPPDATA"; - const wchar_t* userprofile_env = L"USERPROFILE"; - const wchar_t* env_list[] - = {tmp_env, temp_env, localappdata_env, userprofile_env, 0}; - - path p; - for (int i = 0; env_list[i]; ++i) - { + } + + file_status status = detail::status(p, ec); + if (BOOST_UNLIKELY(ec && *ec)) + return path(); + if (BOOST_UNLIKELY(!is_directory(status))) + goto fail_not_dir; + + return p; + +#else // Windows +#if !defined(UNDER_CE) + + const wchar_t* tmp_env = L"TMP"; + const wchar_t* temp_env = L"TEMP"; + const wchar_t* localappdata_env = L"LOCALAPPDATA"; + const wchar_t* userprofile_env = L"USERPROFILE"; + const wchar_t* env_list[] = { tmp_env, temp_env, localappdata_env, userprofile_env }; + + path p; + for (unsigned int i = 0; i < sizeof(env_list) / sizeof(*env_list); ++i) + { std::wstring env = wgetenv(env_list[i]); if (!env.empty()) { - p = env; - if (i >= 2) - p /= L"Temp"; - error_code lcl_ec; - if (exists(p, lcl_ec) && !lcl_ec && is_directory(p, lcl_ec) && !lcl_ec) - break; - p.clear(); + p = env; + if (i >= 2) + p /= L"Temp"; + error_code lcl_ec; + if (exists(p, lcl_ec) && !lcl_ec && is_directory(p, lcl_ec) && !lcl_ec) + break; + p.clear(); } - } - - if (p.empty()) - { - // use vector since in C++03 a string is not required to be contiguous - std::vector<wchar_t> buf(::GetWindowsDirectoryW(NULL, 0)); + } - if (buf.empty() - || ::GetWindowsDirectoryW(&buf[0], static_cast<UINT>(buf.size())) == 0) + if (p.empty()) + { + // use a separate buffer since in C++03 a string is not required to be contiguous + const UINT size = ::GetWindowsDirectoryW(NULL, 0); + if (BOOST_UNLIKELY(size == 0)) { - error(::GetLastError(), ec, "boost::filesystem::temp_directory_path"); - return path(); + getwindir_error: + int errval = ::GetLastError(); + error(errval, ec, "boost::filesystem::temp_directory_path"); + return path(); } - p = &*buf.begin(); // do not depend on buf.size(); see ticket #10388 + + boost::scoped_array< wchar_t > buf(new wchar_t[size]); + if (BOOST_UNLIKELY(::GetWindowsDirectoryW(buf.get(), size) == 0)) + goto getwindir_error; + + p = buf.get(); // do not depend on initial buf size, see ticket #10388 p /= L"Temp"; - } - return p; - -# endif - } - - BOOST_FILESYSTEM_DECL - path system_complete(const path& p, system::error_code* ec) - { -# ifdef BOOST_POSIX_API - (void)ec; - return (p.empty() || p.is_absolute()) - ? p : current_path()/ p; - -# else + } + + return p; + +#else // Windows CE + + // Windows CE has no environment variables, so the same code as used for + // regular Windows, above, doesn't work. + + DWORD size = ::GetTempPathW(0, NULL); + if (size == 0u) + { + fail: + int errval = ::GetLastError(); + error(errval, ec, "boost::filesystem::temp_directory_path"); + return path(); + } + + boost::scoped_array< wchar_t > buf(new wchar_t[size]); + if (::GetTempPathW(size, buf.get()) == 0) + goto fail; + + path p(buf.get()); + p.remove_trailing_separator(); + + file_status status = detail::status(p, ec); + if (ec && *ec) + return path(); + if (!is_directory(status)) + { + error(ERROR_PATH_NOT_FOUND, p, ec, "boost::filesystem::temp_directory_path"); + return path(); + } + + return p; + +#endif // !defined(UNDER_CE) +#endif +} + +BOOST_FILESYSTEM_DECL +path system_complete(path const& p, system::error_code* ec) +{ +#ifdef BOOST_POSIX_API + + return (p.empty() || p.is_absolute()) ? p : current_path() / p; + +#else if (p.empty()) { - if (ec != 0) ec->clear(); - return p; + if (ec) + ec->clear(); + return p; } + + BOOST_CONSTEXPR_OR_CONST std::size_t buf_size = 128u; wchar_t buf[buf_size]; wchar_t* pfn; std::size_t len = get_full_path_name(p, buf_size, buf, &pfn); if (error(len == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::system_complete")) - return path(); + return path(); - if (len < buf_size)// len does not include null termination character - return path(&buf[0]); + if (len < buf_size) // len does not include null termination character + return path(&buf[0]); - boost::scoped_array<wchar_t> big_buf(new wchar_t[len]); + boost::scoped_array< wchar_t > big_buf(new wchar_t[len]); - return error(get_full_path_name(p, len , big_buf.get(), &pfn)== 0 ? BOOST_ERRNO : 0, - p, ec, "boost::filesystem::system_complete") - ? path() - : path(big_buf.get()); -# endif - } + return error(get_full_path_name(p, len, big_buf.get(), &pfn) == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::system_complete") ? path() : path(big_buf.get()); +#endif +} - BOOST_FILESYSTEM_DECL - path weakly_canonical(const path& p, system::error_code* ec) - { - path head(p); - path tail; - system::error_code tmp_ec; - path::iterator itr = p.end(); +BOOST_FILESYSTEM_DECL +path weakly_canonical(path const& p, path const& base, system::error_code* ec) +{ + system::error_code local_ec; + const path::iterator p_end(p.end()); +#if defined(BOOST_POSIX_API) + + path::iterator itr(p_end); + path head(p); for (; !head.empty(); --itr) { - file_status head_status = status(head, tmp_ec); - if (error(head_status.type() == fs::status_error, - head, ec, "boost::filesystem::weakly_canonical")) - return path(); - if (head_status.type() != fs::file_not_found) - break; - head.remove_filename(); + file_status head_status = detail::status(head, &local_ec); + if (BOOST_UNLIKELY(head_status.type() == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::weakly_canonical", head, local_ec)); + + *ec = local_ec; + return path(); + } + + if (head_status.type() != fs::file_not_found) + break; + + head.remove_filename(); } - bool tail_has_dots = false; - for (; itr != p.end(); ++itr) - { - tail /= *itr; - // for a later optimization, track if any dot or dot-dot elements are present - if (itr->native().size() <= 2 - && itr->native()[0] == dot - && (itr->native().size() == 1 || itr->native()[1] == dot)) - tail_has_dots = true; - } - - if (head.empty()) - return p.lexically_normal(); - head = canonical(head, tmp_ec); - if (error(tmp_ec.value(), head, ec, "boost::filesystem::weakly_canonical")) - return path(); - return tail.empty() - ? head - : (tail_has_dots // optimization: only normalize if tail had dot or dot-dot element - ? (head/tail).lexically_normal() - : head/tail); - } -} // namespace detail +#else -//--------------------------------------------------------------------------------------// -// // -// directory_entry // -// // -//--------------------------------------------------------------------------------------// + // On Windows, filesystem APIs such as GetFileAttributesW perform lexical path normalization internally. + // As a result, a path like "c:\a\.." can be reported as present even if "c:\a" is not. This would break + // canonical, as symlink_status that it calls internally would report an error that the file at the intermediate + // path does not exist. To avoid this, scan the initial path in the forward direction. + // Also, operate on paths with preferred separators. This can be important on Windows since GetFileAttributesW, + // which is called in status() may return "file not found" for paths to network shares and mounted cloud + // storages that have forward slashes as separators. + path::iterator itr(p.begin()); + path head; + for (; itr != p_end; ++itr) + { + path const& p_elem = *itr; + if (p_elem.size() == 1u && detail::is_directory_separator(p_elem.native()[0])) + { + // Convert generic separator returned by the iterator for the root directory to + // the preferred separator. + head += path::preferred_separator; + } + else + { + head /= p_elem; + } - file_status - directory_entry::m_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; - } - - file_status - directory_entry::m_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 -// <boost/filesystem/path_traits.hpp>, 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 -} // namespace filesystem -} // namespace boost + file_status head_status = detail::status(head, &local_ec); + if (BOOST_UNLIKELY(head_status.type() == fs::status_error)) + { + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::weakly_canonical", head, local_ec)); -//--------------------------------------------------------------------------------------// -// // -// directory_iterator // -// // -//--------------------------------------------------------------------------------------// + *ec = local_ec; + return path(); + } -namespace -{ -# ifdef BOOST_POSIX_API - - error_code path_max(std::size_t & result) - // this code is based on Stevens and Rago, Advanced Programming in the - // UNIX envirnment, 2nd Ed., ISBN 0-201-43307-9, page 49 - { -# ifdef PATH_MAX - static std::size_t max = PATH_MAX; -# else - static std::size_t max = 0; -# endif - if (max == 0) - { - errno = 0; - long tmp = ::pathconf("/", _PC_NAME_MAX); - if (tmp < 0) - { - if (errno == 0)// indeterminate - max = 4096; // guess - else return error_code(errno, system_category()); - } - else max = static_cast<std::size_t>(tmp + 1); // relative root - } - result = max; - return ok; - } - -#if defined(__PGI) && defined(__USE_FILE_OFFSET64) -#define dirent dirent64 -#endif - - error_code dir_itr_first(void *& handle, void *& buffer, - const char* dir, string& target, - fs::file_status &, fs::file_status &) - { - if ((handle = ::opendir(dir))== 0) - return error_code(errno, system_category()); - target = string("."); // string was static but caused trouble - // when iteration called from dtor, after - // static had already been destroyed - std::size_t path_size (0); // initialization quiets gcc warning (ticket #3509) - error_code ec = path_max(path_size); - if (ec)return ec; - dirent de; - buffer = std::malloc((sizeof(dirent) - sizeof(de.d_name)) - + path_size + 1); // + 1 for "/0" - return ok; - } - - // warning: the only dirent member updated is d_name - inline int readdir_r_simulator(DIR * dirp, struct dirent * entry, - struct dirent ** result)// *result set to 0 on end of directory - { -# if !defined(__CYGWIN__)\ - && defined(_POSIX_THREAD_SAFE_FUNCTIONS)\ - && defined(_SC_THREAD_SAFE_FUNCTIONS)\ - && (_POSIX_THREAD_SAFE_FUNCTIONS+0 >= 0)\ - && (!defined(__hpux) || defined(_REENTRANT)) \ - && (!defined(_AIX) || defined(__THREAD_SAFE)) - - errno = 0; - - if (::sysconf(_SC_THREAD_SAFE_FUNCTIONS)>= 0) - { return ::readdir_r(dirp, entry, result); } -# endif - - errno = 0; - - struct dirent * p; - *result = 0; - if ((p = ::readdir(dirp))== 0) - return errno; - std::strcpy(entry->d_name, p->d_name); - *result = entry; - return 0; - } - - error_code dir_itr_increment(void *& handle, void *& buffer, - string& target, fs::file_status & sf, fs::file_status & symlink_sf) - { - BOOST_ASSERT(buffer != 0); - dirent * entry(static_cast<dirent *>(buffer)); - dirent * result; - int return_code; - if ((return_code = readdir_r_simulator(static_cast<DIR*>(handle), entry, &result))!= 0) - return error_code(errno, system_category()); - if (result == 0) - return fs::detail::dir_itr_close(handle, buffer); - target = entry->d_name; -# ifdef BOOST_FILESYSTEM_STATUS_CACHE - if (entry->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 (entry->d_type == DT_DIR) - sf = symlink_sf = fs::file_status(fs::directory_file); - else if (entry->d_type == DT_REG) - sf = symlink_sf = fs::file_status(fs::regular_file); - else if (entry->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 ok; - } - -# else // BOOST_WINDOWS_API - - error_code dir_itr_first(void *& handle, const fs::path& dir, - 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 - 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 - return error_code( (::GetLastError() == ERROR_FILE_NOT_FOUND - // Windows Mobile returns ERROR_NO_MORE_FILES; see ticket #3551 - || ::GetLastError() == ERROR_NO_MORE_FILES) - ? 0 : ::GetLastError(), 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); + if (head_status.type() == fs::file_not_found) + { + head.remove_filename(); + break; + } } - else + +#endif + + path const& dot_p = dot_path(); + path const& dot_dot_p = dot_dot_path(); + path tail; + bool tail_has_dots = false; + for (; itr != p_end; ++itr) { - 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, wstring& target, - fs::file_status & sf, fs::file_status & symlink_sf) - { - WIN32_FIND_DATAW data; - if (::FindNextFileW(handle, &data)== 0)// fails - { - int 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); + path const& tail_elem = *itr; +#if defined(BOOST_WINDOWS_API) + if (tail_elem.size() == 1u && detail::is_directory_separator(tail_elem.native()[0])) + { + // Convert generic separator returned by the iterator for the root directory to + // the preferred separator. + tail += path::preferred_separator; + continue; + } +#endif + tail /= tail_elem; + // for a later optimization, track if any dot or dot-dot elements are present + if (!tail_has_dots && (tail_elem == dot_p || tail_elem == dot_dot_p)) + tail_has_dots = true; } - else + + if (head.empty()) + return p.lexically_normal(); + + head = detail::canonical(head, base, &local_ec); + if (BOOST_UNLIKELY(!!local_ec)) { - 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 - - const error_code not_found_error_code ( -# ifdef BOOST_WINDOWS_API - ERROR_PATH_NOT_FOUND -# else - ENOENT -# endif - , system_category()); - -} // unnamed namespace - -namespace boost -{ -namespace filesystem -{ - -namespace detail -{ - // 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 - ) - { -# ifdef BOOST_POSIX_API - std::free(buffer); - buffer = 0; - if (handle == 0)return ok; - DIR * h(static_cast<DIR*>(handle)); - handle = 0; - return error_code(::closedir(h)== 0 ? 0 : errno, system_category()); - -# else - if (handle != 0) - { - ::FindClose(handle); - handle = 0; - } - return ok; - -# endif - } - - void directory_iterator_construct(directory_iterator& it, - const path& p, system::error_code* ec) - { - if (error(p.empty() ? not_found_error_code.value() : 0, p, ec, - "boost::filesystem::directory_iterator::construct")) - return; - - path::string_type filename; - file_status file_stat, symlink_file_stat; - error_code result = dir_itr_first(it.m_imp->handle, -# if defined(BOOST_POSIX_API) - it.m_imp->buffer, -# endif - p.c_str(), filename, file_stat, symlink_file_stat); - - if (result) - { - it.m_imp.reset(); - error(result.value(), p, - ec, "boost::filesystem::directory_iterator::construct"); - return; - } - - if (it.m_imp->handle == 0) - it.m_imp.reset(); // eof, so make end iterator - else // not eof - { - it.m_imp->dir_entry.assign(p / filename, file_stat, symlink_file_stat); - if (filename[0] == dot // dot or dot-dot - && (filename.size()== 1 - || (filename[1] == dot - && filename.size()== 2))) - { detail::directory_iterator_increment(it, ec); } - } - } - - void directory_iterator_increment(directory_iterator& it, - system::error_code* ec) - { - BOOST_ASSERT_MSG(it.m_imp.get(), "attempt to increment end iterator"); - BOOST_ASSERT_MSG(it.m_imp->handle != 0, "internal program error"); - - path::string_type filename; - file_status file_stat, symlink_file_stat; - system::error_code temp_ec; - - for (;;) - { - temp_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 (temp_ec) // happens if filesystem is corrupt, such as on a damaged optical disc - { - path error_path(it.m_imp->dir_entry.path().parent_path()); // fix ticket #5900 - it.m_imp.reset(); - if (ec == 0) - BOOST_FILESYSTEM_THROW( - filesystem_error("boost::filesystem::directory_iterator::operator++", - error_path, - error_code(BOOST_ERRNO, system_category()))); - ec->assign(BOOST_ERRNO, system_category()); - return; - } - else if (ec != 0) ec->clear(); + if (!ec) + BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::weakly_canonical", head, local_ec)); - if (it.m_imp->handle == 0) // eof, make end - { - it.m_imp.reset(); - return; - } - - if (!(filename[0] == dot // !(dot or dot-dot) - && (filename.size()== 1 - || (filename[1] == dot - && filename.size()== 2)))) - { - it.m_imp->dir_entry.replace_filename( - filename, file_stat, symlink_file_stat); - return; - } + *ec = local_ec; + return path(); + } + + if (BOOST_LIKELY(!tail.empty())) + { + head /= tail; + + // optimization: only normalize if tail had dot or dot-dot element + if (tail_has_dots) + return head.lexically_normal(); } - } -} // namespace detail + + return head; +} + +} // namespace detail } // namespace filesystem } // namespace boost |