diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 90a303f..17bb3d6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -10,3 +10,8 @@ add_executable(fs_du du.cpp) target_link_libraries(fs_du ghc_filesystem) AddExecutableWithStdFS(std_fs_du du.cpp) +if(EXISTS "${PROJECT_SOURCE_DIR}/examples/benchmark.cpp") + add_executable(fs_benchmark benchmark.cpp) + set_property(TARGET fs_benchmark PROPERTY CXX_STANDARD 17) + target_link_libraries(fs_benchmark ghc_filesystem) +endif() \ No newline at end of file diff --git a/include/ghc/filesystem.hpp b/include/ghc/filesystem.hpp index ec707ed..6987ba4 100644 --- a/include/ghc/filesystem.hpp +++ b/include/ghc/filesystem.hpp @@ -77,17 +77,17 @@ #endif #define GHC_OS_DETECTED #if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# if _MSVC_LANG == 201703L -# define GHC_FILESYSTEM_RUNNING_CPP17 -# else -# define GHC_FILESYSTEM_RUNNING_CPP20 -# endif +#if _MSVC_LANG == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif #elif (defined(__cplusplus) && __cplusplus >= 201703L) -# if __cplusplus == 201703L -# define GHC_FILESYSTEM_RUNNING_CPP17 -# else -# define GHC_FILESYSTEM_RUNNING_CPP20 -# endif +#if __cplusplus == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif #endif #endif @@ -130,12 +130,12 @@ #else #include #include +#include #include #include #include #include #include -#include #ifdef GHC_OS_ANDROID #include #if __ANDROID_API__ < 12 @@ -225,18 +225,27 @@ // LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) #define LWG_2937_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// UTF8-Everywhere is the original behaviour of ghc::filesystem. With this define you can -// enable the more standard conforming implementation option that uses wstring on Windows -// as ghc::filesystem::string_type. -// #define GHC_WIN_WSTRING_STRING_TYPE +// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the windows +// version defaults to std::wstring storage backend. Still all std::string will be interpreted +// as UTF-8 encoded. With this define you can enfoce the old behavior on Windows, using +// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This +// needs more conversions so it is (an was before v1.5) slower, bot might help keeping source +// homogenous in a multi platform project. +// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, // instead of replacing them with the unicode replacement character (U+FFFD). // #define GHC_RAISE_UNICODE_ERRORS //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. +// instead of replacing them with the unicode replacement character (U+FFFD). +#ifndef GHC_WIN_DISABLE_AUTO_PREFIXES +#define GHC_WIN_AUTO_PREFIX_LONG_PATH +#endif // GHC_WIN_DISABLE_AUTO_PREFIXES +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) -#define GHC_FILESYSTEM_VERSION 10401L +#define GHC_FILESYSTEM_VERSION 10499L #if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) #define GHC_WITH_EXCEPTIONS @@ -258,7 +267,7 @@ public: } }; -template +template class path_helper_base { public: @@ -270,12 +279,11 @@ public: #endif }; -#if __cplusplus < 201703L +#if __cplusplus < 201703L template constexpr char_type path_helper_base::preferred_separator; #endif - #ifdef GHC_OS_WINDOWS class path; namespace detail { @@ -283,16 +291,19 @@ bool has_executable_extension(const path& p); } #endif - - // 30.10.8 class path +// 30.10.8 class path class GHC_FS_API_CLASS path -#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_WSTRING_STRING_TYPE) +#if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) #define GHC_USE_WCHAR_T +#define GHC_NATIVEWP(p) p.c_str() +#define GHC_PLATFORM_LITERAL(str) L##str : private path_helper_base { public: using path_helper_base::value_type; #else +#define GHC_NATIVEWP(p) p.wstring().c_str() +#define GHC_PLATFORM_LITERAL(str) str : private path_helper_base { public: @@ -300,7 +311,7 @@ public: #endif using string_type = std::basic_string; using path_helper_base::preferred_separator; - + // 30.10.10.1 enumeration format /// The path format in wich the constructor argument is given. enum format { @@ -319,6 +330,10 @@ public: struct _is_basic_string> : std::true_type { }; + template + struct _is_basic_string, std::allocator>> : std::true_type + { + }; #ifdef __cpp_lib_string_view template struct _is_basic_string> : std::true_type @@ -405,8 +420,8 @@ public: void swap(path& rhs) noexcept; // 30.10.8.4.6 native format observers - const string_type& native() const; // this implementation doesn't support noexcept for native() - const value_type* c_str() const; // this implementation doesn't support noexcept for c_str() + const string_type& native() const noexcept; + const value_type* c_str() const noexcept; operator string_type() const; template , class Allocator = std::allocator> std::basic_string string(const Allocator& a = Allocator()) const; @@ -423,7 +438,7 @@ public: // 30.10.8.4.7 generic format observers template , class Allocator = std::allocator> std::basic_string generic_string(const Allocator& a = Allocator()) const; - const std::string& generic_string() const; // this is different from the standard, that returns by value + std::string generic_string() const; std::wstring generic_wstring() const; #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) std::u8string generic_u8string() const; @@ -476,10 +491,10 @@ public: iterator end() const; private: - using impl_value_type = std::string::value_type; + using impl_value_type = value_type; using impl_string_type = std::basic_string; friend class directory_iterator; - void append_name(const char* name); + void append_name(const value_type* name); static constexpr impl_value_type generic_separator = '/'; template class input_iterator_range @@ -504,15 +519,21 @@ private: }; friend void swap(path& lhs, path& rhs) noexcept; friend size_t hash_value(const path& p) noexcept; + friend path canonical(const path& p, std::error_code& ec); string_type::size_type root_name_length() const noexcept; - static void postprocess_path_with_format(impl_string_type& p, format fmt); + void postprocess_path_with_format(format fmt); + void check_long_path(); impl_string_type _path; #ifdef GHC_OS_WINDOWS + void handle_prefixes(); friend bool detail::has_executable_extension(const path& p); - impl_string_type native_impl() const; - mutable string_type _native_cache; +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + string_type::size_type _prefixLength{0}; +#else // GHC_WIN_AUTO_PREFIX_LONG_PATH + static const string_type::size_type _prefixLength{0}; +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH #else - const impl_string_type& native_impl() const; + static const string_type::size_type _prefixLength{0}; #endif }; @@ -520,7 +541,7 @@ private: GHC_FS_API void swap(path& lhs, path& rhs) noexcept; GHC_FS_API size_t hash_value(const path& p) noexcept; #ifdef GHC_HAS_THREEWAY_COMP -GHC_FS_API std::strong_ordering operator<=>( const path& lhs, const path& rhs ) noexcept; +GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; #endif GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; @@ -575,7 +596,7 @@ public: using iterator_category = std::bidirectional_iterator_tag; iterator(); - iterator(const impl_string_type::const_iterator& first, const impl_string_type::const_iterator& last, const impl_string_type::const_iterator& pos); + iterator(const path& p, const impl_string_type::const_iterator& pos); iterator& operator++(); iterator operator++(int); iterator& operator--(); @@ -587,11 +608,12 @@ public: private: friend class path; - impl_string_type::const_iterator increment(const std::string::const_iterator& pos) const; - impl_string_type::const_iterator decrement(const std::string::const_iterator& pos) const; + impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; + impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; void updateCurrent(); impl_string_type::const_iterator _first; impl_string_type::const_iterator _last; + impl_string_type::const_iterator _prefix; impl_string_type::const_iterator _root; impl_string_type::const_iterator _iter; path _current; @@ -1251,7 +1273,7 @@ GHC_INLINE std::error_code make_system_error(int err) return std::error_code(err ? err : errno, std::system_category()); } #endif - + #endif // GHC_EXPAND_IMPL template @@ -1374,7 +1396,7 @@ GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t frag codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); } - + GHC_INLINE bool validUtf8(const std::string& utf8String) { std::string::const_iterator iter = utf8String.begin(); @@ -1392,9 +1414,9 @@ GHC_INLINE bool validUtf8(const std::string& utf8String) } } // namespace detail - + #endif - + namespace detail { template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> @@ -1476,13 +1498,13 @@ inline StringType fromUtf8(const Utf8String& utf8String, const typename StringTy return result; } -template +template inline StringType fromUtf8(const charT (&utf8String)[N]) { #ifdef __cpp_lib_string_view - return fromUtf8(std::basic_string_view(utf8String, N-1)); + return fromUtf8(std::basic_string_view(utf8String, N - 1)); #else - return fromUtf8(std::basic_string(utf8String, N-1)); + return fromUtf8(std::basic_string(utf8String, N - 1)); #endif } @@ -1508,7 +1530,7 @@ inline std::string toUtf8(const strT& unicodeString) throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); #else appendUTF8(result, 0xfffd); - if(iter == unicodeString.end()) { + if (iter == unicodeString.end()) { break; } #endif @@ -1537,70 +1559,123 @@ inline std::string toUtf8(const charT* unicodeString) return toUtf8(std::basic_string>(unicodeString)); } +#ifdef GHC_USE_WCHAR_T +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return StringType(temp.begin(), temp.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(wString.begin(), wString.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return fromUtf8(temp); +} + +template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return fromUtf8(unicodeString); +} + +template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return std::wstring(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + auto temp = toUtf8(unicodeString); + return fromUtf8(temp); +} + +template +inline std::wstring toWChar(const charT* unicodeString) +{ + return toWChar(std::basic_string>(unicodeString)); +} +#endif // GHC_USE_WCHAR_T + } // namespace detail #ifdef GHC_EXPAND_IMPL namespace detail { -GHC_INLINE bool startsWith(const std::string& what, const std::string& with) +template ::value, bool>::type = true> +GHC_INLINE bool startsWith(const strT& what, const strT& with) { return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); } -GHC_INLINE bool endsWith(const std::string& what, const std::string& with) +template ::value, bool>::type = true> +GHC_INLINE bool endsWith(const strT& what, const strT& with) { - return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with); + return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; } } // namespace detail -GHC_INLINE void path::postprocess_path_with_format(path::impl_string_type& p, path::format fmt) +GHC_INLINE void path::check_long_path() +{ +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + postprocess_path_with_format(native_format); + } +#endif +} + +GHC_INLINE void path::postprocess_path_with_format(path::format fmt) { #ifdef GHC_RAISE_UNICODE_ERRORS - if(!detail::validUtf8(p)) { + if (!detail::validUtf8(_path)) { path t; - t._path = p; + t._path = _path; throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); } #endif switch (fmt) { -#ifndef GHC_OS_WINDOWS +#ifdef GHC_OS_WINDOWS + case path::native_format: + case path::auto_format: + case path::generic_format: + for (auto& c : _path) { + if (c == generic_separator) { + c = preferred_separator; + } + } +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; + } +#endif + handle_prefixes(); + break; +#else case path::auto_format: case path::native_format: -#endif case path::generic_format: // nothing to do break; -#ifdef GHC_OS_WINDOWS - case path::auto_format: - case path::native_format: - if (p.length() >= 4 && p[2] == '?') { - if (p.length() == 4 || (p.length() >= 6 && p[5] == ':')) { - if (detail::startsWith(p, std::string("\\\\?\\"))) { - // remove Windows long filename marker for simple paths - p.erase(0, 4); - } - else if (detail::startsWith(p, std::string("\\??\\"))) { - p.erase(0, 4); - } - } - } - for (auto& c : p) { - if (c == '\\') { - c = '/'; - } - } - break; #endif } - if (p.length() > 2 && p[0] == '/' && p[1] == '/' && p[2] != '/') { - std::string::iterator new_end = std::unique(p.begin() + 2, p.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == '/'; }); - p.erase(new_end, p.end()); + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); } else { - std::string::iterator new_end = std::unique(p.begin(), p.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == '/'; }); - p.erase(new_end, p.end()); + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); } } @@ -1608,9 +1683,13 @@ GHC_INLINE void path::postprocess_path_with_format(path::impl_string_type& p, pa template inline path::path(const Source& source, format fmt) +#ifdef GHC_USE_WCHAR_T + : _path(detail::toWChar(source)) +#else : _path(detail::toUtf8(source)) +#endif { - postprocess_path_with_format(_path, fmt); + postprocess_path_with_format(fmt); } template @@ -1635,7 +1714,7 @@ inline path::path(InputIterator first, InputIterator last, format fmt) namespace detail { -GHC_INLINE bool equals_simple_insensitive(const char* str1, const char* str2) +GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) { #ifdef GHC_OS_WINDOWS #ifdef __GNUC__ @@ -1644,24 +1723,30 @@ GHC_INLINE bool equals_simple_insensitive(const char* str1, const char* str2) return true; } return false; -#else +#else // __GNUC__ +#ifdef GHC_USE_WCHAR_T + return 0 == ::_wcsicmp(str1, str2); +#else // GHC_USE_WCHAR_T return 0 == ::_stricmp(str1, str2); -#endif -#else +#endif // GHC_USE_WCHAR_T +#endif // __GNUC__ +#else // GHC_OS_WINDOWS return 0 == ::strcasecmp(str1, str2); -#endif +#endif // GHC_OS_WINDOWS } -GHC_INLINE int compare_simple_insensitive(const char* str1, size_t len1, const char* str2, size_t len2) +GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) { - while(len1 > 0 && len2 > 0 && ::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2)) { - --len1; --len2; - ++str1; ++str2; + while (len1 > 0 && len2 > 0 && ::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2)) { + --len1; + --len2; + ++str1; + ++str2; } if (len1 && len2) { return *str1 < *str2 ? -1 : 1; } - if(len1 == 0 && len2 == 0) { + if (len1 == 0 && len2 == 0) { return 0; } return len1 == 0 ? -1 : 1; @@ -1674,7 +1759,7 @@ GHC_INLINE const char* strerror_adapter(char* gnu, char*) GHC_INLINE const char* strerror_adapter(int posix, char* buffer) { - if(posix) { + if (posix) { return "Error in strerror_r!"; } return buffer; @@ -1741,7 +1826,7 @@ GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlin #pragma GCC diagnostic pop #endif if (api_call) { - if (api_call(detail::fromUtf8(new_hardlink.u8string()).c_str(), detail::fromUtf8(target_name.u8string()).c_str(), NULL) == 0) { + if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { ec = detail::make_system_error(); } } @@ -1749,6 +1834,21 @@ GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlin ec = detail::make_system_error(ERROR_NOT_SUPPORTED); } } + +GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) +{ + ULONG size = ::GetFullPathNameW(p, 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); + if (s2 && s2 < size) { + return path(std::wstring(buf.data(), s2)); + } + } + ec = detail::make_system_error(); + return path(); +} + #else GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) { @@ -1850,7 +1950,7 @@ GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) #endif #endif - std::shared_ptr file(CreateFileW(p.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + std::shared_ptr file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); if (file.get() == INVALID_HANDLE_VALUE) { ec = detail::make_system_error(); return path(); @@ -1862,12 +1962,21 @@ GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { if (IsReparseTagMicrosoft(reparseData->ReparseTag)) { switch (reparseData->ReparseTag) { - case IO_REPARSE_TAG_SYMLINK: - result = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + case IO_REPARSE_TAG_SYMLINK: { + auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); + auto substituteName = + std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { + result = printName; + } + else { + result = substituteName; + } if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { result = p.parent_path() / result; } break; + } case IO_REPARSE_TAG_MOUNT_POINT: result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); break; @@ -1974,7 +2083,7 @@ GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uin #ifdef GHC_OS_WINDOWS file_status fs; WIN32_FILE_ATTRIBUTE_DATA attr; - if (!GetFileAttributesExW(detail::fromUtf8(p.u8string()).c_str(), GetFileExInfoStandard, &attr)) { + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); } else { @@ -2019,7 +2128,7 @@ GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status return file_status(file_type::unknown); } WIN32_FILE_ATTRIBUTE_DATA attr; - if (!::GetFileAttributesExW(p.wstring().c_str(), GetFileExInfoStandard, &attr)) { + if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); } else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { @@ -2120,22 +2229,24 @@ GHC_INLINE path::path() noexcept {} GHC_INLINE path::path(const path& p) : _path(p._path) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif { } GHC_INLINE path::path(path&& p) noexcept : _path(std::move(p._path)) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif { } GHC_INLINE path::path(string_type&& source, format fmt) -#ifdef GHC_USE_WCHAR_T - : _path(detail::toUtf8(source)) -#else : _path(std::move(source)) -#endif { - postprocess_path_with_format(_path, fmt); + postprocess_path_with_format(fmt); } #endif // GHC_EXPAND_IMPL @@ -2172,12 +2283,18 @@ GHC_INLINE path::~path() {} GHC_INLINE path& path::operator=(const path& p) { _path = p._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif return *this; } GHC_INLINE path& path::operator=(path&& p) noexcept { _path = std::move(p._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif return *this; } @@ -2188,12 +2305,8 @@ GHC_INLINE path& path::operator=(path::string_type&& source) GHC_INLINE path& path::assign(path::string_type&& source) { -#ifdef GHC_USE_WCHAR_T - _path = detail::toUtf8(source); -#else _path = std::move(source); -#endif - postprocess_path_with_format(_path, native_format); + postprocess_path_with_format(native_format); return *this; } @@ -2208,8 +2321,12 @@ inline path& path::operator=(const Source& source) template inline path& path::assign(const Source& source) { +#ifdef GHC_USE_WCHAR_T + _path.assign(detail::toWChar(source)); +#else _path.assign(detail::toUtf8(source)); - postprocess_path_with_format(_path, native_format); +#endif + postprocess_path_with_format(native_format); return *this; } @@ -2217,6 +2334,9 @@ template <> inline path& path::assign(const path& source) { _path = source._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = source._prefixLength; +#endif return *this; } @@ -2224,7 +2344,7 @@ template inline path& path::assign(InputIterator first, InputIterator last) { _path.assign(first, last); - postprocess_path_with_format(_path, native_format); + postprocess_path_with_format(native_format); return *this; } @@ -2237,8 +2357,8 @@ GHC_INLINE path& path::operator/=(const path& p) { if (p.empty()) { // was: if ((!has_root_directory() && is_absolute()) || has_filename()) - if (!_path.empty() && _path[_path.length() - 1] != '/' && _path[_path.length() - 1] != ':') { - _path += '/'; + if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { + _path += preferred_separator; } return *this; } @@ -2250,7 +2370,7 @@ GHC_INLINE path& path::operator/=(const path& p) assign(root_name()); } else if ((!has_root_directory() && is_absolute()) || has_filename()) { - _path += '/'; + _path += preferred_separator; } auto iter = p.begin(); bool first = true; @@ -2258,25 +2378,27 @@ GHC_INLINE path& path::operator/=(const path& p) ++iter; } while (iter != p.end()) { - if (!first && !(!_path.empty() && _path[_path.length() - 1] == '/')) { - _path += '/'; + if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { + _path += preferred_separator; } first = false; - _path += (*iter++).generic_string(); + _path += (*iter++).native(); } + check_long_path(); return *this; } -GHC_INLINE void path::append_name(const char* name) +GHC_INLINE void path::append_name(const value_type* name) { if (_path.empty()) { this->operator/=(path(name)); } else { - if (_path.back() != path::generic_separator) { - _path.push_back(path::generic_separator); + if (_path.back() != path::preferred_separator) { + _path.push_back(path::preferred_separator); } _path += name; + check_long_path(); } } @@ -2337,17 +2459,14 @@ GHC_INLINE path& path::operator+=(const value_type* x) GHC_INLINE path& path::operator+=(value_type x) { #ifdef GHC_OS_WINDOWS - if (x == '\\') { - x = generic_separator; + if (x == generic_separator) { + x = preferred_separator; } #endif - if (_path.empty() || _path.back() != generic_separator) { -#ifdef GHC_USE_WCHAR_T - _path += detail::toUtf8(string_type(1, x)); -#else + if (_path.empty() || _path.back() != preferred_separator) { _path += x; -#endif } + check_long_path(); return *this; } @@ -2371,15 +2490,15 @@ template inline path& path::concat(const Source& x) { path p(x); - postprocess_path_with_format(p._path, native_format); _path += p._path; + postprocess_path_with_format(native_format); return *this; } template inline path& path::concat(InputIterator first, InputIterator last) { _path.append(first, last); - postprocess_path_with_format(_path, native_format); + postprocess_path_with_format(native_format); return *this; } @@ -2390,6 +2509,9 @@ inline path& path::concat(InputIterator first, InputIterator last) GHC_INLINE void path::clear() noexcept { _path.clear(); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; +#endif } GHC_INLINE path& path::make_preferred() @@ -2427,58 +2549,19 @@ GHC_INLINE path& path::replace_extension(const path& replacement) GHC_INLINE void path::swap(path& rhs) noexcept { _path.swap(rhs._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + std::swap(_prefixLength, rhs._prefixLength); +#endif } //----------------------------------------------------------------------------- // 30.10.8.4.6, native format observers -#ifdef GHC_OS_WINDOWS -GHC_INLINE path::impl_string_type path::native_impl() const -{ - impl_string_type result; - if (is_absolute() && _path.length() > MAX_PATH - 10) { - // expand absolute non namespaced long Windows filenames with marker - if (has_root_name() && _path[0] == '/' && _path[1] != '/') { - result = "\\\\?\\" + _path.substr(1); - } - else { - result = "\\\\?\\" + _path; - } - } - else { - result = _path; - } - /*if (has_root_name() && root_name()._path[0] == '/') { - return _path; - }*/ - for (auto& c : result) { - if (c == '/') { - c = '\\'; - } - } - return result; -} -#else -GHC_INLINE const path::impl_string_type& path::native_impl() const +GHC_INLINE const path::string_type& path::native() const noexcept { return _path; } -#endif -GHC_INLINE const path::string_type& path::native() const -{ -#ifdef GHC_OS_WINDOWS -#ifdef GHC_USE_WCHAR_T - _native_cache = detail::fromUtf8(native_impl()); -#else - _native_cache = native_impl(); -#endif - return _native_cache; -#else - return _path; -#endif -} - -GHC_INLINE const path::value_type* path::c_str() const +GHC_INLINE const path::value_type* path::c_str() const noexcept { return native().c_str(); } @@ -2493,14 +2576,22 @@ GHC_INLINE path::operator path::string_type() const template inline std::basic_string path::string(const Allocator& a) const { - return detail::fromUtf8>(native_impl(), a); +#ifdef GHC_USE_WCHAR_T + return detail::fromWChar>(_path, a); +#else + return detail::fromUtf8>(_path, a); +#endif } #ifdef GHC_EXPAND_IMPL GHC_INLINE std::string path::string() const { - return native_impl(); +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif } GHC_INLINE std::wstring path::wstring() const @@ -2515,23 +2606,33 @@ GHC_INLINE std::wstring path::wstring() const #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) GHC_INLINE std::u8string path::u8string() const { - return std::u8string(reinterpret_cast(native_impl().c_str())); +#ifdef GHC_USE_WCHAR_T + return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); +#else + return std::u8string(reinterpret_cast(c_str())); +#endif } #else GHC_INLINE std::string path::u8string() const { - return native_impl(); +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif } #endif GHC_INLINE std::u16string path::u16string() const { - return detail::fromUtf8(native_impl()); + // TODO: optimize + return detail::fromUtf8(string()); } GHC_INLINE std::u32string path::u32string() const { - return detail::fromUtf8(native_impl()); + // TODO: optimize + return detail::fromUtf8(string()); } #endif // GHC_EXPAND_IMPL @@ -2541,41 +2642,79 @@ GHC_INLINE std::u32string path::u32string() const template inline std::basic_string path::generic_string(const Allocator& a) const { +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto result = detail::fromWChar, path::string_type>(_path, a); +#else + auto result = detail::fromUtf8>(_path, a); +#endif + for (auto& c : result) { + if (c == preferred_separator) { + c = generic_separator; + } + } + return result; +#else return detail::fromUtf8>(_path, a); +#endif } #ifdef GHC_EXPAND_IMPL -GHC_INLINE const std::string& path::generic_string() const +GHC_INLINE std::string path::generic_string() const { +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else return _path; +#endif } GHC_INLINE std::wstring path::generic_wstring() const { +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else return detail::fromUtf8(_path); -} +#endif +} // namespace filesystem #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) GHC_INLINE std::u8string path::generic_u8string() const { +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else return std::u8string(reinterpret_cast(_path.c_str())); +#endif } #else GHC_INLINE std::string path::generic_u8string() const { +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else return _path; +#endif } #endif GHC_INLINE std::u16string path::generic_u16string() const { +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else return detail::fromUtf8(_path); +#endif } GHC_INLINE std::u32string path::generic_u32string() const { +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else return detail::fromUtf8(_path); +#endif } //----------------------------------------------------------------------------- @@ -2613,26 +2752,22 @@ GHC_INLINE int path::compare(const path& p) const noexcept if (iter2 == p._path.end()) { return 1; } - if (*iter1 == '/') { + if (*iter1 == preferred_separator) { return -1; } - if (*iter2 == '/') { + if (*iter2 == preferred_separator) { return 1; } return *iter1 < *iter2 ? -1 : 1; -#else // LWG_2936_BEHAVIOUR +#else // LWG_2936_BEHAVIOUR #ifdef GHC_OS_WINDOWS auto rnl1 = root_name_length(); auto rnl2 = p.root_name_length(); auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); - if(rnc) { + if (rnc) { return rnc; } - auto p1 = _path; - std::replace(p1.begin()+static_cast(rnl1), p1.end(), '/', '\\'); - auto p2 = p._path; - std::replace(p2.begin()+static_cast(rnl2), p2.end(), '/', '\\'); - return p1.compare(rnl1, std::string::npos, p2, rnl2, std::string::npos); + return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); #else return _path.compare(p._path); #endif @@ -2658,15 +2793,29 @@ GHC_INLINE int path::compare(const value_type* s) const //----------------------------------------------------------------------------- // 30.10.8.4.9, decomposition +#ifdef GHC_OS_WINDOWS +GHC_INLINE void path::handle_prefixes() +{ +#if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; + if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { + if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { + _prefixLength = 4; + } + } +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +} +#endif + GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept { #ifdef GHC_OS_WINDOWS - if (_path.length() >= 2 && std::toupper(static_cast(_path[0])) >= 'A' && std::toupper(static_cast(_path[0])) <= 'Z' && _path[1] == ':') { + if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { return 2; } #endif - if (_path.length() > 2 && _path[0] == '/' && _path[1] == '/' && _path[2] != '/' && std::isprint(_path[2])) { - impl_string_type::size_type pos = _path.find_first_of("/\\", 3); + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { + impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); if (pos == impl_string_type::npos) { return _path.length(); } @@ -2679,42 +2828,43 @@ GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept GHC_INLINE path path::root_name() const { - return path(_path.substr(0, root_name_length()), generic_format); + return path(_path.substr(_prefixLength, root_name_length()), native_format); } GHC_INLINE path path::root_directory() const { - if(has_root_directory()) { - return path("/", generic_format); + if (has_root_directory()) { + static const path _root_dir(std::string(1, preferred_separator), native_format); + return _root_dir; } return path(); } GHC_INLINE path path::root_path() const { - return path(root_name().generic_string() + root_directory().generic_string(), generic_format); + return path(root_name().string() + root_directory().string(), native_format); } GHC_INLINE path path::relative_path() const { - auto rootPathLen = root_name_length() + (has_root_directory() ? 1 : 0); + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); } GHC_INLINE path path::parent_path() const { - auto rootPathLen = root_name_length() + (has_root_directory() ? 1 : 0); - if(rootPathLen < _path.length()) { - if (empty() || begin() == --end()) { + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + if (rootPathLen < _path.length()) { + if (empty()) { return path(); } else { auto piter = end(); auto iter = piter.decrement(_path.end()); - if(iter > _path.begin() + static_cast(rootPathLen) && *iter != '/') { + if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { --iter; } - return path(_path.begin(), iter, format::generic_format); + return path(_path.begin(), iter, native_format); } } else { @@ -2724,19 +2874,19 @@ GHC_INLINE path path::parent_path() const GHC_INLINE path path::filename() const { - return relative_path().empty() ? path() : path(*--end()); + return !has_relative_path() ? path() : path(*--end()); } GHC_INLINE path path::stem() const { - impl_string_type fn = filename().string(); + impl_string_type fn = filename().native(); if (fn != "." && fn != "..") { impl_string_type::size_type pos = fn.rfind('.'); if (pos != impl_string_type::npos && pos > 0) { - return path{fn.substr(0, pos), generic_format}; + return path{fn.substr(0, pos), native_format}; } } - return path{fn, generic_format}; + return path{fn, native_format}; } GHC_INLINE path path::extension() const @@ -2746,7 +2896,7 @@ GHC_INLINE path path::extension() const const auto& fn = *--iter; impl_string_type::size_type pos = fn._path.rfind('.'); if (pos != std::string::npos && pos > 0) { - return path(fn._path.substr(pos), generic_format); + return path(fn._path.substr(pos), native_format); } } return path(); @@ -2763,8 +2913,8 @@ GHC_INLINE bool has_executable_extension(const path& p) if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { return false; } - const char* ext = fn._path.c_str() + pos + 1; - if (detail::equals_simple_insensitive(ext, "exe") || detail::equals_simple_insensitive(ext, "cmd") || detail::equals_simple_insensitive(ext, "bat") || detail::equals_simple_insensitive(ext, "com")) { + const path::value_type* ext = fn._path.c_str() + pos + 1; + if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { return true; } } @@ -2787,8 +2937,8 @@ GHC_INLINE bool path::has_root_name() const GHC_INLINE bool path::has_root_directory() const { - auto rootLen = root_name_length(); - return (_path.length() > rootLen && _path[rootLen] == '/'); + auto rootLen = _prefixLength + root_name_length(); + return (_path.length() > rootLen && _path[rootLen] == preferred_separator); } GHC_INLINE bool path::has_root_path() const @@ -2798,7 +2948,7 @@ GHC_INLINE bool path::has_root_path() const GHC_INLINE bool path::has_relative_path() const { - auto rootPathLen = root_name_length() + (has_root_directory() ? 1 : 0); + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); return rootPathLen < _path.length(); } @@ -2853,7 +3003,7 @@ GHC_INLINE path path::lexically_normal() const continue; } else if (*(--dest.end()) != "..") { - if (dest._path.back() == generic_separator) { + if (dest._path.back() == preferred_separator) { dest._path.pop_back(); } dest.remove_filename(); @@ -2916,50 +3066,37 @@ GHC_INLINE path path::lexically_proximate(const path& base) const // 30.10.8.5, iterators GHC_INLINE path::iterator::iterator() {} -GHC_INLINE path::iterator::iterator(const path::impl_string_type::const_iterator& first, const path::impl_string_type::const_iterator& last, const path::impl_string_type::const_iterator& pos) - : _first(first) - , _last(last) +GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) + : _first(p._path.begin()) + , _last(p._path.end()) + , _prefix(_first + static_cast(p._prefixLength)) + , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) , _iter(pos) { - updateCurrent(); - // find the position of a potential root directory slash -#ifdef GHC_OS_WINDOWS - if (_last - _first >= 3 && std::toupper(static_cast(*first)) >= 'A' && std::toupper(static_cast(*first)) <= 'Z' && *(first + 1) == ':' && *(first + 2) == '/') { - _root = _first + 2; - } - else -#endif - { - if (_first != _last && *_first == '/') { - if (_last - _first >= 2 && *(_first + 1) == '/' && !(_last - _first >= 3 && *(_first + 2) == '/')) { - _root = increment(_first); - } - else { - _root = _first; - } - } - else { - _root = _last; - } + if(pos != _last) { + updateCurrent(); } } GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const { path::impl_string_type::const_iterator i = pos; - bool fromStart = i == _first; + bool fromStart = i == _first || i == _prefix; if (i != _last) { - // we can only sit on a slash if it is a network name or a root - if (*i++ == '/') { - if (i != _last && *i == '/') { - if (fromStart && !(i + 1 != _last && *(i + 1) == '/')) { + if (fromStart && i == _first && _prefix > _first) { + i = _prefix; + } + else if (*i++ == preferred_separator) { + // we can only sit on a slash if it is a network name or a root + if (i != _last && *i == preferred_separator) { + if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { // leadind double slashes detected, treat this and the // following until a slash as one unit - i = std::find(++i, _last, '/'); + i = std::find(++i, _last, preferred_separator); } else { // skip redundant slashes - while (i != _last && *i == '/') { + while (i != _last && *i == preferred_separator) { ++i; } } @@ -2970,7 +3107,7 @@ GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(cons ++i; } else { - i = std::find(i, _last, '/'); + i = std::find(i, _last, preferred_separator); } } } @@ -2984,18 +3121,18 @@ GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(cons --i; // if this is now the root slash or the trailing slash, we are done, // else check for network name - if (i != _root && (pos != _last || *i != '/')) { + if (i != _root && (pos != _last || *i != preferred_separator)) { #ifdef GHC_OS_WINDOWS - static const std::string seps = "/:"; + static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); if (i > _first && *i == ':') { i++; } #else - i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), '/').base(); + i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); #endif // Now we have to check if this is a network name - if (i - _first == 2 && *_first == '/' && *(_first + 1) == '/') { + if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { i -= 2; } } @@ -3005,25 +3142,21 @@ GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(cons GHC_INLINE void path::iterator::updateCurrent() { - if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == '/' && _iter != _root) && (_iter + 1 == _last))) { + if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { _current.clear(); } else { _current.assign(_iter, increment(_iter)); - if (_current.generic_string().size() > 1 && _current.generic_string()[0] == '/' && _current.generic_string()[_current.generic_string().size() - 1] == '/') { - // shrink successive slashes to one - _current = "/"; - } } } GHC_INLINE path::iterator& path::iterator::operator++() { _iter = increment(_iter); - while (_iter != _last && // we didn't reach the end - _iter != _root && // this is not a root position - *_iter == '/' && // we are on a slash - (_iter + 1) != _last // the slash is not the last char + while (_iter != _last && // we didn't reach the end + _iter != _root && // this is not a root position + *_iter == preferred_separator && // we are on a separator + (_iter + 1) != _last // the slash is not the last char ) { ++_iter; } @@ -3074,12 +3207,12 @@ GHC_INLINE path::iterator::pointer path::iterator::operator->() const GHC_INLINE path::iterator path::begin() const { - return iterator(_path.begin(), _path.end(), _path.begin()); + return iterator(*this, _path.begin()); } GHC_INLINE path::iterator path::end() const { - return iterator(_path.begin(), _path.end(), _path.end()); + return iterator(*this, _path.end()); } //----------------------------------------------------------------------------- @@ -3095,7 +3228,7 @@ GHC_INLINE size_t hash_value(const path& p) noexcept } #ifdef GHC_HAS_THREEWAY_COMP -GHC_INLINE std::strong_ordering operator<=>( const path& lhs, const path& rhs ) noexcept +GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) <=> 0; } @@ -3131,7 +3264,6 @@ GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept return lhs.compare(rhs) >= 0; } - GHC_INLINE path operator/(const path& lhs, const path& rhs) { path result(lhs); @@ -3269,10 +3401,10 @@ GHC_INLINE path absolute(const path& p, std::error_code& ec) if (p.empty()) { return absolute(current_path(ec), ec) / ""; } - ULONG size = ::GetFullPathNameW(p.wstring().c_str(), 0, 0, 0); + ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); if (size) { std::vector buf(size, 0); - ULONG s2 = GetFullPathNameW(p.wstring().c_str(), size, buf.data(), nullptr); + ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); if (s2 && s2 < size) { path result = path(std::wstring(buf.data(), s2)); if (p.filename() == ".") { @@ -3330,7 +3462,6 @@ GHC_INLINE path canonical(const path& p, std::error_code& ec) return path(); } path work = p.is_absolute() ? p : absolute(p, ec); - path root = work.root_path(); path result; auto fs = status(work, ec); @@ -3343,6 +3474,7 @@ GHC_INLINE path canonical(const path& p, std::error_code& ec) } bool redo; do { + auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); redo = false; result.clear(); for (auto pe : work) { @@ -3353,7 +3485,7 @@ GHC_INLINE path canonical(const path& p, std::error_code& ec) result = result.parent_path(); continue; } - else if ((result / pe).string().length() <= root.string().length()) { + else if ((result / pe).string().length() <= rootPathLen) { result /= pe; continue; } @@ -3541,7 +3673,7 @@ GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options overwrite = true; } #ifdef GHC_OS_WINDOWS - if (!::CopyFileW(detail::fromUtf8(from.u8string()).c_str(), detail::fromUtf8(to.u8string()).c_str(), !overwrite)) { + if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { ec = detail::make_system_error(); return false; } @@ -3641,7 +3773,8 @@ GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept std::error_code tmp_ec; if (is_directory(current, tmp_ec)) { ec.clear(); - } else { + } + else { return false; } } @@ -3703,12 +3836,12 @@ GHC_INLINE bool create_directory(const path& p, const path& attributes, std::err #endif #ifdef GHC_OS_WINDOWS if (!attributes.empty()) { - if (!::CreateDirectoryExW(detail::fromUtf8(attributes.u8string()).c_str(), detail::fromUtf8(p.u8string()).c_str(), NULL)) { + if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { ec = detail::make_system_error(); return false; } } - else if (!::CreateDirectoryW(detail::fromUtf8(p.u8string()).c_str(), NULL)) { + else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { ec = detail::make_system_error(); return false; } @@ -3829,7 +3962,7 @@ GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS - if (!::SetCurrentDirectoryW(detail::fromUtf8(p.u8string()).c_str())) { + if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { ec = detail::make_system_error(); } #else @@ -3876,9 +4009,9 @@ GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) { ec.clear(); #ifdef GHC_OS_WINDOWS - std::shared_ptr file1(::CreateFileW(p1.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + std::shared_ptr file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); auto e1 = ::GetLastError(); - std::shared_ptr file2(::CreateFileW(p2.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + std::shared_ptr file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); if (file1.get() == INVALID_HANDLE_VALUE || file2.get() == INVALID_HANDLE_VALUE) { #ifdef LWG_2937_BEHAVIOUR ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); @@ -3936,7 +4069,7 @@ GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept ec.clear(); #ifdef GHC_OS_WINDOWS WIN32_FILE_ATTRIBUTE_DATA attr; - if (!GetFileAttributesExW(detail::fromUtf8(p.u8string()).c_str(), GetFileExInfoStandard, &attr)) { + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); return static_cast(-1); } @@ -3969,7 +4102,7 @@ GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcep ec.clear(); #ifdef GHC_OS_WINDOWS uintmax_t result = static_cast(-1); - std::shared_ptr file(::CreateFileW(p.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + std::shared_ptr file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); BY_HANDLE_FILE_INFORMATION inf; if (file.get() == INVALID_HANDLE_VALUE) { ec = detail::make_system_error(); @@ -4200,7 +4333,7 @@ GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::err ec.clear(); auto d = new_time.time_since_epoch(); #ifdef GHC_OS_WINDOWS - std::shared_ptr file(::CreateFileW(p.wstring().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle); + std::shared_ptr file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle); FILETIME ft; auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; ft.dwLowDateTime = static_cast(tt); @@ -4229,7 +4362,7 @@ GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::err times[0].tv_sec = 0; times[0].tv_nsec = UTIME_OMIT; times[1].tv_sec = std::chrono::duration_cast(d).count(); - times[1].tv_nsec = 0; //std::chrono::duration_cast(d).count() % 1000000000; + times[1].tv_nsec = 0; // std::chrono::duration_cast(d).count() % 1000000000; if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { ec = detail::make_system_error(); } @@ -4289,10 +4422,10 @@ GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::e } #ifdef GHC_OS_WINDOWS #ifdef __GNUC__ - auto oldAttr = GetFileAttributesW(p.wstring().c_str()); + auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); if (oldAttr != INVALID_FILE_ATTRIBUTES) { DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; - if (oldAttr == newAttr || SetFileAttributesW(p.wstring().c_str(), newAttr)) { + if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { return; } } @@ -4322,7 +4455,7 @@ GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::e GHC_INLINE path proximate(const path& p, std::error_code& ec) { auto cp = current_path(ec); - if(!ec) { + if (!ec) { return proximate(p, cp, ec); } return path(); @@ -4397,8 +4530,13 @@ GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto cstr = p.c_str(); +#else std::wstring np = detail::fromUtf8(p.u8string()); - DWORD attr = GetFileAttributesW(np.c_str()); + auto cstr = np.c_str(); +#endif + DWORD attr = GetFileAttributesW(cstr); if (attr == INVALID_FILE_ATTRIBUTES) { auto error = ::GetLastError(); if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { @@ -4408,12 +4546,12 @@ GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept } if (!ec) { if (attr & FILE_ATTRIBUTE_DIRECTORY) { - if (!RemoveDirectoryW(np.c_str())) { + if (!RemoveDirectoryW(cstr)) { ec = detail::make_system_error(); } } else { - if (!DeleteFileW(np.c_str())) { + if (!DeleteFileW(cstr)) { ec = detail::make_system_error(); } } @@ -4458,9 +4596,11 @@ GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept break; } bool is_symlink_result = iter->is_symlink(ec); - if (ec) return static_cast(-1); + if (ec) + return static_cast(-1); bool is_directory_result = iter->is_directory(ec); - if (ec) return static_cast(-1); + if (ec) + return static_cast(-1); if (!is_symlink_result && is_directory_result) { count += remove_all(iter->path(), ec); if (ec) { @@ -4503,7 +4643,7 @@ GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) no ec.clear(); #ifdef GHC_OS_WINDOWS if (from != to) { - if (!MoveFileExW(detail::fromUtf8(from.u8string()).c_str(), detail::fromUtf8(to.u8string()).c_str(), (DWORD)MOVEFILE_REPLACE_EXISTING)) { + if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { ec = detail::make_system_error(); } } @@ -4533,7 +4673,7 @@ GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) #ifdef GHC_OS_WINDOWS LARGE_INTEGER lisize; lisize.QuadPart = static_cast(size); - if(lisize.QuadPart < 0) { + if (lisize.QuadPart < 0) { #ifdef ERROR_FILE_TOO_LARGE ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); #else @@ -4541,7 +4681,7 @@ GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) #endif return; } - std::shared_ptr file(CreateFileW(detail::fromUtf8(p.u8string()).c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle); + std::shared_ptr file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle); if (file.get() == INVALID_HANDLE_VALUE) { ec = detail::make_system_error(); } @@ -4571,10 +4711,10 @@ GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS - ULARGE_INTEGER freeBytesAvailableToCaller = {{0, 0}}; - ULARGE_INTEGER totalNumberOfBytes = {{0, 0}}; - ULARGE_INTEGER totalNumberOfFreeBytes = {{0, 0}}; - if (!GetDiskFreeSpaceExW(detail::fromUtf8(p.u8string()).c_str(), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { + ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; + if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { ec = detail::make_system_error(); return {static_cast(-1), static_cast(-1), static_cast(-1)}; } @@ -5135,7 +5275,7 @@ public: { if (!_base.empty()) { ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); - if ((_dirHandle = FindFirstFileW(detail::fromUtf8((_base / "*").u8string()).c_str(), &_findData)) != INVALID_HANDLE_VALUE) { + if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { increment(_ec); } @@ -5167,22 +5307,26 @@ public: do { if (FindNextFileW(_dirHandle, &_findData)) { _current = _base; +#ifdef GHC_USE_WCHAR_T + _current.append_name(_findData.cFileName); +#else #ifdef GHC_RAISE_UNICODE_ERRORS try { _current.append_name(detail::toUtf8(_findData.cFileName).c_str()); } - catch(filesystem_error& fe) { + catch (filesystem_error& fe) { ec = fe.code(); return; } #else _current.append_name(detail::toUtf8(_findData.cFileName).c_str()); +#endif #endif copyToDirEntry(ec); } else { auto err = ::GetLastError(); - if(err != ERROR_NO_MORE_FILES) { + if (err != ERROR_NO_MORE_FILES) { _ec = ec = detail::make_system_error(err); } FindClose(_dirHandle); @@ -5268,7 +5412,7 @@ public: _current = _base; _current.append_name(_entry->d_name); _dir_entry = directory_entry(_current, ec); - if(ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { + if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { ec.clear(); skip = true; } @@ -5511,9 +5655,11 @@ GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator+ GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept { auto status = (*this)->status(ec); - if (ec) return *this; + if (ec) + return *this; auto symlink_status = (*this)->symlink_status(ec); - if (ec) return *this; + if (ec) + return *this; if (recursion_pending() && is_directory(status) && (!is_symlink(symlink_status) || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); } diff --git a/include/ghc/fs_std.hpp b/include/ghc/fs_std.hpp index f295e37..0d9d3cc 100644 --- a/include/ghc/fs_std.hpp +++ b/include/ghc/fs_std.hpp @@ -43,7 +43,7 @@ using fstream = std::fstream; #endif #endif #ifndef GHC_USE_STD_FS -#define GHC_WIN_WSTRING_STRING_TYPE +//#define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE #include namespace fs { using namespace ghc::filesystem; diff --git a/include/ghc/fs_std_fwd.hpp b/include/ghc/fs_std_fwd.hpp index f07c09b..148751e 100644 --- a/include/ghc/fs_std_fwd.hpp +++ b/include/ghc/fs_std_fwd.hpp @@ -46,7 +46,7 @@ using fstream = std::fstream; #endif #endif #ifndef GHC_USE_STD_FS -#define GHC_WIN_WSTRING_STRING_TYPE +//#define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE #define GHC_FILESYSTEM_FWD #include namespace fs { diff --git a/include/ghc/fs_std_impl.hpp b/include/ghc/fs_std_impl.hpp index d7b609d..455438d 100644 --- a/include/ghc/fs_std_impl.hpp +++ b/include/ghc/fs_std_impl.hpp @@ -37,7 +37,7 @@ #endif #endif #ifndef GHC_USE_STD_FS -#define GHC_WIN_WSTRING_STRING_TYPE +//#define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE #define GHC_FILESYSTEM_IMPLEMENTATION #include #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a463231..3ad4271 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,18 +31,18 @@ else() ParseAndAddCatchTests(filesystem_test) AddExecutableWithStdFS(std_filesystem_test filesystem_test.cpp catch.hpp) if(WIN32) - add_executable(filesystem_test_wchar filesystem_test.cpp catch.hpp) - target_link_libraries(filesystem_test_wchar ghc_filesystem) - target_compile_options(filesystem_test_wchar PRIVATE + add_executable(filesystem_test_char filesystem_test.cpp catch.hpp) + target_link_libraries(filesystem_test_char ghc_filesystem) + target_compile_options(filesystem_test_char PRIVATE $<$:-Wall -Wextra -Werror> $<$:-Wall -Werror> $<$:/WX>) if(CMAKE_CXX_COMPILER_ID MATCHES MSVC) - target_compile_definitions(filesystem_test_wchar PRIVATE _CRT_SECURE_NO_WARNINGS GHC_WIN_WSTRING_STRING_TYPE) + target_compile_definitions(filesystem_test_char PRIVATE _CRT_SECURE_NO_WARNINGS GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) else() - target_compile_definitions(filesystem_test_wchar PRIVATE GHC_WIN_WSTRING_STRING_TYPE) + target_compile_definitions(filesystem_test_char PRIVATE GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) endif() - ParseAndAddCatchTests(filesystem_test_wchar) + ParseAndAddCatchTests(filesystem_test_char) endif() if("cxx_std_17" IN_LIST CMAKE_CXX_COMPILE_FEATURES) AddTestExecutableWithStdCpp(17 filesystem_test.cpp catch.hpp) diff --git a/test/filesystem_test.cpp b/test/filesystem_test.cpp index 2b1a403..651f45b 100644 --- a/test/filesystem_test.cpp +++ b/test/filesystem_test.cpp @@ -2816,8 +2816,8 @@ TEST_CASE("Windows: path namespace handling", "[filesystem][path][fs.path.win.na {R"(\\?\C:\Windows\notepad.exe)", R"(\\?\C:\Windows\notepad.exe)", "\\\\?", "\\\\?\\", "//?,/,C:,Windows,notepad.exe"}, {R"(\??\C:\Windows\notepad.exe)", R"(\??\C:\Windows\notepad.exe)", "\\??", "\\??\\", "/??,/,C:,Windows,notepad.exe"}, #else - {R"(\\?\C:\Windows\notepad.exe)", R"(C:\Windows\notepad.exe)", "C:", "C:\\", "C:,/,Windows,notepad.exe"}, - {R"(\??\C:\Windows\notepad.exe)", R"(C:\Windows\notepad.exe)", "C:", "C:\\", "C:,/,Windows,notepad.exe"}, + {R"(\\?\C:\Windows\notepad.exe)", R"(\\?\C:\Windows\notepad.exe)", "C:", "C:\\", "//?/,C:,/,Windows,notepad.exe"}, + {R"(\??\C:\Windows\notepad.exe)", R"(\??\C:\Windows\notepad.exe)", "C:", "C:\\", "/?\?/,C:,/,Windows,notepad.exe"}, #endif {R"(\\.\C:\Windows\notepad.exe)", R"(\\.\C:\Windows\notepad.exe)", "\\\\.", "\\\\.\\", "//.,/,C:,Windows,notepad.exe"}, {R"(\\?\HarddiskVolume1\Windows\notepad.exe)", R"(\\?\HarddiskVolume1\Windows\notepad.exe)", "\\\\?", "\\\\?\\", "//?,/,HarddiskVolume1,Windows,notepad.exe"}, @@ -2834,6 +2834,7 @@ TEST_CASE("Windows: path namespace handling", "[filesystem][path][fs.path.win.na INFO("Used path: " + ti._path); auto p = fs::path(ti._path); CHECK(p.string() == ti._string); + CHECK(p.is_absolute()); CHECK(p.root_name().string() == ti._rootName); CHECK(p.root_path().string() == ti._rootPath); CHECK(iterateResult(p) == ti._iterateResult);