diff --git a/include/ghc/filesystem.hpp b/include/ghc/filesystem.hpp index 532f751..eb7363d 100644 --- a/include/ghc/filesystem.hpp +++ b/include/ghc/filesystem.hpp @@ -234,9 +234,15 @@ // 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 @@ -319,6 +325,10 @@ class GHC_FS_API_CLASS path 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 @@ -505,15 +515,21 @@ class GHC_FS_API_CLASS path }; 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); impl_string_type _path; #ifdef GHC_OS_WINDOWS + void handle_prefixes(); + void check_long_path(); 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 }; @@ -576,7 +592,7 @@ class GHC_FS_API_CLASS path::iterator 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--(); @@ -593,6 +609,7 @@ class GHC_FS_API_CLASS path::iterator 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; @@ -1544,25 +1561,35 @@ inline std::string toUtf8(const charT* unicodeString) namespace detail { -//template ::value >> -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() +{ +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type("\\\\?\\"))) { + 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 @@ -1571,22 +1598,17 @@ GHC_INLINE void path::postprocess_path_with_format(path::impl_string_type& p, pa case path::native_format: case path::auto_format: case path::generic_format: - for (auto& c : p) { + for (auto& c : _path) { if (c == generic_separator) { c = internal_separator; } } - 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); - } - } +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type("\\\\?\\"))) { + _path = "\\\\?\\" + _path; } +#endif + handle_prefixes(); break; #else case path::auto_format: @@ -1596,13 +1618,13 @@ GHC_INLINE void path::postprocess_path_with_format(path::impl_string_type& p, pa break; #endif } - if (p.length() > 2 && p[0] == internal_separator && p[1] == internal_separator && p[2] != internal_separator) { - std::string::iterator new_end = std::unique(p.begin() + 2, p.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == internal_separator; }); - p.erase(new_end, p.end()); + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == internal_separator && _path[_prefixLength + 1] == internal_separator && _path[_prefixLength + 2] != internal_separator) { + std::string::iterator new_end = std::unique(_path.begin() + _prefixLength + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == internal_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 == internal_separator; }); - p.erase(new_end, p.end()); + std::string::iterator new_end = std::unique(_path.begin() + _prefixLength, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == internal_separator; }); + _path.erase(new_end, _path.end()); } } @@ -1612,7 +1634,7 @@ template inline path::path(const Source& source, format fmt) : _path(detail::toUtf8(source)) { - postprocess_path_with_format(_path, fmt); + postprocess_path_with_format(fmt); } template @@ -1751,6 +1773,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) { @@ -1864,12 +1901,20 @@ 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; @@ -2122,11 +2167,13 @@ GHC_INLINE path::path() noexcept {} GHC_INLINE path::path(const path& p) : _path(p._path) + , _prefixLength(p._prefixLength) { } GHC_INLINE path::path(path&& p) noexcept : _path(std::move(p._path)) + , _prefixLength(p._prefixLength) { } @@ -2137,7 +2184,7 @@ GHC_INLINE path::path(string_type&& source, format fmt) : _path(std::move(source)) #endif { - postprocess_path_with_format(_path, fmt); + postprocess_path_with_format(fmt); } #endif // GHC_EXPAND_IMPL @@ -2174,12 +2221,14 @@ GHC_INLINE path::~path() {} GHC_INLINE path& path::operator=(const path& p) { _path = p._path; + _prefixLength = p._prefixLength; return *this; } GHC_INLINE path& path::operator=(path&& p) noexcept { _path = std::move(p._path); + _prefixLength = p._prefixLength; return *this; } @@ -2195,7 +2244,7 @@ GHC_INLINE path& path::assign(path::string_type&& source) #else _path = std::move(source); #endif - postprocess_path_with_format(_path, native_format); + postprocess_path_with_format(native_format); return *this; } @@ -2211,7 +2260,7 @@ template inline path& path::assign(const Source& source) { _path.assign(detail::toUtf8(source)); - postprocess_path_with_format(_path, native_format); + postprocess_path_with_format(native_format); return *this; } @@ -2219,6 +2268,7 @@ template <> inline path& path::assign(const path& source) { _path = source._path; + _prefixLength = source._prefixLength; return *this; } @@ -2226,7 +2276,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; } @@ -2266,6 +2316,7 @@ GHC_INLINE path& path::operator/=(const path& p) first = false; _path += (*iter++).string(); } + check_long_path(); return *this; } @@ -2279,6 +2330,7 @@ GHC_INLINE void path::append_name(const char* name) _path.push_back(path::internal_separator); } _path += name; + check_long_path(); } } @@ -2350,6 +2402,7 @@ GHC_INLINE path& path::operator+=(value_type x) _path += x; #endif } + check_long_path(); return *this; } @@ -2374,14 +2427,14 @@ inline path& path::concat(const Source& x) { path p(x); _path += p._path; - postprocess_path_with_format(p._path, native_format); + 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; } @@ -2392,6 +2445,7 @@ inline path& path::concat(InputIterator first, InputIterator last) GHC_INLINE void path::clear() noexcept { _path.clear(); + _prefixLength = 0; } GHC_INLINE path& path::make_preferred() @@ -2429,46 +2483,11 @@ GHC_INLINE path& path::replace_extension(const path& replacement) GHC_INLINE void path::swap(path& rhs) noexcept { _path.swap(rhs._path); + std::swap(_prefixLength, rhs._prefixLength); } //----------------------------------------------------------------------------- // 30.10.8.4.6, native format observers -#ifdef GHC_OS_WINDOWS -GHC_INLINE path::impl_string_type path::native_impl() const -{ - return _path; -#if 0 - 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; -#endif -} -#else -GHC_INLINE const path::impl_string_type& path::native_impl() const -{ - return _path; -} -#endif - GHC_INLINE const path::string_type& path::native() const noexcept { return _path; @@ -2698,15 +2717,27 @@ GHC_INLINE int path::compare(const value_type* s) const //----------------------------------------------------------------------------- // 30.10.8.4.9, decomposition +GHC_INLINE void path::handle_prefixes() +{ +#if defined(GHC_OS_WINDOWS) && 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("\\\\?\\")) || detail::startsWith(_path, impl_string_type("\\??\\"))) { + _prefixLength = 4; + } + } +#endif // GHC_OS_WINDOWS +} + 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] == internal_separator && _path[1] == internal_separator && _path[2] != internal_separator && std::isprint(_path[2])) { - impl_string_type::size_type pos = _path.find(internal_separator, 3); + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == internal_separator && _path[_prefixLength + 1] == internal_separator && _path[_prefixLength + 2] != internal_separator && std::isprint(_path[_prefixLength + 2])) { + impl_string_type::size_type pos = _path.find(internal_separator, _prefixLength + 3); if (pos == impl_string_type::npos) { return _path.length(); } @@ -2719,13 +2750,13 @@ 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()) { - static const path _root_dir(std::string(1, internal_separator), generic_format); + static const path _root_dir(std::string(1, internal_separator), native_format); return _root_dir; } return path(); @@ -2733,18 +2764,18 @@ GHC_INLINE path path::root_directory() const 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); + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); if(rootPathLen < _path.length()) { if (empty() || begin() == --end()) { return path(); @@ -2755,7 +2786,7 @@ GHC_INLINE path path::parent_path() const if (iter > _path.begin() + static_cast(rootPathLen) && *iter != internal_separator) { --iter; } - return path(_path.begin(), iter, format::generic_format); + return path(_path.begin(), iter, native_format); } } else { @@ -2765,7 +2796,7 @@ 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 @@ -2774,10 +2805,10 @@ GHC_INLINE path path::stem() const 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 @@ -2787,7 +2818,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(); @@ -2828,7 +2859,7 @@ GHC_INLINE bool path::has_root_name() const GHC_INLINE bool path::has_root_directory() const { - auto rootLen = root_name_length(); + auto rootLen = _prefixLength + root_name_length(); return (_path.length() > rootLen && _path[rootLen] == internal_separator); } @@ -2839,7 +2870,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(); } @@ -2957,41 +2988,25 @@ 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()) , _iter(pos) + , _prefix(_first + p._prefixLength) + , _root(p.has_root_directory() ? _first + p._prefixLength + p.root_name_length() : _last) { 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) == internal_separator) { - _root = _first + 2; - } - else -#endif - { - if (_first != _last && *_first == internal_separator) { - if (_last - _first >= 2 && *(_first + 1) == internal_separator && !(_last - _first >= 3 && *(_first + 2) == internal_separator)) { - _root = increment(_first); - } - else { - _root = _first; - } - } - else { - _root = _last; - } - } } 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++ == internal_separator) { + if (fromStart && i == _first && _prefix > _first) { + i = _prefix; + } else if (*i++ == internal_separator) { + // we can only sit on a slash if it is a network name or a root if (i != _last && *i == internal_separator) { if (fromStart && !(i + 1 != _last && *(i + 1) == internal_separator)) { // leadind double slashes detected, treat this and the @@ -3115,12 +3130,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()); } //----------------------------------------------------------------------------- @@ -3371,7 +3386,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); @@ -3384,6 +3398,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) { @@ -3394,7 +3409,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; } @@ -3416,6 +3431,7 @@ GHC_INLINE path canonical(const path& p, std::error_code& ec) result /= target; continue; } + } else { result /= pe; diff --git a/test/filesystem_test.cpp b/test/filesystem_test.cpp index eb9e879..5fb009b 100644 --- a/test/filesystem_test.cpp +++ b/test/filesystem_test.cpp @@ -2797,8 +2797,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"}, @@ -2815,6 +2815,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);