зеркало из https://github.com/microsoft/STL.git
4475 строки
188 KiB
C++
4475 строки
188 KiB
C++
// filesystem standard header
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#ifndef _FILESYSTEM_
|
|
#define _FILESYSTEM_
|
|
#include <yvals_core.h>
|
|
#if _STL_COMPILER_PREPROCESSOR
|
|
|
|
#if !_HAS_CXX17
|
|
_EMIT_STL_WARNING(STL4038, "The contents of <filesystem> are available only with C++17 or later.");
|
|
#else // ^^^ !_HAS_CXX17 / _HAS_CXX17 vvv
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cwchar>
|
|
#include <iomanip>
|
|
#include <locale>
|
|
#include <memory>
|
|
#include <system_error>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <xfilesystem_abi.h>
|
|
#include <xstring>
|
|
|
|
#if _HAS_CXX20
|
|
#include <compare>
|
|
#endif // _HAS_CXX20
|
|
|
|
#pragma pack(push, _CRT_PACKING)
|
|
#pragma warning(push, _STL_WARNING_LEVEL)
|
|
#pragma warning(disable : _STL_DISABLED_WARNINGS)
|
|
_STL_DISABLE_CLANG_WARNINGS
|
|
#pragma push_macro("new")
|
|
#undef new
|
|
|
|
_STD_BEGIN
|
|
namespace filesystem {
|
|
_NODISCARD inline wstring _Convert_narrow_to_wide(const __std_code_page _Code_page, const string_view _Input) {
|
|
wstring _Output;
|
|
|
|
if (!_Input.empty()) {
|
|
if (!_STD _In_range<int>(_Input.size())) {
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
|
|
const int _Len = _Check_convert_result(__std_fs_convert_narrow_to_wide(
|
|
_Code_page, _Input.data(), static_cast<int>(_Input.size()), nullptr, 0));
|
|
|
|
_Output.resize(static_cast<size_t>(_Len));
|
|
|
|
(void) _Check_convert_result(__std_fs_convert_narrow_to_wide(
|
|
_Code_page, _Input.data(), static_cast<int>(_Input.size()), _Output.data(), _Len));
|
|
}
|
|
|
|
return _Output;
|
|
}
|
|
|
|
// More lenient version of _Convert_wide_to_narrow: Instead of failing on non-representable characters,
|
|
// replace them with a replacement character.
|
|
template <class _Traits, class _Alloc>
|
|
_NODISCARD basic_string<typename _Traits::char_type, _Traits, _Alloc> _Convert_wide_to_narrow_replace_chars(
|
|
const __std_code_page _Code_page, const wstring_view _Input, const _Alloc& _Al) {
|
|
basic_string<typename _Traits::char_type, _Traits, _Alloc> _Output(_Al);
|
|
|
|
if (!_Input.empty()) {
|
|
if (!_STD _In_range<int>(_Input.size())) {
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
|
|
const int _Len = _Check_convert_result(__std_fs_convert_wide_to_narrow_replace_chars(
|
|
_Code_page, _Input.data(), static_cast<int>(_Input.size()), nullptr, 0));
|
|
|
|
_Output.resize(static_cast<size_t>(_Len));
|
|
|
|
const auto _Data_as_char = reinterpret_cast<char*>(_Output.data());
|
|
|
|
(void) _Check_convert_result(__std_fs_convert_wide_to_narrow_replace_chars(
|
|
_Code_page, _Input.data(), static_cast<int>(_Input.size()), _Data_as_char, _Len));
|
|
}
|
|
|
|
return _Output;
|
|
}
|
|
|
|
_NODISCARD inline wstring _Convert_utf32_to_wide(const u32string_view _Input) {
|
|
wstring _Output;
|
|
|
|
_Output.reserve(_Input.size()); // ideal when surrogate pairs are uncommon
|
|
|
|
for (const auto& _Code_point : _Input) {
|
|
if (_Code_point <= 0xD7FFU) {
|
|
_Output.push_back(static_cast<wchar_t>(_Code_point));
|
|
} else if (_Code_point <= 0xDFFFU) {
|
|
_Throw_system_error(errc::invalid_argument);
|
|
} else if (_Code_point <= 0xFFFFU) {
|
|
_Output.push_back(static_cast<wchar_t>(_Code_point));
|
|
} else if (_Code_point <= 0x10FFFFU) {
|
|
_Output.push_back(static_cast<wchar_t>(0xD7C0U + (_Code_point >> 10)));
|
|
_Output.push_back(static_cast<wchar_t>(0xDC00U + (_Code_point & 0x3FFU)));
|
|
} else {
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
}
|
|
|
|
return _Output;
|
|
}
|
|
|
|
template <class _Traits, class _Alloc>
|
|
_NODISCARD basic_string<char32_t, _Traits, _Alloc> _Convert_wide_to_utf32(
|
|
const wstring_view _Input, const _Alloc& _Al) {
|
|
basic_string<char32_t, _Traits, _Alloc> _Output(_Al);
|
|
|
|
_Output.reserve(_Input.size()); // ideal when surrogate pairs are uncommon
|
|
|
|
const wchar_t* _First = _Input.data();
|
|
const wchar_t* const _Last = _First + _Input.size();
|
|
|
|
for (; _First != _Last; ++_First) {
|
|
if (*_First <= 0xD7FFU) {
|
|
_Output.push_back(*_First);
|
|
} else if (*_First <= 0xDBFFU) { // found leading surrogate
|
|
const char32_t _Leading = *_First; // widen for later math
|
|
|
|
++_First;
|
|
|
|
if (_First == _Last) { // missing trailing surrogate
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
|
|
const char32_t _Trailing = *_First; // widen for later math
|
|
|
|
if (0xDC00U <= _Trailing && _Trailing <= 0xDFFFU) { // valid trailing surrogate
|
|
_Output.push_back(0xFCA02400U + (_Leading << 10) + _Trailing);
|
|
} else { // invalid trailing surrogate
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
} else if (*_First <= 0xDFFFU) { // found trailing surrogate by itself, invalid
|
|
_Throw_system_error(errc::invalid_argument);
|
|
} else {
|
|
_Output.push_back(*_First);
|
|
}
|
|
}
|
|
|
|
return _Output;
|
|
}
|
|
|
|
template <class _Traits, class _Alloc, class _EcharT = typename _Traits::char_type>
|
|
_NODISCARD basic_string<_EcharT, _Traits, _Alloc> _Convert_wide_to(const wstring_view _Input, const _Alloc& _Al) {
|
|
if constexpr (is_same_v<_EcharT, char>) {
|
|
return _Convert_wide_to_narrow<_Traits>(__std_fs_code_page(), _Input, _Al);
|
|
}
|
|
#ifdef __cpp_char8_t
|
|
else if constexpr (is_same_v<_EcharT, char8_t>) {
|
|
return _Convert_wide_to_narrow<_Traits>(__std_code_page::_Utf8, _Input, _Al);
|
|
}
|
|
#endif // defined(__cpp_char8_t)
|
|
else if constexpr (is_same_v<_EcharT, char32_t>) {
|
|
return _Convert_wide_to_utf32<_Traits>(_Input, _Al);
|
|
} else { // wchar_t, char16_t
|
|
return basic_string<_EcharT, _Traits, _Alloc>(_Input.data(), _Input.data() + _Input.size(), _Al);
|
|
}
|
|
}
|
|
|
|
template <class _Ty, class = void>
|
|
constexpr bool _Is_Source_impl = false;
|
|
|
|
template <class _Ty>
|
|
constexpr bool _Is_Source_impl<_Ty, void_t<typename iterator_traits<_Ty>::value_type>> =
|
|
_Is_EcharT<typename iterator_traits<_Ty>::value_type>;
|
|
|
|
template <class _Ty>
|
|
constexpr bool _Is_Source = _Is_Source_impl<decay_t<_Ty>>;
|
|
|
|
_EXPORT_STD class path;
|
|
|
|
template <>
|
|
inline constexpr bool _Is_Source<path> = false; // to avoid constraint recursion via the converting constructor and
|
|
// iterator_traits when determining if path is copyable.
|
|
|
|
template <class _Elem, class _Traits, class _Alloc>
|
|
constexpr bool _Is_Source<basic_string<_Elem, _Traits, _Alloc>> = _Is_EcharT<_Elem>;
|
|
|
|
template <class _Elem, class _Traits>
|
|
constexpr bool _Is_Source<basic_string_view<_Elem, _Traits>> = _Is_EcharT<_Elem>;
|
|
|
|
struct _Normal_conversion {};
|
|
|
|
struct _Utf8_conversion {};
|
|
|
|
// A "stringoid" is basic_string_view<_EcharT> or basic_string<_EcharT>.
|
|
|
|
template <class _Conversion>
|
|
_NODISCARD wstring _Convert_stringoid_to_wide(const string_view _Input, _Conversion) {
|
|
_STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Conversion, _Normal_conversion, _Utf8_conversion>);
|
|
|
|
if constexpr (is_same_v<_Conversion, _Normal_conversion>) {
|
|
return _Convert_narrow_to_wide(__std_fs_code_page(), _Input);
|
|
} else {
|
|
return _Convert_narrow_to_wide(__std_code_page::_Utf8, _Input);
|
|
}
|
|
}
|
|
|
|
template <class _Conversion>
|
|
_NODISCARD wstring _Convert_stringoid_to_wide(const wstring_view _Input, _Conversion) {
|
|
static_assert(
|
|
is_same_v<_Conversion, _Normal_conversion>, "invalid value_type, see N4950 [depr.fs.path.factory]/1");
|
|
return wstring{_Input};
|
|
}
|
|
|
|
#ifdef __cpp_char8_t
|
|
template <class _Conversion>
|
|
_NODISCARD wstring _Convert_stringoid_to_wide(const basic_string_view<char8_t> _Input, _Conversion) {
|
|
_STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Conversion, _Normal_conversion, _Utf8_conversion>);
|
|
|
|
const string_view _Input_as_char{reinterpret_cast<const char*>(_Input.data()), _Input.size()};
|
|
return _Convert_narrow_to_wide(__std_code_page::_Utf8, _Input_as_char);
|
|
}
|
|
#endif // defined(__cpp_char8_t)
|
|
|
|
template <class _Conversion>
|
|
_NODISCARD wstring _Convert_stringoid_to_wide(const u16string_view _Input, _Conversion) {
|
|
static_assert(
|
|
is_same_v<_Conversion, _Normal_conversion>, "invalid value_type, see N4950 [depr.fs.path.factory]/1");
|
|
return wstring{_Input.data(), _Input.data() + _Input.size()};
|
|
}
|
|
|
|
template <class _Conversion>
|
|
_NODISCARD wstring _Convert_stringoid_to_wide(const u32string_view _Input, _Conversion) {
|
|
static_assert(
|
|
is_same_v<_Conversion, _Normal_conversion>, "invalid value_type, see N4950 [depr.fs.path.factory]/1");
|
|
return _Convert_utf32_to_wide(_Input);
|
|
}
|
|
|
|
template <class _EcharT, class _Traits>
|
|
_NODISCARD auto _Stringoid_from_Source(const basic_string_view<_EcharT, _Traits>& _Source) {
|
|
return basic_string_view<_EcharT>(_Source.data(), _Source.size()); // erase mismatching _Traits
|
|
}
|
|
|
|
template <class _EcharT, class _Traits, class _Alloc>
|
|
_NODISCARD auto _Stringoid_from_Source(const basic_string<_EcharT, _Traits, _Alloc>& _Source) {
|
|
return basic_string_view<_EcharT>(_Source.data(), _Source.size()); // erase mismatching _Traits
|
|
}
|
|
|
|
template <class _Src>
|
|
_NODISCARD auto _Stringoid_from_Source(const _Src& _Source) {
|
|
using _EcharT = _Iter_value_t<decay_t<_Src>>;
|
|
if constexpr (is_pointer_v<_Unwrapped_unverified_t<const _Src&>>) {
|
|
return basic_string_view<_EcharT>(_Get_unwrapped_unverified(_Source));
|
|
} else if constexpr (is_pointer_v<_Unwrapped_t<const _Src&>>) {
|
|
const auto _Data = _Get_unwrapped(_Source);
|
|
auto _Next = _Source;
|
|
while (*_Next != _EcharT{}) {
|
|
++_Next;
|
|
}
|
|
|
|
return basic_string_view<_EcharT>(_Data, static_cast<size_t>(_Get_unwrapped(_Next) - _Data));
|
|
} else {
|
|
basic_string<_EcharT> _Str;
|
|
for (auto _Next = _Source; *_Next != _EcharT{}; ++_Next) {
|
|
_Str.push_back(*_Next);
|
|
}
|
|
|
|
return _Str;
|
|
}
|
|
}
|
|
|
|
#if _ITERATOR_DEBUG_LEVEL == 2
|
|
template <class _EcharT, size_t _SourceSize>
|
|
_NODISCARD basic_string_view<_EcharT> _Stringoid_from_Source(const _EcharT (&_Src)[_SourceSize]) {
|
|
for (size_t _Idx = 0;; ++_Idx) {
|
|
_STL_VERIFY(_Idx < _SourceSize, "path input not null terminated");
|
|
if (_Src[_Idx] == _EcharT{}) {
|
|
return basic_string_view<_EcharT>(_Src, _Idx);
|
|
}
|
|
}
|
|
}
|
|
#endif // _ITERATOR_DEBUG_LEVEL == 2
|
|
|
|
template <class _Src, class _Conversion = _Normal_conversion>
|
|
_NODISCARD wstring _Convert_Source_to_wide(const _Src& _Source, _Conversion _Tag = {}) {
|
|
return _Convert_stringoid_to_wide(_Stringoid_from_Source(_Source), _Tag);
|
|
}
|
|
|
|
template <class _InIt>
|
|
_NODISCARD auto _Stringoid_from_range(_InIt _First, _InIt _Last) {
|
|
_Adl_verify_range(_First, _Last);
|
|
const auto _UFirst = _Get_unwrapped(_First);
|
|
const auto _ULast = _Get_unwrapped(_Last);
|
|
|
|
if constexpr (is_pointer_v<decltype(_UFirst)>) {
|
|
return basic_string_view<_Iter_value_t<_InIt>>(_UFirst, static_cast<size_t>(_ULast - _UFirst));
|
|
} else {
|
|
return basic_string<_Iter_value_t<_InIt>>(_UFirst, _ULast);
|
|
}
|
|
}
|
|
|
|
template <class _InIt, class _Conversion = _Normal_conversion>
|
|
_NODISCARD wstring _Convert_range_to_wide(_InIt _First, _InIt _Last, _Conversion _Tag = {}) {
|
|
return _Convert_stringoid_to_wide(_Stringoid_from_range(_First, _Last), _Tag);
|
|
}
|
|
|
|
_NODISCARD inline wstring _Convert_stringoid_with_locale_to_wide(const string_view _Input, const locale& _Loc) {
|
|
const auto& _Facet = _STD use_facet<codecvt<wchar_t, char, mbstate_t>>(_Loc);
|
|
|
|
wstring _Output(_Input.size(), L'\0'); // almost always sufficient
|
|
|
|
for (;;) {
|
|
mbstate_t _State{};
|
|
const char* const _From_begin = _Input.data();
|
|
const char* const _From_end = _From_begin + _Input.size();
|
|
const char* _From_next = nullptr;
|
|
wchar_t* const _To_begin = _Output.data();
|
|
wchar_t* const _To_end = _To_begin + _Output.size();
|
|
wchar_t* _To_next = nullptr;
|
|
|
|
const auto _Result = _Facet.in(_State, _From_begin, _From_end, _From_next, _To_begin, _To_end, _To_next);
|
|
|
|
if (_From_next < _From_begin || _From_next > _From_end || _To_next < _To_begin || _To_next > _To_end) {
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
|
|
switch (_Result) {
|
|
case codecvt_base::ok:
|
|
_Output.resize(static_cast<size_t>(_To_next - _To_begin));
|
|
return _Output;
|
|
|
|
case codecvt_base::partial:
|
|
// N4950 [locale.codecvt.virtuals]/5:
|
|
// "A return value of partial, if (from_next == from_end), indicates that either the
|
|
// destination sequence has not absorbed all the available destination elements,
|
|
// or that additional source elements are needed before another destination element can be produced."
|
|
if ((_From_next == _From_end && _To_next != _To_end) || _Output.size() > _Output.max_size() / 2) {
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
|
|
_Output.resize(_Output.size() * 2);
|
|
break; // out of switch, keep looping
|
|
|
|
case codecvt_base::error:
|
|
case codecvt_base::noconv:
|
|
default:
|
|
_Throw_system_error(errc::invalid_argument);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct _Is_slash_oper { // predicate testing if input is a preferred-separator or fallback-separator
|
|
_NODISCARD _STATIC_CALL_OPERATOR constexpr bool operator()(const wchar_t _Ch) _CONST_CALL_OPERATOR {
|
|
return _Ch == L'\\' || _Ch == L'/';
|
|
}
|
|
};
|
|
|
|
inline constexpr _Is_slash_oper _Is_slash{};
|
|
|
|
template <class _Ty>
|
|
_NODISCARD _Ty _Unaligned_load(const void* _Ptr) { // load a _Ty from _Ptr
|
|
static_assert(is_trivial_v<_Ty>, "Unaligned loads require trivial types");
|
|
_Ty _Tmp;
|
|
_CSTD memcpy(&_Tmp, _Ptr, sizeof(_Tmp));
|
|
return _Tmp;
|
|
}
|
|
|
|
_NODISCARD inline bool _Is_drive_prefix(const wchar_t* const _First) {
|
|
// test if _First points to a prefix of the form X:
|
|
// pre: _First points to at least 2 wchar_t instances
|
|
// pre: Little endian
|
|
auto _Value = _Unaligned_load<unsigned int>(_First);
|
|
_Value &= 0xFFFF'FFDFu; // transform lowercase drive letters into uppercase ones
|
|
_Value -= (static_cast<unsigned int>(L':') << (sizeof(wchar_t) * CHAR_BIT)) | L'A';
|
|
return _Value < 26;
|
|
}
|
|
|
|
_NODISCARD inline bool _Has_drive_letter_prefix(const wchar_t* const _First, const wchar_t* const _Last) {
|
|
// test if [_First, _Last) has a prefix of the form X:
|
|
return _Last - _First >= 2 && _Is_drive_prefix(_First);
|
|
}
|
|
|
|
_NODISCARD inline const wchar_t* _Find_root_name_end(const wchar_t* const _First, const wchar_t* const _Last) {
|
|
// attempt to parse [_First, _Last) as a path and return the end of root-name if it exists; otherwise, _First
|
|
|
|
// This is the place in the generic grammar where library implementations have the most freedom.
|
|
// Below are example Windows paths, and what we've decided to do with them:
|
|
// * X:DriveRelative, X:\DosAbsolute
|
|
// We parse X: as root-name, if and only if \ is present we consider that root-directory
|
|
// * \RootRelative
|
|
// We parse no root-name, and \ as root-directory
|
|
// * \\server\share
|
|
// We parse \\server as root-name, \ as root-directory, and share as the first element in relative-path.
|
|
// Technically, Windows considers all of \\server\share the logical "root", but for purposes
|
|
// of decomposition we want those split, so that path{R"(\\server\share)"}.replace_filename("other_share")
|
|
// is \\server\other_share
|
|
// * \\?\device
|
|
// * \??\device
|
|
// * \\.\device
|
|
// CreateFile appears to treat these as the same thing; we will set the first three characters as root-name
|
|
// and the first \ as root-directory. Support for these prefixes varies by particular Windows version, but
|
|
// for the purposes of path decomposition we don't need to worry about that.
|
|
// * \\?\UNC\server\share
|
|
// MSDN explicitly documents the \\?\UNC syntax as a special case. What actually happens is that the device
|
|
// Mup, or "Multiple UNC provider", owns the path \\?\UNC in the NT namespace, and is responsible for the
|
|
// network file access. When the user says \\server\share, CreateFile translates that into
|
|
// \\?\UNC\server\share to get the remote server access behavior. Because NT treats this like any other
|
|
// device, we have chosen to treat this as the \\?\ case above.
|
|
if (_Last - _First < 2) {
|
|
return _First;
|
|
}
|
|
|
|
if (_Has_drive_letter_prefix(_First, _Last)) { // check for X: first because it's the most common root-name
|
|
return _First + 2;
|
|
}
|
|
|
|
if (!_Is_slash(_First[0])) { // all the other root-names start with a slash; check that first because
|
|
// we expect paths without a leading slash to be very common
|
|
return _First;
|
|
}
|
|
|
|
// $ means anything other than a slash, including potentially the end of the input
|
|
if (_Last - _First >= 4 && _Is_slash(_First[3]) && (_Last - _First == 4 || !_Is_slash(_First[4])) // \xx\$
|
|
&& ((_Is_slash(_First[1]) && (_First[2] == L'?' || _First[2] == L'.')) // \\?\$ or \\.\$
|
|
|| (_First[1] == L'?' && _First[2] == L'?'))) { // \??\$
|
|
return _First + 3;
|
|
}
|
|
|
|
if (_Last - _First >= 3 && _Is_slash(_First[1]) && !_Is_slash(_First[2])) { // \\server
|
|
return _STD find_if(_First + 3, _Last, _Is_slash);
|
|
}
|
|
|
|
// no match
|
|
return _First;
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_root_name(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the root-name if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
return wstring_view(_First, static_cast<size_t>(_Find_root_name_end(_First, _Last) - _First));
|
|
}
|
|
|
|
_NODISCARD inline const wchar_t* _Find_relative_path(const wchar_t* const _First, const wchar_t* const _Last) {
|
|
// attempt to parse [_First, _Last) as a path and return the start of relative-path
|
|
return _STD find_if_not(_Find_root_name_end(_First, _Last), _Last, _Is_slash);
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_root_directory(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the root-directory if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
const auto _Root_name_end = _Find_root_name_end(_First, _Last);
|
|
const auto _Relative_path = _STD find_if_not(_Root_name_end, _Last, _Is_slash);
|
|
return wstring_view(_Root_name_end, static_cast<size_t>(_Relative_path - _Root_name_end));
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_root_path(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the root-path if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
return wstring_view(_First, static_cast<size_t>(_Find_relative_path(_First, _Last) - _First));
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_relative_path(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the relative-path if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
const auto _Relative_path = _Find_relative_path(_First, _Last);
|
|
return wstring_view(_Relative_path, static_cast<size_t>(_Last - _Relative_path));
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_parent_path(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the parent_path if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
auto _Last = _First + _Str.size();
|
|
const auto _Relative_path = _Find_relative_path(_First, _Last);
|
|
// case 1: relative-path ends in a directory-separator, remove the separator to remove "magic empty path"
|
|
// for example: R"(/cat/dog/\//\)"
|
|
// case 2: relative-path doesn't end in a directory-separator, remove the filename and last directory-separator
|
|
// to prevent creation of a "magic empty path"
|
|
// for example: "/cat/dog"
|
|
while (_Relative_path != _Last && !_Is_slash(_Last[-1])) {
|
|
// handle case 2 by removing trailing filename, puts us into case 1
|
|
--_Last;
|
|
}
|
|
|
|
while (_Relative_path != _Last && _Is_slash(_Last[-1])) { // handle case 1 by removing trailing slashes
|
|
--_Last;
|
|
}
|
|
|
|
return wstring_view(_First, static_cast<size_t>(_Last - _First));
|
|
}
|
|
|
|
_NODISCARD inline const wchar_t* _Find_filename(const wchar_t* const _First, const wchar_t* _Last) {
|
|
// attempt to parse [_First, _Last) as a path and return the start of filename if it exists; otherwise, _Last
|
|
const auto _Relative_path = _Find_relative_path(_First, _Last);
|
|
while (_Relative_path != _Last && !_Is_slash(_Last[-1])) {
|
|
--_Last;
|
|
}
|
|
|
|
return _Last;
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_filename(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the filename if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
const auto _Filename = _Find_filename(_First, _Last);
|
|
return wstring_view(_Filename, static_cast<size_t>(_Last - _Filename));
|
|
}
|
|
|
|
_NODISCARD constexpr const wchar_t* _Find_extension(const wchar_t* const _Filename, const wchar_t* const _Ads) {
|
|
// find dividing point between stem and extension in a generic format filename consisting of [_Filename, _Ads)
|
|
auto _Extension = _Ads;
|
|
if (_Filename == _Extension) { // empty path
|
|
return _Ads;
|
|
}
|
|
|
|
--_Extension;
|
|
if (_Filename == _Extension) {
|
|
// path is length 1 and either dot, or has no dots; either way, extension() is empty
|
|
return _Ads;
|
|
}
|
|
|
|
if (*_Extension == L'.') { // we might have found the end of stem
|
|
if (_Filename == _Extension - 1 && _Extension[-1] == L'.') { // dotdot special case
|
|
return _Ads;
|
|
} else { // x.
|
|
return _Extension;
|
|
}
|
|
}
|
|
|
|
while (_Filename != --_Extension) {
|
|
if (*_Extension == L'.') { // found a dot which is not in first position, so it starts extension()
|
|
return _Extension;
|
|
}
|
|
}
|
|
|
|
// if we got here, either there are no dots, in which case extension is empty, or the first element
|
|
// is a dot, in which case we have the leading single dot special case, which also makes extension empty
|
|
return _Ads;
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_stem(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the stem if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
const auto _Filename = _Find_filename(_First, _Last);
|
|
const auto _Ads =
|
|
_STD find(_Filename, _Last, L':'); // strip alternate data streams in intra-filename decomposition
|
|
const auto _Extension = _Find_extension(_Filename, _Ads);
|
|
return wstring_view(_Filename, static_cast<size_t>(_Extension - _Filename));
|
|
}
|
|
|
|
_NODISCARD inline wstring_view _Parse_extension(const wstring_view _Str) {
|
|
// attempt to parse _Str as a path and return the extension if it exists; otherwise, an empty view
|
|
const auto _First = _Str.data();
|
|
const auto _Last = _First + _Str.size();
|
|
const auto _Filename = _Find_filename(_First, _Last);
|
|
const auto _Ads =
|
|
_STD find(_Filename, _Last, L':'); // strip alternate data streams in intra-filename decomposition
|
|
const auto _Extension = _Find_extension(_Filename, _Ads);
|
|
return wstring_view(_Extension, static_cast<size_t>(_Ads - _Extension));
|
|
}
|
|
|
|
_NODISCARD inline int _Range_compare(const wchar_t* const _Lfirst, const wchar_t* const _Llast,
|
|
const wchar_t* const _Rfirst, const wchar_t* const _Rlast) {
|
|
// 3 way compare [_Lfirst, _Llast) with [_Rfirst, _Rlast)
|
|
return _Traits_compare<char_traits<wchar_t>>(
|
|
_Lfirst, static_cast<size_t>(_Llast - _Lfirst), _Rfirst, static_cast<size_t>(_Rlast - _Rfirst));
|
|
}
|
|
|
|
_NODISCARD inline bool _Is_drive_prefix_with_slash_slash_question(const wstring_view _Text) {
|
|
// test if _Text starts with a \\?\X: prefix
|
|
return _Text.size() >= 6 && _Text._Starts_with(LR"(\\?\)"sv) && _Is_drive_prefix(_Text.data() + 4);
|
|
}
|
|
|
|
_NODISCARD inline bool _Is_dot_or_dotdot(const __std_fs_find_data& _Data) {
|
|
// tests if _File_name of __std_fs_find_data is . or ..
|
|
if (_Data._File_name[0] != L'.') {
|
|
return false;
|
|
}
|
|
|
|
const auto _Second_char = _Data._File_name[1];
|
|
if (_Second_char == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (_Second_char != L'.') {
|
|
return false;
|
|
}
|
|
|
|
return _Data._File_name[2] == 0;
|
|
}
|
|
|
|
struct _Find_file_handle {
|
|
__std_fs_dir_handle _Handle = __std_fs_dir_handle::_Invalid;
|
|
|
|
_Find_file_handle() noexcept = default;
|
|
_Find_file_handle(_Find_file_handle&& _Rhs) noexcept
|
|
: _Handle(_STD exchange(_Rhs._Handle, __std_fs_dir_handle::_Invalid)) {}
|
|
|
|
_Find_file_handle& operator=(_Find_file_handle&& _Rhs) noexcept {
|
|
auto _Tmp = _STD exchange(_Rhs._Handle, __std_fs_dir_handle::_Invalid);
|
|
_Tmp = _STD exchange(_Handle, _Tmp);
|
|
__std_fs_directory_iterator_close(_Tmp);
|
|
return *this;
|
|
}
|
|
|
|
_NODISCARD __std_win_error _Open(const wchar_t* _Path_spec, __std_fs_find_data* _Results) noexcept {
|
|
return __std_fs_directory_iterator_open(_Path_spec, &_Handle, _Results);
|
|
}
|
|
|
|
~_Find_file_handle() noexcept {
|
|
__std_fs_directory_iterator_close(_Handle);
|
|
}
|
|
|
|
explicit operator bool() const noexcept {
|
|
return _Handle != __std_fs_dir_handle::_Invalid;
|
|
}
|
|
};
|
|
|
|
template <class _Base_iter>
|
|
class _Path_iterator;
|
|
|
|
_EXPORT_STD class path {
|
|
template <class _Base_iter>
|
|
friend class _Path_iterator;
|
|
friend inline path absolute(const path& _Input, error_code& _Ec);
|
|
friend inline __std_win_error _Canonical(path& _Result, const wstring& _Text);
|
|
friend inline path temp_directory_path(error_code& _Ec);
|
|
friend inline path current_path(error_code& _Ec);
|
|
friend inline void current_path(const path& _To);
|
|
friend inline void current_path(const path& _To, error_code& _Ec) noexcept;
|
|
friend inline __std_win_error _Read_symlink(const path& _Symlink_path, path& _Result);
|
|
|
|
public:
|
|
using value_type = wchar_t;
|
|
using string_type = _STD wstring;
|
|
|
|
static constexpr wchar_t preferred_separator = L'\\';
|
|
|
|
enum format { auto_format, native_format, generic_format };
|
|
|
|
path() = default;
|
|
path(const path&) = default;
|
|
path(path&&) = default;
|
|
~path() = default;
|
|
path& operator=(const path&) = default;
|
|
path& operator=(path&&) noexcept = default;
|
|
|
|
path(string_type&& _Source) : _Text(_STD move(_Source)) {}
|
|
|
|
path(string_type&& _Source, format) : _Text(_STD move(_Source)) {
|
|
// format has no meaning for this implementation, as the generic grammar is acceptable as a native path
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path(const _Src& _Source, format = auto_format) : _Text(_Convert_Source_to_wide(_Source)) {
|
|
// format has no meaning for this implementation, as the generic grammar is acceptable as a native path
|
|
}
|
|
|
|
template <class _InIt>
|
|
path(_InIt _First, _InIt _Last, format = auto_format) : _Text(_Convert_range_to_wide(_First, _Last)) {
|
|
// format has no meaning for this implementation, as the generic grammar is acceptable as a native path
|
|
static_assert(_Is_EcharT<_Iter_value_t<_InIt>>, "invalid value_type, see N4950 [fs.req]/3");
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path(const _Src& _Source, const locale& _Loc, format = auto_format)
|
|
: _Text(_Convert_stringoid_with_locale_to_wide(_Stringoid_from_Source(_Source), _Loc)) {
|
|
// format has no meaning for this implementation, as the generic grammar is acceptable as a native path
|
|
using _Stringoid = decltype(_Stringoid_from_Source(_Source));
|
|
static_assert(is_same_v<typename _Stringoid::value_type, char>,
|
|
"invalid value_type, see N4950 [fs.path.construct]/5");
|
|
}
|
|
|
|
template <class _InIt>
|
|
path(_InIt _First, _InIt _Last, const locale& _Loc, format = auto_format)
|
|
: _Text(_Convert_stringoid_with_locale_to_wide(_Stringoid_from_range(_First, _Last), _Loc)) {
|
|
// format has no meaning for this implementation, as the generic grammar is acceptable as a native path
|
|
static_assert(is_same_v<_Iter_value_t<_InIt>, char>, "invalid value_type, see N4950 [fs.path.construct]/5");
|
|
}
|
|
|
|
path& operator=(string_type&& _Source) noexcept /* strengthened */ {
|
|
// set native() to _Source
|
|
_Text = _STD move(_Source);
|
|
return *this;
|
|
}
|
|
|
|
path& assign(string_type&& _Source) noexcept /* strengthened */ {
|
|
// set native() to _Source
|
|
_Text = _STD move(_Source);
|
|
return *this;
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path& operator=(const _Src& _Source) { // set native() to _Source
|
|
_Text = _Convert_Source_to_wide(_Source);
|
|
return *this;
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path& assign(const _Src& _Source) { // set native() to _Source
|
|
_Text = _Convert_Source_to_wide(_Source);
|
|
return *this;
|
|
}
|
|
|
|
template <class _InIt>
|
|
path& assign(_InIt _First, _InIt _Last) { // set native() to [_First, _Last)
|
|
static_assert(_Is_EcharT<_Iter_value_t<_InIt>>, "invalid value_type, see N4950 [fs.req]/3");
|
|
_Text = _Convert_range_to_wide(_First, _Last);
|
|
return *this;
|
|
}
|
|
|
|
path& operator/=(const path& _Other) {
|
|
// set *this to the path lexically resolved by _Other relative to *this
|
|
// examples:
|
|
// path{"cat"} / "c:/dog"; // yields "c:/dog"
|
|
// path{"cat"} / "c:"; // yields "c:"
|
|
// path{"c:"} / ""; // yields "c:"
|
|
// path{"c:cat"} / "/dog"; // yields "c:/dog"
|
|
// path{"c:cat"} / "c:dog"; // yields "c:cat/dog"
|
|
// path{"c:cat"} / "d:dog"; // yields "d:dog"
|
|
// several places herein quote the standard, but the standard's variable p is replaced with _Other
|
|
|
|
if (_Other.is_absolute()) { // if _Other.is_absolute(), then op=(_Other)
|
|
return operator=(_Other);
|
|
}
|
|
|
|
const auto _My_first = _Text.data();
|
|
const auto _My_last = _My_first + _Text.size();
|
|
const auto _Other_first = _Other._Text.data();
|
|
const auto _Other_last = _Other_first + _Other._Text.size();
|
|
const auto _My_root_name_end = _Find_root_name_end(_My_first, _My_last);
|
|
const auto _Other_root_name_end = _Find_root_name_end(_Other_first, _Other_last);
|
|
if (_Other_first != _Other_root_name_end
|
|
&& _Range_compare(_My_first, _My_root_name_end, _Other_first, _Other_root_name_end) != 0) {
|
|
// if _Other.has_root_name() && _Other.root_name() != root_name(), then op=(_Other)
|
|
return operator=(_Other);
|
|
}
|
|
|
|
if (_Other_root_name_end != _Other_last && _Is_slash(*_Other_root_name_end)) {
|
|
// If _Other.has_root_directory() removes any root directory and relative-path from *this
|
|
_Text.erase(static_cast<size_t>(_My_root_name_end - _My_first));
|
|
} else {
|
|
// Otherwise, if (!has_root_directory && is_absolute) || has_filename appends path::preferred_separator
|
|
if (_My_root_name_end == _My_last) {
|
|
// Here, !has_root_directory && !has_filename
|
|
// Going through our root_name kinds:
|
|
// X: can't be absolute here, since those paths are absolute only when has_root_directory
|
|
// \\?\ can't exist without has_root_directory
|
|
// \\server can be absolute here
|
|
if (_My_root_name_end - _My_first >= 3) {
|
|
_Text.push_back(preferred_separator);
|
|
}
|
|
} else {
|
|
// Here, has_root_directory || has_filename
|
|
// If there is a trailing slash, the trailing slash might be part of root_directory.
|
|
// If it is, has_root_directory && !has_filename, so the test fails.
|
|
// If there is a trailing slash not part of root_directory, then !has_filename, so only
|
|
// (!has_root_directory && is_absolute) remains
|
|
// Going through our root_name kinds:
|
|
// X:cat\ needs a root_directory to be absolute
|
|
// \\server\cat must have a root_directory to exist with a relative_path
|
|
// \\?\ must have a root_directory to exist
|
|
// As a result, the test fails if there is a trailing slash.
|
|
// If there is no trailing slash, then has_filename, so the test passes.
|
|
// Therefore, the test passes if and only if there is no trailing slash.
|
|
if (!_Is_slash(_My_last[-1])) {
|
|
_Text.push_back(preferred_separator);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then appends the native format pathname of _Other, omitting any root-name from its generic format
|
|
// pathname, to the native format pathname.
|
|
_Text.append(_Other_root_name_end, static_cast<size_t>(_Other_last - _Other_root_name_end));
|
|
return *this;
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path& operator/=(const _Src& _Source) {
|
|
return operator/=(path{_Source});
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path& append(const _Src& _Source) {
|
|
return operator/=(path{_Source});
|
|
}
|
|
|
|
template <class _InIt>
|
|
path& append(_InIt _First, _InIt _Last) {
|
|
static_assert(_Is_EcharT<_Iter_value_t<_InIt>>, "invalid value_type, see N4950 [fs.req]/3");
|
|
return operator/=(path{_First, _Last});
|
|
}
|
|
|
|
path& operator+=(const path& _Added) { // concat _Added to native()
|
|
return operator+=(_Added._Text);
|
|
}
|
|
|
|
path& operator+=(const string_type& _Added) { // concat _Added to native()
|
|
_Text._Orphan_all();
|
|
_Text += _Added;
|
|
return *this;
|
|
}
|
|
|
|
path& operator+=(const wstring_view _Added) { // concat _Added to native()
|
|
_Text._Orphan_all();
|
|
_Text += _Added;
|
|
return *this;
|
|
}
|
|
|
|
path& operator+=(const value_type* const _Added) { // concat _Added to native()
|
|
_Text._Orphan_all();
|
|
_Text += _Added;
|
|
return *this;
|
|
}
|
|
|
|
path& operator+=(const value_type _Added) { // concat _Added to native()
|
|
_Text._Orphan_all();
|
|
_Text += _Added;
|
|
return *this;
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path& operator+=(const _Src& _Added) { // concat _Added to native()
|
|
return operator+=(path{_Added}._Text);
|
|
}
|
|
|
|
template <class _EcharT, enable_if_t<_Is_EcharT<_EcharT>, int> = 0>
|
|
path& operator+=(const _EcharT _Added) { // concat _Added to native()
|
|
return operator+=(path{&_Added, &_Added + 1}._Text);
|
|
}
|
|
|
|
template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
path& concat(const _Src& _Added) { // concat _Added to native()
|
|
return operator+=(path{_Added}._Text);
|
|
}
|
|
|
|
template <class _InIt>
|
|
path& concat(_InIt _First, _InIt _Last) { // concat [_First, _Last) to native()
|
|
static_assert(_Is_EcharT<_Iter_value_t<_InIt>>, "invalid value_type, see N4950 [fs.req]/3");
|
|
return operator+=(path{_First, _Last}._Text);
|
|
}
|
|
|
|
void clear() noexcept { // set *this to the empty path
|
|
_Text._Orphan_all();
|
|
_Text.clear();
|
|
}
|
|
|
|
path& make_preferred() noexcept /* strengthened */ {
|
|
// transform each fallback-separator into preferred-separator
|
|
_Text._Orphan_all();
|
|
_STD replace(_Text.begin(), _Text.end(), L'/', L'\\');
|
|
return *this;
|
|
}
|
|
|
|
path& remove_filename() noexcept /* strengthened */ {
|
|
// remove any filename from *this
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Filename = _Find_filename(_First, _Last);
|
|
_Text._Orphan_all();
|
|
_Text.erase(static_cast<size_t>(_Filename - _First));
|
|
return *this;
|
|
}
|
|
|
|
void _Remove_filename_and_separator() noexcept { // remove filename and preceding non-root directory-separator
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Root_name_end = _Find_root_name_end(_First, _Last);
|
|
const auto _Root_dir_end =
|
|
(_Root_name_end != _Last && _Is_slash(*_Root_name_end)) ? _Root_name_end + 1 : _Root_name_end;
|
|
|
|
using _Reverse_iter = reverse_iterator<const wchar_t*>;
|
|
|
|
const _Reverse_iter _Rbegin{_Last};
|
|
const _Reverse_iter _Rend{_Root_dir_end};
|
|
|
|
const auto _Rslash_first = _STD find_if(_Rbegin, _Rend, _Is_slash);
|
|
const auto _Rslash_last = _STD find_if_not(_Rslash_first, _Rend, _Is_slash);
|
|
|
|
const _Reverse_iter _Rlast{_First};
|
|
|
|
_Text._Orphan_all();
|
|
_Text.erase(static_cast<size_t>(_Rlast - _Rslash_last));
|
|
}
|
|
|
|
path& replace_filename(const path& _Replacement) { // remove any filename from *this and append _Replacement
|
|
remove_filename();
|
|
return operator/=(_Replacement);
|
|
}
|
|
|
|
path& replace_extension(/* const path& _Replacement = {} */) noexcept /* strengthened */ {
|
|
// remove any extension() (and alternate data stream references) from *this's filename()
|
|
const wchar_t* _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Filename = _Find_filename(_First, _Last);
|
|
const auto _Ads = _STD find(_Filename, _Last, L':');
|
|
const auto _Extension = _Find_extension(_Filename, _Ads);
|
|
_Text._Orphan_all();
|
|
_Text.erase(static_cast<size_t>(_Extension - _First));
|
|
return *this;
|
|
}
|
|
|
|
path& replace_extension(const path& _Replacement) {
|
|
// remove any extension() (and alternate data stream references) from *this's filename(), and concatenate
|
|
// _Replacement
|
|
replace_extension();
|
|
if (!_Replacement.empty() && _Replacement._Text[0] != L'.') {
|
|
_Text.push_back(L'.');
|
|
}
|
|
|
|
return operator+=(_Replacement._Text);
|
|
}
|
|
|
|
void swap(path& _Rhs) noexcept {
|
|
_Text.swap(_Rhs._Text);
|
|
}
|
|
|
|
_NODISCARD const string_type& native() const noexcept {
|
|
// return a reference to the internally stored wstring in the native format
|
|
return _Text;
|
|
}
|
|
|
|
_NODISCARD const value_type* c_str() const noexcept {
|
|
// return a NTCTS to the internally stored path in the native format
|
|
return _Text.c_str();
|
|
}
|
|
|
|
operator string_type() const { // implicitly convert *this into a string containing the native format
|
|
return _Text;
|
|
}
|
|
|
|
template <class _EcharT, class _Traits = char_traits<_EcharT>, class _Alloc = allocator<_EcharT>,
|
|
enable_if_t<_Is_EcharT<_EcharT>, int> = 0>
|
|
_NODISCARD basic_string<_EcharT, _Traits, _Alloc> string(const _Alloc& _Al = _Alloc()) const {
|
|
// convert the native path from this instance into a basic_string
|
|
return _Convert_wide_to<_Traits>(_Text, _Al);
|
|
}
|
|
|
|
_NODISCARD _STD string string() const { // convert the native path from this instance into a string
|
|
return string<char>();
|
|
}
|
|
|
|
_NODISCARD _STD wstring wstring() const { // copy the native path from this instance into a wstring
|
|
return _Text;
|
|
}
|
|
|
|
_NODISCARD auto u8string() const { // convert the native path from this instance into a UTF-8 string
|
|
#ifdef __cpp_lib_char8_t
|
|
using _U8Ty = char8_t;
|
|
#else // ^^^ defined(__cpp_lib_char8_t) / !defined(__cpp_lib_char8_t) vvv
|
|
using _U8Ty = char;
|
|
#endif // ^^^ !defined(__cpp_lib_char8_t) ^^^
|
|
return _Convert_wide_to_narrow<char_traits<_U8Ty>>(__std_code_page::_Utf8, _Text, allocator<_U8Ty>{});
|
|
}
|
|
|
|
_NODISCARD _STD u16string u16string() const { // convert the native path from this instance into a u16string
|
|
return string<char16_t>();
|
|
}
|
|
|
|
_NODISCARD _STD u32string u32string() const { // convert the native path from this instance into a u32string
|
|
return string<char32_t>();
|
|
}
|
|
|
|
template <class _EcharT, class _Traits = char_traits<_EcharT>, class _Alloc = allocator<_EcharT>,
|
|
enable_if_t<_Is_EcharT<_EcharT>, int> = 0>
|
|
_NODISCARD basic_string<_EcharT, _Traits, _Alloc> generic_string(const _Alloc& _Al = _Alloc()) const {
|
|
// convert the native path from this instance into a generic basic_string
|
|
using _Alwide = _Rebind_alloc_t<_Alloc, wchar_t>;
|
|
_Alwide _Al_wchar(_Al);
|
|
basic_string<wchar_t, char_traits<wchar_t>, _Alwide> _Generic_str(_Al_wchar);
|
|
_Generic_str.resize(_Text.size());
|
|
_STD replace_copy(_Text.begin(), _Text.end(), _Generic_str.begin(), L'\\', L'/');
|
|
return _Convert_wide_to<_Traits>(_Generic_str, _Al);
|
|
}
|
|
|
|
_NODISCARD _STD string generic_string() const {
|
|
// convert the native path from this instance into a generic string
|
|
return generic_string<char>();
|
|
}
|
|
|
|
_NODISCARD _STD wstring generic_wstring() const {
|
|
// convert the current native path into a copy of it in the generic format
|
|
// note: intra-filename() observers stem() and extension() strip alternate data
|
|
// streams, but filenames with alternate data streams inside can serve as
|
|
// perfectly valid values of filename in the generic format, so in the interest of
|
|
// destroying less information we have preserved them here.
|
|
_STD wstring _Result;
|
|
_Result.resize(_Text.size());
|
|
_STD replace_copy(_Text.begin(), _Text.end(), _Result.begin(), L'\\', L'/');
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD auto generic_u8string() const {
|
|
// convert the native path from this instance into a generic UTF-8 string
|
|
auto _Result = u8string();
|
|
using _U8Ty = decltype(_Result)::value_type;
|
|
_STD replace(_Result.begin(), _Result.end(), _U8Ty{u8'\\'}, _U8Ty{u8'/'});
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD _STD u16string generic_u16string() const {
|
|
// convert the native path from this instance into a generic u16string
|
|
_STD u16string _Result = u16string();
|
|
_STD replace(_Result.begin(), _Result.end(), u'\\', u'/');
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD _STD u32string generic_u32string() const {
|
|
// convert the native path from this instance into a generic u32string
|
|
_STD u32string _Result = u32string();
|
|
_STD replace(_Result.begin(), _Result.end(), U'\\', U'/');
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD int compare(const path& _Other) const noexcept { // compare *this with _Other
|
|
return compare(static_cast<wstring_view>(_Other._Text));
|
|
}
|
|
|
|
_NODISCARD int compare(const string_type& _Other) const noexcept /* strengthened */ {
|
|
// compare *this with _Other
|
|
return compare(static_cast<wstring_view>(_Other));
|
|
}
|
|
|
|
_NODISCARD int compare(const basic_string_view<value_type> _Other) const noexcept /* strengthened */ {
|
|
// compare *this with _Other
|
|
// several places herein quote the standard, but the standard's variable p is replaced with _Other
|
|
const auto _My_first = _Text.data();
|
|
const auto _My_last = _My_first + _Text.size();
|
|
const auto _My_root_name_end = _Find_root_name_end(_My_first, _My_last);
|
|
const auto _Other_first = _Other.data();
|
|
const auto _Other_last = _Other_first + _Other.size();
|
|
const auto _Other_root_name_end = _Find_root_name_end(_Other_first, _Other_last);
|
|
|
|
// Let rootNameComparison be the result of this->root_name().native().compare(_Other.root_name().native())
|
|
const int _Root_cmp = _Range_compare(_My_first, _My_root_name_end, _Other_first, _Other_root_name_end);
|
|
if (_Root_cmp != 0) { // If rootNameComparison is not 0, rootNameComparison
|
|
return _Root_cmp;
|
|
}
|
|
|
|
auto _My_relative = _STD find_if_not(_My_root_name_end, _My_last, _Is_slash);
|
|
auto _Other_relative = _STD find_if_not(_Other_root_name_end, _Other_last, _Is_slash);
|
|
const bool _My_has_root_name = _My_root_name_end != _My_relative;
|
|
const bool _Other_has_root_name = _Other_root_name_end != _Other_relative;
|
|
// If !this->has_root_directory() and _Other.has_root_directory(), a value less than 0
|
|
// If this->has_root_directory() and !_Other.has_root_directory(), a value greater than 0
|
|
const int _Root_name_cmp = _My_has_root_name - _Other_has_root_name;
|
|
if (_Root_name_cmp != 0) {
|
|
return _Root_name_cmp;
|
|
}
|
|
|
|
// Otherwise, lexicographic by element
|
|
for (;;) {
|
|
const bool _My_empty = _My_relative == _My_last;
|
|
const bool _Other_empty = _Other_relative == _Other_last;
|
|
const int _Empty_cmp = _Other_empty - _My_empty;
|
|
if (_My_empty || _Empty_cmp != 0) {
|
|
return _Empty_cmp;
|
|
}
|
|
|
|
const bool _My_slash = _Is_slash(*_My_relative);
|
|
const bool _Other_slash = _Is_slash(*_Other_relative);
|
|
const int _Slash_cmp = _Other_slash - _My_slash;
|
|
if (_Slash_cmp != 0) {
|
|
return _Slash_cmp;
|
|
}
|
|
|
|
if (_My_slash) { // comparing directory-separator
|
|
_My_relative = _STD find_if_not(_My_relative + 1, _My_last, _Is_slash);
|
|
_Other_relative = _STD find_if_not(_Other_relative + 1, _Other_last, _Is_slash);
|
|
continue;
|
|
}
|
|
|
|
const int _Cmp = *_My_relative - *_Other_relative;
|
|
if (_Cmp != 0) {
|
|
return _Cmp;
|
|
}
|
|
|
|
++_My_relative;
|
|
++_Other_relative;
|
|
}
|
|
}
|
|
|
|
_NODISCARD int compare(const value_type* const _Other) const noexcept /* strengthened */ {
|
|
// compare *this with _Other
|
|
return compare(static_cast<wstring_view>(_Other));
|
|
}
|
|
|
|
_NODISCARD path root_name() const {
|
|
// parse the root-name from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_root_name(_Text);
|
|
}
|
|
|
|
_NODISCARD path root_directory() const {
|
|
// parse the root-directory from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_root_directory(_Text);
|
|
}
|
|
|
|
_NODISCARD path root_path() const {
|
|
// parse the root-path from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_root_path(_Text);
|
|
}
|
|
|
|
_NODISCARD path relative_path() const {
|
|
// parse the relative-path from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_relative_path(_Text);
|
|
}
|
|
|
|
_NODISCARD path parent_path() const {
|
|
// parse the parent-path from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_parent_path(_Text);
|
|
}
|
|
|
|
_NODISCARD path filename() const {
|
|
// parse the filename from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_filename(_Text);
|
|
}
|
|
|
|
_NODISCARD path stem() const {
|
|
// parse the stem from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_stem(_Text);
|
|
}
|
|
|
|
_NODISCARD path extension() const {
|
|
// parse the extension from *this and return a copy if present; otherwise, return the empty path
|
|
return _Parse_extension(_Text);
|
|
}
|
|
|
|
_NODISCARD bool empty() const noexcept {
|
|
return _Text.empty();
|
|
}
|
|
|
|
_NODISCARD bool has_root_name() const noexcept /* strengthened */ {
|
|
// parse the root-name from *this and return whether it exists
|
|
return !_Parse_root_name(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_root_directory() const noexcept /* strengthened */ {
|
|
// parse the root-directory from *this and return whether it exists
|
|
return !_Parse_root_directory(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_root_path() const noexcept /* strengthened */ {
|
|
// parse the root-path from *this and return whether it exists
|
|
return !_Parse_root_path(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_relative_path() const noexcept /* strengthened */ {
|
|
// parse the relative-path from *this and return whether it exists
|
|
return !_Parse_relative_path(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_parent_path() const noexcept /* strengthened */ {
|
|
// parse the parent-path from *this and return whether it exists
|
|
return !_Parse_parent_path(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_filename() const noexcept /* strengthened */ {
|
|
// parse the filename from *this and return whether it exists
|
|
return !_Parse_filename(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_stem() const noexcept /* strengthened */ {
|
|
// parse the stem from *this and return whether it exists
|
|
return !_Parse_stem(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool has_extension() const noexcept /* strengthened */ {
|
|
// parse the extension from *this and return whether it exists
|
|
return !_Parse_extension(_Text).empty();
|
|
}
|
|
|
|
_NODISCARD bool is_absolute() const noexcept /* strengthened */ {
|
|
// paths with a root-name that is a drive letter and no root-directory are drive relative, such as x:example
|
|
// paths with no root-name or root-directory are relative, such as example
|
|
// paths with no root-name but a root-directory are root relative, such as \example
|
|
// all other paths are absolute
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
if (_Has_drive_letter_prefix(_First, _Last)) { // test for X:\ but not X:cat
|
|
return _Last - _First >= 3 && _Is_slash(_First[2]);
|
|
}
|
|
|
|
// if root-name is otherwise nonempty, then it must be one of the always-absolute prefixes like
|
|
// \\?\ or \\server, so the path is absolute. Otherwise it is relative.
|
|
return _First != _Find_root_name_end(_First, _Last);
|
|
}
|
|
|
|
_NODISCARD bool is_relative() const noexcept /* strengthened */ {
|
|
// test if *this is a relative path
|
|
return !is_absolute();
|
|
}
|
|
|
|
_NODISCARD path lexically_normal() const {
|
|
constexpr wstring_view _Dot = L"."sv;
|
|
constexpr wstring_view _Dot_dot = L".."sv;
|
|
|
|
// N4950 [fs.path.generic]/6:
|
|
// "Normalization of a generic format pathname means:"
|
|
|
|
// "1. If the path is empty, stop."
|
|
if (empty()) {
|
|
return {};
|
|
}
|
|
|
|
// "2. Replace each slash character in the root-name with a preferred-separator."
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Root_name_end = _Find_root_name_end(_First, _Last);
|
|
string_type _Normalized(_First, _Root_name_end);
|
|
_STD replace(_Normalized.begin(), _Normalized.end(), L'/', L'\\');
|
|
|
|
// "3. Replace each directory-separator with a preferred-separator.
|
|
// [ Note 4: The generic pathname grammar defines directory-separator
|
|
// as one or more slashes and preferred-separators. -end note ]"
|
|
vector<wstring_view> _Vec; // Empty wstring_view means directory-separator
|
|
// that will be normalized to a preferred-separator.
|
|
// Non-empty wstring_view means filename.
|
|
_Vec.reserve(19); // avoid frequent re-allocations
|
|
bool _Has_root_directory = false; // true: there is a slash right after root-name.
|
|
auto _Ptr = _Root_name_end;
|
|
if (_Ptr != _Last && _Is_slash(*_Ptr)) {
|
|
_Has_root_directory = true;
|
|
_Normalized += preferred_separator;
|
|
++_Ptr;
|
|
while (_Ptr != _Last && _Is_slash(*_Ptr)) {
|
|
++_Ptr;
|
|
}
|
|
}
|
|
// _Vec will start with a filename (if not empty).
|
|
while (_Ptr != _Last) {
|
|
if (_Is_slash(*_Ptr)) {
|
|
if (_Vec.empty() || !_Vec.back().empty()) {
|
|
// collapse one or more slashes and preferred-separators to one empty wstring_view
|
|
_Vec.emplace_back();
|
|
}
|
|
++_Ptr;
|
|
} else {
|
|
const auto _Filename_end = _STD find_if(_Ptr + 1, _Last, _Is_slash);
|
|
_Vec.emplace_back(_Ptr, static_cast<size_t>(_Filename_end - _Ptr));
|
|
_Ptr = _Filename_end;
|
|
}
|
|
}
|
|
|
|
// "4. Remove each dot filename and any immediately following directory-separator."
|
|
// "5. As long as any appear, remove a non-dot-dot filename immediately followed by a
|
|
// directory-separator and a dot-dot filename, along with any immediately following directory-separator."
|
|
// "6. If there is a root-directory, remove all dot-dot filenames
|
|
// and any directory-separators immediately following them.
|
|
// [ Note 5: These dot-dot filenames attempt to refer to nonexistent parent directories. -end note ]"
|
|
auto _New_end = _Vec.begin();
|
|
for (auto _Pos = _Vec.begin(); _Pos != _Vec.end();) {
|
|
const auto _Elem = *_Pos++; // _Pos points at a filename here; it points at end or a separator after ++.
|
|
if (_Elem == _Dot) {
|
|
// ignore dot (and following separator).
|
|
if (_Pos == _Vec.end()) {
|
|
break;
|
|
}
|
|
} else if (_Elem != _Dot_dot) {
|
|
// append normal filename and separator.
|
|
*_New_end++ = _Elem; // _New_end points at end or a separator after ++.
|
|
if (_Pos == _Vec.end()) {
|
|
break;
|
|
}
|
|
++_New_end; // _New_end(<=_Pos) doesn't point at end; accept separator.
|
|
} else { // _Dot_dot
|
|
if (_New_end != _Vec.begin() && _New_end[-2] != _Dot_dot) {
|
|
// _New_end == _Vec.begin() + 2n here.
|
|
// remove preceding non-dot-dot filename and separator.
|
|
_New_end -= 2;
|
|
if (_Pos == _Vec.end()) {
|
|
break;
|
|
}
|
|
} else if (!_Has_root_directory) {
|
|
// due to 6, append dot-dot and separator only if !_Has_root_directory.
|
|
*_New_end++ = _Dot_dot;
|
|
if (_Pos == _Vec.end()) {
|
|
break;
|
|
}
|
|
++_New_end;
|
|
} else {
|
|
// ignore dot-dot and separator.
|
|
if (_Pos == _Vec.end()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
++_Pos; // _Pos points at a separator here; it points at end or a filename after ++.
|
|
}
|
|
_Vec.erase(_New_end, _Vec.end());
|
|
|
|
// "7. If the last filename is dot-dot, remove any trailing directory-separator."
|
|
if (_Vec.size() >= 2 && _Vec.back().empty() && _Vec.end()[-2] == _Dot_dot) {
|
|
_Vec.pop_back();
|
|
}
|
|
|
|
// Build up _Normalized by flattening _Vec.
|
|
for (const auto& _Elem : _Vec) {
|
|
if (_Elem.empty()) {
|
|
_Normalized += preferred_separator;
|
|
} else {
|
|
_Normalized += _Elem;
|
|
}
|
|
}
|
|
|
|
// "8. If the path is empty, add a dot."
|
|
if (_Normalized.empty()) {
|
|
_Normalized = _Dot;
|
|
}
|
|
|
|
// "The result of normalization is a path in normal form, which is said to be normalized."
|
|
return path(_STD move(_Normalized));
|
|
}
|
|
|
|
_NODISCARD inline path lexically_relative(const path& _Base) const;
|
|
|
|
_NODISCARD path lexically_proximate(const path& _Base) const {
|
|
path _Result = lexically_relative(_Base);
|
|
|
|
if (_Result.empty()) {
|
|
_Result = *this;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
using iterator = _Path_iterator<string_type::const_iterator>;
|
|
using const_iterator = iterator;
|
|
|
|
_NODISCARD inline iterator begin() const;
|
|
_NODISCARD inline iterator end() const noexcept; // strengthened
|
|
|
|
template <class _Elem, class _Traits>
|
|
friend _STD basic_ostream<_Elem, _Traits>& operator<<( // TRANSITION, VSO-570323
|
|
_STD basic_ostream<_Elem, _Traits>& _Ostr, const path& _Path) { // TRANSITION, VSO-570323
|
|
// insert a path into a stream
|
|
return _Ostr << _STD quoted(_Path.string<_Elem, _Traits>());
|
|
}
|
|
|
|
template <class _Elem, class _Traits>
|
|
friend _STD basic_istream<_Elem, _Traits>& operator>>( // TRANSITION, VSO-570323
|
|
_STD basic_istream<_Elem, _Traits>& _Istr, path& _Path) { // TRANSITION, VSO-570323
|
|
// extract a path from a stream
|
|
basic_string<_Elem, _Traits> _Tmp;
|
|
_Istr >> _STD quoted(_Tmp);
|
|
_Path = _STD move(_Tmp); // obvious optimization not depicted in N4950 [fs.path.io]/3
|
|
return _Istr;
|
|
}
|
|
|
|
_NODISCARD friend bool operator==(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right._Text) == 0;
|
|
}
|
|
|
|
#if _HAS_CXX20
|
|
_NODISCARD friend strong_ordering operator<=>(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right._Text) <=> 0;
|
|
}
|
|
#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv
|
|
_NODISCARD friend bool operator!=(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right) != 0;
|
|
}
|
|
|
|
_NODISCARD friend bool operator<(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right) < 0;
|
|
}
|
|
|
|
_NODISCARD friend bool operator>(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right) > 0;
|
|
}
|
|
|
|
_NODISCARD friend bool operator<=(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right) <= 0;
|
|
}
|
|
|
|
_NODISCARD friend bool operator>=(const path& _Left, const path& _Right) noexcept {
|
|
return _Left.compare(_Right) >= 0;
|
|
}
|
|
#endif // ^^^ !_HAS_CXX20 ^^^
|
|
|
|
_NODISCARD friend path operator/(const path& _Left, const path& _Right) { // append a pair of paths together
|
|
const auto _Right_size = _Right._Text.size();
|
|
const auto _Right_first = _Right._Text.data();
|
|
const auto _Right_last = _Right_first + _Right_size;
|
|
|
|
// Handle the most common case: !has_root_name(_Right) && !has_root_directory(_Right)
|
|
if (_Right_size != 0 && !_Has_drive_letter_prefix(_Right_first, _Right_last) && !_Is_slash(*_Right_first)) {
|
|
const auto _Left_size = _Left._Text.size();
|
|
const auto _Left_first = _Left._Text.data();
|
|
const auto _Left_last = _Left_first + _Left_size;
|
|
|
|
// Appending a slash to "X:" would make it an absolute path
|
|
const bool _Left_is_just_drive = _Left_size == 2 && _Is_drive_prefix(_Left_first);
|
|
const bool _Is_slash_needed = _Left_size != 0 && !_Left_is_just_drive && !_Is_slash(_Left_last[-1]);
|
|
|
|
const auto _Total_size = _Left_size + static_cast<size_t>(_Is_slash_needed) + _Right_size;
|
|
|
|
path _Tmp;
|
|
_Tmp._Text._Resize_and_overwrite(_Total_size, [=](wchar_t* _Ptr, const size_t _Size) {
|
|
_CSTD memcpy(_Ptr, _Left_first, _Left_size * sizeof(wchar_t));
|
|
_Ptr += _Left_size;
|
|
if (_Is_slash_needed) {
|
|
*_Ptr++ = preferred_separator;
|
|
}
|
|
_CSTD memcpy(_Ptr, _Right_first, _Right_size * sizeof(wchar_t));
|
|
return _Size;
|
|
});
|
|
return _Tmp;
|
|
}
|
|
|
|
path _Tmp = _Left;
|
|
_Tmp /= _Right;
|
|
return _Tmp;
|
|
}
|
|
|
|
private:
|
|
string_type _Text;
|
|
};
|
|
|
|
_EXPORT_STD template <class _Src, enable_if_t<_Is_Source<_Src>, int> = 0>
|
|
_CXX20_DEPRECATE_U8PATH _NODISCARD path u8path(const _Src& _Source) { // construct a path from UTF-8 _Source
|
|
return path{_Convert_Source_to_wide(_Source, _Utf8_conversion{})};
|
|
}
|
|
|
|
_EXPORT_STD template <class _InIt>
|
|
_CXX20_DEPRECATE_U8PATH _NODISCARD path u8path(_InIt _First, _InIt _Last) {
|
|
// construct a path from UTF-8 [_First, _Last)
|
|
static_assert(_Is_EcharT<_Iter_value_t<_InIt>>, "invalid value_type, see N4950 [fs.req]/3");
|
|
return path{_Convert_range_to_wide(_First, _Last, _Utf8_conversion{})};
|
|
}
|
|
|
|
template <class _Base_iter>
|
|
class _Path_iterator { // almost bidirectional iterator for path
|
|
public:
|
|
// Note that the path::iterator can be decremented and can be dereferenced multiple times,
|
|
// but doesn't actually meet the forward iterator requirements. See N4950 [fs.path.itr]/2:
|
|
// ... for dereferenceable iterators a and b of type path::iterator with a == b, there is no requirement
|
|
// that *a and *b are bound to the same object
|
|
// This means that you can't give path::iterator to std::reverse_iterator, as operator* there ends
|
|
// up returning a reference to a destroyed temporary. Other algorithms requiring bidirectional
|
|
// iterators may be similarly affected, so we've marked input for now.
|
|
// Also note, in the vector<path>(p.begin(), p.end()) case, the user actually probably wants
|
|
// input behavior, as distance()-ing a path is fairly expensive.
|
|
using iterator_category = input_iterator_tag;
|
|
using value_type = path;
|
|
using difference_type = ptrdiff_t;
|
|
using pointer = const path*;
|
|
using reference = const path&;
|
|
|
|
_Path_iterator() = default;
|
|
|
|
_Path_iterator(const _Base_iter& _Position_, const path* _Mypath_) noexcept
|
|
: _Position(_Position_), _Element(), _Mypath(_Mypath_) {}
|
|
|
|
_Path_iterator(const _Base_iter& _Position_, const path& _Element_, const path* _Mypath_)
|
|
: _Position(_Position_), _Element(_Element_), _Mypath(_Mypath_) {}
|
|
_Path_iterator(const _Base_iter& _Position_, path&& _Element_, const path* _Mypath_)
|
|
: _Position(_Position_), _Element(_STD move(_Element_)), _Mypath(_Mypath_) {}
|
|
|
|
_Path_iterator(const _Path_iterator&) = default;
|
|
_Path_iterator(_Path_iterator&&) = default;
|
|
_Path_iterator& operator=(const _Path_iterator&) = default;
|
|
_Path_iterator& operator=(_Path_iterator&&) = default;
|
|
|
|
_NODISCARD reference operator*() const noexcept {
|
|
return _Element;
|
|
}
|
|
|
|
_NODISCARD pointer operator->() const noexcept {
|
|
return _STD addressof(_Element);
|
|
}
|
|
|
|
_Path_iterator& operator++() {
|
|
const auto& _Text = _Mypath->native();
|
|
const auto _Size = _Element.native().size();
|
|
_Adl_verify_range(_Text.begin(), _Position); // engaged when *this is checked
|
|
const auto _Begin = _Text.data();
|
|
const auto _End = _Begin + _Text.size();
|
|
_Adl_verify_range(_Begin, _Position); // engaged when *this is unchecked
|
|
if (_Begin == _Get_unwrapped(_Position)) { // test if the next element will be root-directory
|
|
_Position += static_cast<ptrdiff_t>(_Size);
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Root_name_end = _Find_root_name_end(_First, _Last);
|
|
const auto _Root_directory_end = _STD find_if_not(_Root_name_end, _Last, _Is_slash);
|
|
if (_First != _Root_name_end && _Root_name_end != _Root_directory_end) {
|
|
// current element is root-name, root-directory exists, so next is root-directory
|
|
_Element._Text.assign(_Root_name_end, _Root_directory_end);
|
|
return *this;
|
|
}
|
|
|
|
// If we get here, either there is no root-name, and by !_Is_slash(*_Position), no root-directory,
|
|
// or, current element is root-name, and root-directory doesn't exist.
|
|
// Either way, the next element is the first of relative-path
|
|
} else if (_Is_slash(*_Position)) { // current element is root-directory, or the "magic empty path"
|
|
if (_Size == 0) { // current element was "magic empty path", become end()
|
|
++_Position;
|
|
return *this;
|
|
}
|
|
|
|
// current element was root-directory, advance to relative-path
|
|
_Position += static_cast<ptrdiff_t>(_Size);
|
|
} else { // current element is one of relative-path
|
|
_Position += static_cast<ptrdiff_t>(_Size);
|
|
}
|
|
|
|
if (_Get_unwrapped(_Position) == _End) {
|
|
_Element.clear();
|
|
return *this;
|
|
}
|
|
|
|
// at this point, the next element is a standard filename from relative_path(), and _Position
|
|
// points at the preferred-separator or fallback-separator after the previous element
|
|
while (_Is_slash(*_Position)) { // advance to the start of the following path element
|
|
if (_Get_unwrapped(++_Position) == _End) { // "magic" empty element selected
|
|
--_Position;
|
|
_Element.clear();
|
|
return *this;
|
|
}
|
|
}
|
|
|
|
_Element._Text.assign(_Get_unwrapped(_Position), _STD find_if(_Get_unwrapped(_Position), _End, _Is_slash));
|
|
return *this;
|
|
}
|
|
|
|
_Path_iterator operator++(int) {
|
|
_Path_iterator _Tmp = *this;
|
|
++*this;
|
|
return _Tmp;
|
|
}
|
|
|
|
_Path_iterator& operator--() {
|
|
const auto& _Text = _Mypath->native();
|
|
_Adl_verify_range(_Text.begin(), _Position); // engaged when *this is checked
|
|
const auto _First = _Text.data();
|
|
_Adl_verify_range(_First, _Position); // engaged when *this is unchecked
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Root_name_end_ptr = _Find_root_name_end(_First, _Last);
|
|
const auto _Root_directory_end_ptr = _STD find_if_not(_Root_name_end_ptr, _Last, _Is_slash);
|
|
|
|
if (_Root_name_end_ptr != _Root_directory_end_ptr && _Get_unwrapped(_Position) == _Root_directory_end_ptr) {
|
|
// current element is the first of relative-path, and the prev element is root-directory
|
|
_Seek_wrapped(_Position, _Root_name_end_ptr);
|
|
_Element._Text.assign(
|
|
_Root_name_end_ptr, static_cast<size_t>(_Root_directory_end_ptr - _Root_name_end_ptr));
|
|
return *this;
|
|
}
|
|
|
|
if (_First != _Root_name_end_ptr && _Get_unwrapped(_Position) == _Root_name_end_ptr) {
|
|
// current element is root-directory or, if that doesn't exist, first
|
|
// element of relative-path prev element therefore is root-name
|
|
_Seek_wrapped(_Position, _First);
|
|
_Element._Text.assign(_First, static_cast<size_t>(_Root_name_end_ptr - _First));
|
|
return *this;
|
|
}
|
|
|
|
// from here, the result will be somewhere in relative-path
|
|
if (_Get_unwrapped(_Position) == _Last && _Is_slash(_Position[-1])) { // target is "magic empty path"
|
|
--_Position;
|
|
_Element.clear();
|
|
return *this;
|
|
}
|
|
|
|
while (_Root_directory_end_ptr != _Get_unwrapped(_Position) && _Is_slash(_Position[-1])) {
|
|
--_Position;
|
|
}
|
|
|
|
const auto _New_end = _Position;
|
|
while (_Root_directory_end_ptr != _Get_unwrapped(_Position) && !_Is_slash(_Position[-1])) {
|
|
--_Position;
|
|
}
|
|
|
|
_Element._Text.assign(_Position, _New_end);
|
|
return *this;
|
|
}
|
|
|
|
_Path_iterator operator--(int) {
|
|
_Path_iterator _Tmp = *this;
|
|
--*this;
|
|
return _Tmp;
|
|
}
|
|
|
|
_NODISCARD friend bool operator==(const _Path_iterator& _Lhs, const _Path_iterator& _Rhs) {
|
|
return _Lhs._Position == _Rhs._Position;
|
|
}
|
|
|
|
#if !_HAS_CXX20
|
|
_NODISCARD friend bool operator!=(const _Path_iterator& _Lhs, const _Path_iterator& _Rhs) {
|
|
return _Lhs._Position != _Rhs._Position;
|
|
}
|
|
#endif // !_HAS_CXX20
|
|
|
|
#if _ITERATOR_DEBUG_LEVEL != 0
|
|
friend void _Verify_range(const _Path_iterator& _Lhs, const _Path_iterator& _Rhs) {
|
|
_Verify_range(_Lhs._Position, _Rhs._Position);
|
|
}
|
|
#endif // _ITERATOR_DEBUG_LEVEL != 0
|
|
|
|
using _Prevent_inheriting_unwrap = _Path_iterator;
|
|
|
|
template <class _Iter2 = _Base_iter, enable_if_t<_Unwrappable_v<const _Iter2&>, int> = 0>
|
|
_NODISCARD _Path_iterator<_Unwrapped_t<const _Iter2&>> _Unwrapped() const& {
|
|
return {_Position._Unwrapped(), _Element, _Mypath};
|
|
}
|
|
template <class _Iter2 = _Base_iter, enable_if_t<_Unwrappable_v<_Iter2>, int> = 0>
|
|
_NODISCARD _Path_iterator<_Unwrapped_t<_Iter2>> _Unwrapped() && noexcept {
|
|
_STL_INTERNAL_STATIC_ASSERT(_Has_nothrow_unwrapped<_Iter2>);
|
|
return {_Position._Unwrapped(), _STD move(_Element), _Mypath};
|
|
}
|
|
|
|
static constexpr bool _Unwrap_when_unverified = _Do_unwrap_when_unverified_v<_Base_iter>;
|
|
|
|
template <class _Other>
|
|
friend class _Path_iterator;
|
|
|
|
template <class _Other, enable_if_t<_Wrapped_seekable_v<_Base_iter, const _Other&>, int> = 0>
|
|
constexpr void _Seek_to(const _Path_iterator<_Other>& _It) {
|
|
_Position._Seek_to(_It._Position);
|
|
_Element = _It._Element;
|
|
// _Mypath intentionally unchanged
|
|
}
|
|
|
|
private:
|
|
// if the current element is the "magic empty path",
|
|
// points to preferred-separator or fallback-separator immediately before
|
|
// otherwise, points to the first element of _Element
|
|
_Base_iter _Position{};
|
|
path _Element{};
|
|
const path* _Mypath{};
|
|
};
|
|
|
|
_EXPORT_STD inline void swap(path& _Left, path& _Right) noexcept {
|
|
_Left.swap(_Right);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline size_t hash_value(const path& _Path) noexcept {
|
|
// calculate a hash value for _Path
|
|
// See path::compare; we effectively decompose the path with special handling for root_name, root_directory.
|
|
// Examples:
|
|
// c:\cat\dog => {"c:", true , "cat", "dog"}
|
|
// c:cat\dog => {"c:", false, "cat", "dog"}
|
|
// \cat\dog => {"" , true , "cat", "dog"}
|
|
// cat\dog => {"" , false, "cat", "dog"}
|
|
// c:\cat\dog\ => {"c:", true , "cat", "dog", ""}
|
|
size_t _Val = _FNV_offset_basis;
|
|
const auto& _Text = _Path.native();
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
|
|
// First, like compare, examine the raw root_name directly
|
|
auto _Next = _Find_root_name_end(_First, _Last);
|
|
_Val = _Fnv1a_append_range(_Val, _First, _Next);
|
|
|
|
// The remaining path elements, including root_directory, are effectively hashed by normalizing each
|
|
// directory-separator into a single preferred-separator when that goes into the hash function.
|
|
// path::compare has special handling for root_directory to ensure c:\cat sorts before c:cat, but hash only
|
|
// cares about equality, so no special case is necessary.
|
|
bool _Slash_inserted = false;
|
|
for (; _Next != _Last; ++_Next) {
|
|
if (_Is_slash(*_Next)) {
|
|
if (!_Slash_inserted) {
|
|
_Val = _Fnv1a_append_value(_Val, path::preferred_separator);
|
|
_Slash_inserted = true;
|
|
}
|
|
} else {
|
|
_Val = _Fnv1a_append_value(_Val, *_Next);
|
|
_Slash_inserted = false;
|
|
}
|
|
}
|
|
|
|
return _Val;
|
|
}
|
|
|
|
_NODISCARD inline bool _Starts_with_root_name(const wchar_t* const _First, const wchar_t* const _Last) {
|
|
return _Find_root_name_end(_First, _Last) != _First;
|
|
}
|
|
|
|
_NODISCARD inline bool _Relative_path_contains_root_name(const path& _Path) {
|
|
const auto& _Native = _Path.native();
|
|
auto _First = _Native.data();
|
|
const auto _Last = _First + _Native.size();
|
|
_First = _Find_relative_path(_First, _Last);
|
|
while (_First != _Last) {
|
|
const auto _Next = _STD find_if(_First, _Last, _Is_slash);
|
|
if (_Starts_with_root_name(_First, _Next)) {
|
|
return true;
|
|
}
|
|
_First = _STD find_if_not(_Next, _Last, _Is_slash);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD inline path path::lexically_relative(const path& _Base_raw) const {
|
|
constexpr wstring_view _Dot = L"."sv;
|
|
constexpr wstring_view _Dot_dot = L".."sv;
|
|
|
|
// LWG-3699: `lexically_relative` on UNC drive paths (`\\?\C:\...`) results in a default-constructed value
|
|
// This avoids doing any unnecessary copies;
|
|
// the return value of `relative_path()` is lifetime-extended if necessary.
|
|
const bool _Both_unc_paths = _Is_drive_prefix_with_slash_slash_question(_Text)
|
|
&& _Is_drive_prefix_with_slash_slash_question(_Base_raw._Text);
|
|
const path& _This = _Both_unc_paths ? relative_path() : *this;
|
|
const path& _Base = _Both_unc_paths ? _Base_raw.relative_path() : _Base_raw;
|
|
|
|
path _Result;
|
|
|
|
if (_This.root_name() != _Base.root_name() || _This.is_absolute() != _Base.is_absolute()
|
|
|| (!_This.has_root_directory() && _Base.has_root_directory())
|
|
|| (_Relative_path_contains_root_name(_This) || _Relative_path_contains_root_name(_Base))) {
|
|
return _Result;
|
|
}
|
|
|
|
const iterator _This_end = _This.end();
|
|
const iterator _Base_begin = _Base.begin();
|
|
const iterator _Base_end = _Base.end();
|
|
|
|
auto _Mismatched = _STD mismatch(_This.begin(), _This_end, _Base_begin, _Base_end);
|
|
iterator& _A_iter = _Mismatched.first;
|
|
iterator& _B_iter = _Mismatched.second;
|
|
|
|
if (_A_iter == _This_end && _B_iter == _Base_end) {
|
|
_Result = _Dot;
|
|
return _Result;
|
|
}
|
|
|
|
{ // Skip root-name and root-directory elements, N4950 [fs.path.itr]/4.1, 4.2
|
|
ptrdiff_t _B_dist = _STD distance(_Base_begin, _B_iter);
|
|
|
|
const ptrdiff_t _Base_root_dist =
|
|
static_cast<ptrdiff_t>(_Base.has_root_name()) + static_cast<ptrdiff_t>(_Base.has_root_directory());
|
|
|
|
while (_B_dist < _Base_root_dist) {
|
|
++_B_iter;
|
|
++_B_dist;
|
|
}
|
|
}
|
|
|
|
ptrdiff_t _Num = 0;
|
|
|
|
for (; _B_iter != _Base_end; ++_B_iter) {
|
|
const path& _Elem = *_B_iter;
|
|
|
|
if (_Elem.empty()) { // skip empty element, N4950 [fs.path.itr]/4.4
|
|
} else if (_Elem == _Dot) { // skip filename elements that are dot, N4950 [fs.path.gen]/3.6
|
|
} else if (_Elem == _Dot_dot) {
|
|
--_Num;
|
|
} else {
|
|
++_Num;
|
|
}
|
|
}
|
|
|
|
if (_Num < 0) {
|
|
return _Result;
|
|
}
|
|
|
|
if (_Num == 0 && (_A_iter == _This_end || _A_iter->empty())) {
|
|
_Result = _Dot;
|
|
return _Result;
|
|
}
|
|
|
|
for (; _Num > 0; --_Num) {
|
|
_Result /= _Dot_dot;
|
|
}
|
|
|
|
for (; _A_iter != _This_end; ++_A_iter) {
|
|
_Result /= *_A_iter;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD inline path::iterator path::begin() const {
|
|
const auto _First = _Text.data();
|
|
const auto _Last = _First + _Text.size();
|
|
const auto _Root_name_end = _Find_root_name_end(_First, _Last);
|
|
const wchar_t* _First_end;
|
|
if (_First == _Root_name_end) { // first element isn't root-name
|
|
auto _Root_directory_end = _STD find_if_not(_Root_name_end, _Last, _Is_slash);
|
|
if (_First == _Root_directory_end) { // first element is first relative-path entry
|
|
_First_end = _STD find_if(_Root_directory_end, _Last, _Is_slash);
|
|
} else { // first element is root-directory
|
|
_First_end = _Root_directory_end;
|
|
}
|
|
} else { // first element is root-name
|
|
_First_end = _Root_name_end;
|
|
}
|
|
|
|
return iterator(_Text.cbegin(), wstring_view(_First, static_cast<size_t>(_First_end - _First)), this);
|
|
}
|
|
|
|
_NODISCARD inline path::iterator path::end() const noexcept /* strengthened */ {
|
|
return iterator(_Text.cend(), this);
|
|
}
|
|
|
|
_EXPORT_STD class filesystem_error : public system_error { // base of all filesystem-error exceptions
|
|
public:
|
|
filesystem_error(const string& _Message, const error_code _Errcode)
|
|
: system_error(_Errcode, _Message), _What(runtime_error::what()) {}
|
|
|
|
filesystem_error(const string& _Message, const path& _Path1_arg, const error_code _Errcode)
|
|
: system_error(_Errcode, _Message), _Path1(_Path1_arg), _Path2(),
|
|
_What(_Pretty_message(runtime_error::what(), _Path1_arg)) {}
|
|
|
|
filesystem_error(
|
|
const string& _Message, const path& _Path1_arg, const path& _Path2_arg, const error_code _Errcode)
|
|
: system_error(_Errcode, _Message), _Path1(_Path1_arg), _Path2(_Path2_arg),
|
|
_What(_Pretty_message(runtime_error::what(), _Path1_arg, _Path2_arg)) {}
|
|
|
|
_NODISCARD const path& path1() const noexcept {
|
|
return _Path1;
|
|
}
|
|
|
|
_NODISCARD const path& path2() const noexcept {
|
|
return _Path2;
|
|
}
|
|
|
|
_NODISCARD const char* what() const noexcept override {
|
|
return _What.c_str();
|
|
}
|
|
|
|
private:
|
|
static string _Pretty_message(const string_view _Op, const path& _Path1, const path& _Path2 = {}) {
|
|
string _Result;
|
|
// Convert the paths to narrow encoding in a way that gracefully handles non-encodable characters
|
|
const auto _Code_page = __std_fs_code_page();
|
|
const string _Path1_str = _Convert_wide_to_narrow_replace_chars<char_traits<char>>(
|
|
_Code_page, _Path1.native(), allocator<char>{});
|
|
const string _Path2_str = _Convert_wide_to_narrow_replace_chars<char_traits<char>>(
|
|
_Code_page, _Path2.native(), allocator<char>{});
|
|
_Result.reserve(_Op.size() + (_Path2_str.empty() ? 4 : 8) + _Path1_str.size() + _Path2_str.size());
|
|
_Result += _Op;
|
|
_Result += R"(: ")"sv; // 3 chars
|
|
_Result += _Path1_str;
|
|
if (!_Path2_str.empty()) {
|
|
_Result += R"(", ")"sv; // 4 chars
|
|
_Result += _Path2_str;
|
|
}
|
|
_Result += '"'; // 1 char
|
|
return _Result;
|
|
}
|
|
|
|
path _Path1;
|
|
path _Path2;
|
|
string _What;
|
|
|
|
#if !_HAS_EXCEPTIONS
|
|
protected:
|
|
void _Doraise() const override { // perform class-specific exception handling
|
|
_RAISE(*this);
|
|
}
|
|
#endif // !_HAS_EXCEPTIONS
|
|
};
|
|
|
|
[[noreturn]] inline void _Throw_fs_error(const char* _Op, __std_win_error _Error) {
|
|
_THROW(filesystem_error(_Op, _Make_ec(_Error)));
|
|
}
|
|
|
|
[[noreturn]] inline void _Throw_fs_error(const char* _Op, __std_win_error _Error, const path& _Path1) {
|
|
_THROW(filesystem_error(_Op, _Path1, _Make_ec(_Error)));
|
|
}
|
|
|
|
[[noreturn]] inline void _Throw_fs_error(
|
|
const char* _Op, __std_win_error _Error, const path& _Path1, const path& _Path2) {
|
|
_THROW(filesystem_error(_Op, _Path1, _Path2, _Make_ec(_Error)));
|
|
}
|
|
|
|
[[noreturn]] inline void _Throw_fs_error(const char* _Op, const error_code& _Error) {
|
|
_THROW(filesystem_error(_Op, _Error));
|
|
}
|
|
|
|
[[noreturn]] inline void _Throw_fs_error(const char* _Op, const error_code& _Error, const path& _Path1) {
|
|
_THROW(filesystem_error(_Op, _Path1, _Error));
|
|
}
|
|
|
|
[[noreturn]] inline void _Throw_fs_error(
|
|
const char* _Op, const error_code& _Error, const path& _Path1, const path& _Path2) {
|
|
_THROW(filesystem_error(_Op, _Path1, _Path2, _Error));
|
|
}
|
|
|
|
_EXPORT_STD enum class file_type {
|
|
none,
|
|
not_found,
|
|
regular,
|
|
directory,
|
|
symlink,
|
|
|
|
block, // not used on Windows
|
|
character, // not used in this implementation; theoretically some special files like CON
|
|
// might qualify, but querying for this is extremely expensive and unlikely
|
|
// to be useful in practice
|
|
|
|
fifo, // not used on Windows (\\.\pipe named pipes don't behave exactly like POSIX fifos)
|
|
socket, // not used on Windows
|
|
unknown,
|
|
|
|
junction // implementation-defined value indicating an NT junction
|
|
};
|
|
|
|
_EXPORT_STD enum class perms {
|
|
none = 0,
|
|
|
|
owner_read = 0400,
|
|
owner_write = 0200,
|
|
owner_exec = 0100,
|
|
owner_all = 0700,
|
|
|
|
group_read = 0040,
|
|
group_write = 0020,
|
|
group_exec = 0010,
|
|
group_all = 0070,
|
|
|
|
others_read = 0004,
|
|
others_write = 0002,
|
|
others_exec = 0001,
|
|
others_all = 0007,
|
|
|
|
all = 0777, // returned for all files without FILE_ATTRIBUTE_READONLY
|
|
set_uid = 04000,
|
|
set_gid = 02000,
|
|
sticky_bit = 01000,
|
|
mask = 07777,
|
|
unknown = 0xFFFF,
|
|
|
|
_All_write = owner_write | group_write | others_write,
|
|
_File_attribute_readonly = all & ~_All_write // returned for files with FILE_ATTRIBUTE_READONLY
|
|
};
|
|
|
|
_BITMASK_OPS(_EXPORT_STD, perms)
|
|
|
|
_EXPORT_STD enum class copy_options {
|
|
none = static_cast<int>(__std_fs_copy_options::_None),
|
|
|
|
_Existing_mask = static_cast<int>(__std_fs_copy_options::_Existing_mask),
|
|
skip_existing = static_cast<int>(__std_fs_copy_options::_Skip_existing),
|
|
overwrite_existing = static_cast<int>(__std_fs_copy_options::_Overwrite_existing),
|
|
update_existing = static_cast<int>(__std_fs_copy_options::_Update_existing),
|
|
|
|
recursive = 0x10,
|
|
|
|
_Symlinks_mask = 0xF00,
|
|
copy_symlinks = 0x100,
|
|
skip_symlinks = 0x200,
|
|
|
|
_Copy_form_mask = 0xF000,
|
|
directories_only = 0x1000,
|
|
create_symlinks = 0x2000,
|
|
create_hard_links = 0x4000,
|
|
|
|
_Unspecified_copy_prevention_tag = 0x10000 // to be removed by LWG-3057
|
|
};
|
|
|
|
_BITMASK_OPS(_EXPORT_STD, copy_options)
|
|
|
|
_EXPORT_STD class file_status {
|
|
public:
|
|
// [fs.file_status.cons], constructors and destructor
|
|
file_status() noexcept = default;
|
|
explicit file_status(file_type _Ft, perms _Perms = perms::unknown) noexcept : _Myftype(_Ft), _Myperms(_Perms) {}
|
|
file_status(const file_status&) noexcept = default;
|
|
file_status(file_status&&) noexcept = default;
|
|
~file_status() noexcept = default;
|
|
|
|
// assignments
|
|
file_status& operator=(const file_status&) noexcept = default;
|
|
file_status& operator=(file_status&&) noexcept = default;
|
|
|
|
// [fs.file_status.mods], modifiers
|
|
void type(file_type _Ft) noexcept {
|
|
_Myftype = _Ft;
|
|
}
|
|
|
|
void permissions(perms _Perms) noexcept {
|
|
_Myperms = _Perms;
|
|
}
|
|
|
|
// [fs.file_status.obs], observers
|
|
_NODISCARD file_type type() const noexcept {
|
|
return _Myftype;
|
|
}
|
|
|
|
_NODISCARD perms permissions() const noexcept {
|
|
return _Myperms;
|
|
}
|
|
|
|
#if _HAS_CXX20
|
|
_NODISCARD friend bool operator==(const file_status& _Lhs, const file_status& _Rhs) noexcept {
|
|
return _Lhs._Myftype == _Rhs._Myftype && _Lhs._Myperms == _Rhs._Myperms;
|
|
}
|
|
#endif // _HAS_CXX20
|
|
|
|
void _Refresh(const __std_win_error _Error, const __std_fs_stats& _Stats) noexcept {
|
|
if (_Error == __std_win_error::_Success) {
|
|
const auto _Attrs = _Stats._Attributes;
|
|
|
|
if (_Bitmask_includes_any(_Attrs, __std_fs_file_attr::_Readonly)) {
|
|
this->permissions(perms::_File_attribute_readonly);
|
|
} else {
|
|
this->permissions(perms::all);
|
|
}
|
|
|
|
if (_Bitmask_includes_any(_Attrs, __std_fs_file_attr::_Reparse_point)) {
|
|
if (_Stats._Reparse_point_tag == __std_fs_reparse_tag::_Symlink) {
|
|
this->type(file_type::symlink);
|
|
return;
|
|
}
|
|
|
|
if (_Stats._Reparse_point_tag == __std_fs_reparse_tag::_Mount_point) {
|
|
this->type(file_type::junction);
|
|
return;
|
|
}
|
|
|
|
// All other reparse points considered ordinary files or directories
|
|
}
|
|
|
|
if (_Bitmask_includes_any(_Attrs, __std_fs_file_attr::_Directory)) {
|
|
this->type(file_type::directory);
|
|
} else {
|
|
this->type(file_type::regular);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
this->permissions(perms::unknown);
|
|
this->type(__std_is_file_not_found(_Error) ? file_type::not_found : file_type::none);
|
|
}
|
|
|
|
private:
|
|
file_type _Myftype = file_type::none;
|
|
perms _Myperms = perms::unknown;
|
|
};
|
|
|
|
_EXPORT_STD _NODISCARD inline bool exists(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates an existing file
|
|
switch (_Status.type()) {
|
|
case file_type::none:
|
|
case file_type::not_found:
|
|
return false;
|
|
case file_type::regular:
|
|
case file_type::directory:
|
|
case file_type::symlink:
|
|
case file_type::block:
|
|
case file_type::character:
|
|
case file_type::fifo:
|
|
case file_type::socket:
|
|
case file_type::unknown:
|
|
case file_type::junction:
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_block_file(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a block file
|
|
return _Status.type() == file_type::block;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_character_file(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a character file
|
|
return _Status.type() == file_type::character;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_directory(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a directory
|
|
return _Status.type() == file_type::directory;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_fifo(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a fifo
|
|
return _Status.type() == file_type::fifo;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_other(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates other file types
|
|
switch (_Status.type()) {
|
|
case file_type::none:
|
|
case file_type::not_found:
|
|
case file_type::regular:
|
|
case file_type::directory:
|
|
case file_type::symlink:
|
|
return false;
|
|
case file_type::block:
|
|
case file_type::character:
|
|
case file_type::fifo:
|
|
case file_type::socket:
|
|
case file_type::unknown:
|
|
case file_type::junction:
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_regular_file(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a regular file
|
|
return _Status.type() == file_type::regular;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_socket(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a socket
|
|
return _Status.type() == file_type::socket;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_symlink(const file_status _Status) noexcept {
|
|
// tests whether _Status indicates a symlink
|
|
return _Status.type() == file_type::symlink;
|
|
}
|
|
|
|
struct _File_status_and_error {
|
|
file_status _Status;
|
|
__std_win_error _Error;
|
|
|
|
_NODISCARD bool _Not_good() const noexcept {
|
|
// [fs.op.status]/Throws: result values of file_status(file_type::not_found) and
|
|
// file_status(file_type::unknown) are not considered failures and do not cause an exception to be thrown.
|
|
return _Error != __std_win_error::_Success && _Status.type() != file_type::not_found
|
|
&& _Status.type() != file_type::unknown;
|
|
}
|
|
};
|
|
|
|
inline constexpr auto _Status_stats_flags =
|
|
__std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_Follow_symlinks;
|
|
|
|
inline constexpr auto _Symlink_status_stats_flags =
|
|
__std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_Reparse_tag;
|
|
|
|
#if _HAS_CXX20
|
|
_EXPORT_STD using file_time_type = _CHRONO time_point<_CHRONO file_clock>;
|
|
#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv
|
|
using file_time_type = _CHRONO time_point<filesystem::_File_time_clock>;
|
|
#endif // ^^^ !_HAS_CXX20 ^^^
|
|
|
|
struct _Dir_enum_impl;
|
|
struct _Recursive_dir_enum_impl;
|
|
|
|
_EXPORT_STD class directory_entry {
|
|
public:
|
|
// [fs.dir.entry.cons], constructors and destructor
|
|
directory_entry() noexcept : _Cached_data{}, _Path() {}
|
|
|
|
directory_entry(const directory_entry&) = default;
|
|
directory_entry(directory_entry&&) noexcept = default;
|
|
explicit directory_entry(const filesystem::path& _Path_arg) : _Cached_data{}, _Path(_Path_arg) {
|
|
refresh();
|
|
}
|
|
|
|
directory_entry(const filesystem::path& _Path_arg, error_code& _Ec) : _Cached_data{}, _Path(_Path_arg) {
|
|
refresh(_Ec);
|
|
if (_Ec) {
|
|
_Path.clear();
|
|
}
|
|
}
|
|
|
|
~directory_entry() = default;
|
|
|
|
// assignments
|
|
directory_entry& operator=(const directory_entry&) = default;
|
|
directory_entry& operator=(directory_entry&&) noexcept = default;
|
|
|
|
// [fs.dir.entry.mods], modifiers
|
|
void assign(const filesystem::path& _Path_arg) {
|
|
_Path.assign(_Path_arg);
|
|
refresh();
|
|
}
|
|
|
|
void assign(const filesystem::path& _Path_arg, error_code& _Ec) {
|
|
_Ec.clear(); // for exception safety
|
|
_Path.assign(_Path_arg);
|
|
refresh(_Ec);
|
|
}
|
|
|
|
void replace_filename(const filesystem::path& _Path_arg) {
|
|
_Path.replace_filename(_Path_arg);
|
|
refresh();
|
|
}
|
|
|
|
void replace_filename(const filesystem::path& _Path_arg, error_code& _Ec) {
|
|
_Ec.clear(); // for exception safety
|
|
_Path.replace_filename(_Path_arg);
|
|
refresh(_Ec);
|
|
}
|
|
|
|
void refresh() {
|
|
const auto _Error = _Refresh(_Cached_data, _Path);
|
|
if (_Error != __std_win_error::_Success && !__std_is_file_not_found(_Error)) {
|
|
_Throw_fs_error("directory_entry::refresh", _Error, _Path);
|
|
}
|
|
}
|
|
|
|
void refresh(error_code& _Ec) noexcept {
|
|
_Ec.clear();
|
|
const auto _Error = _Refresh(_Cached_data, _Path);
|
|
if (_Error != __std_win_error::_Success && !__std_is_file_not_found(_Error)) {
|
|
_Ec = _Make_ec(_Error);
|
|
}
|
|
}
|
|
|
|
private:
|
|
_NODISCARD bool _Has_cached_attribute(const __std_fs_file_attr _Attrs) const noexcept {
|
|
return _Bitmask_includes_any(_Cached_data._Attributes, _Attrs);
|
|
}
|
|
|
|
public:
|
|
// [fs.dir.entry.obs], observers
|
|
_NODISCARD const filesystem::path& path() const noexcept {
|
|
return _Path;
|
|
}
|
|
|
|
operator const filesystem::path&() const noexcept {
|
|
return _Path;
|
|
}
|
|
|
|
_NODISCARD bool exists() const {
|
|
const auto _Type = this->status().type();
|
|
return _Type != file_type::not_found && _Type != file_type::none;
|
|
}
|
|
|
|
_NODISCARD bool exists(error_code& _Ec) const noexcept {
|
|
const auto _Type = this->status(_Ec).type();
|
|
return _Type != file_type::not_found && _Type != file_type::none;
|
|
}
|
|
|
|
_NODISCARD bool is_block_file() const {
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_block_file(error_code& _Ec) const noexcept {
|
|
_Ec.clear();
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_character_file() const {
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_character_file(error_code& _Ec) const noexcept {
|
|
_Ec.clear();
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_directory() const {
|
|
return _STD filesystem::is_directory(this->status());
|
|
}
|
|
|
|
_NODISCARD bool is_directory(error_code& _Ec) const noexcept {
|
|
return _STD filesystem::is_directory(this->status(_Ec));
|
|
}
|
|
|
|
_NODISCARD bool is_fifo() const {
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_fifo(error_code& _Ec) const noexcept {
|
|
_Ec.clear();
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_other() const {
|
|
return _STD filesystem::is_other(this->status());
|
|
}
|
|
|
|
_NODISCARD bool is_other(error_code& _Ec) const noexcept {
|
|
return _STD filesystem::is_other(this->status(_Ec));
|
|
}
|
|
|
|
_NODISCARD bool is_regular_file() const {
|
|
return _STD filesystem::is_regular_file(this->status());
|
|
}
|
|
|
|
_NODISCARD bool is_regular_file(error_code& _Ec) const noexcept {
|
|
return _STD filesystem::is_regular_file(this->status(_Ec));
|
|
}
|
|
|
|
_NODISCARD bool is_socket() const {
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool is_socket(error_code& _Ec) const noexcept {
|
|
_Ec.clear();
|
|
return false;
|
|
}
|
|
|
|
_NODISCARD bool _Is_symlink_or_junction() const noexcept {
|
|
return _Has_cached_attribute(__std_fs_file_attr::_Reparse_point)
|
|
&& (_Cached_data._Reparse_point_tag == __std_fs_reparse_tag::_Symlink
|
|
|| _Cached_data._Reparse_point_tag == __std_fs_reparse_tag::_Mount_point);
|
|
}
|
|
|
|
_NODISCARD bool is_symlink() const {
|
|
return _STD filesystem::is_symlink(this->symlink_status());
|
|
}
|
|
|
|
_NODISCARD bool is_symlink(error_code& _Ec) const noexcept {
|
|
return _STD filesystem::is_symlink(this->symlink_status(_Ec));
|
|
}
|
|
|
|
_NODISCARD bool _Available(const __std_fs_stats_flags _Flags) const noexcept {
|
|
return _Bitmask_includes_any(_Cached_data._Available, _Flags);
|
|
}
|
|
|
|
private:
|
|
_NODISCARD __std_win_error _File_size(uintmax_t& _Result) const noexcept {
|
|
if (_Available(__std_fs_stats_flags::_File_size)) {
|
|
_Result = _Cached_data._File_size;
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
__std_fs_stats _Stats;
|
|
constexpr auto _Flags = __std_fs_stats_flags::_File_size | __std_fs_stats_flags::_Follow_symlinks;
|
|
const auto _Error =
|
|
__std_fs_get_stats(_Path.c_str(), &_Stats, _Flags, _Cached_data._Symlink_hint_attributes());
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Result = _Stats._File_size;
|
|
} else {
|
|
_Result = static_cast<uintmax_t>(-1);
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
public:
|
|
_NODISCARD uintmax_t file_size() const {
|
|
uintmax_t _Result;
|
|
const auto _Error = _File_size(_Result);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("directory_entry::file_size", _Error, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD uintmax_t file_size(error_code& _Ec) const noexcept {
|
|
uintmax_t _Result;
|
|
_Ec = _Make_ec(_File_size(_Result));
|
|
return _Result;
|
|
}
|
|
|
|
private:
|
|
_NODISCARD __std_win_error _Hard_link_count(uintmax_t& _Result) const noexcept {
|
|
if (_Available(__std_fs_stats_flags::_Link_count)) {
|
|
_Result = _Cached_data._Link_count;
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
__std_fs_stats _Stats;
|
|
constexpr auto _Flags = __std_fs_stats_flags::_Link_count | __std_fs_stats_flags::_Follow_symlinks;
|
|
const auto _Error =
|
|
__std_fs_get_stats(_Path.c_str(), &_Stats, _Flags, _Cached_data._Symlink_hint_attributes());
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Result = _Stats._Link_count;
|
|
} else {
|
|
_Result = static_cast<uintmax_t>(-1);
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
public:
|
|
_NODISCARD uintmax_t hard_link_count() const {
|
|
uintmax_t _Result;
|
|
const auto _Error = _Hard_link_count(_Result);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("directory_entry::hard_link_count", _Error, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD uintmax_t hard_link_count(error_code& _Ec) const noexcept {
|
|
uintmax_t _Result;
|
|
_Ec = _Make_ec(_Hard_link_count(_Result));
|
|
return _Result;
|
|
}
|
|
|
|
private:
|
|
_NODISCARD __std_win_error _Last_write_time(file_time_type& _Result) const noexcept {
|
|
if (_Available(__std_fs_stats_flags::_Last_write_time)) {
|
|
_Result = file_time_type{file_time_type::duration{_Cached_data._Last_write_time}};
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
__std_fs_stats _Stats;
|
|
constexpr auto _Flags = __std_fs_stats_flags::_Last_write_time | __std_fs_stats_flags::_Follow_symlinks;
|
|
const auto _Error =
|
|
__std_fs_get_stats(_Path.c_str(), &_Stats, _Flags, _Cached_data._Symlink_hint_attributes());
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Result = file_time_type{file_time_type::duration{_Stats._Last_write_time}};
|
|
} else {
|
|
_Result = file_time_type{file_time_type::duration{LLONG_MIN}};
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
public:
|
|
_NODISCARD file_time_type last_write_time() const {
|
|
file_time_type _Result;
|
|
const auto _Error = _Last_write_time(_Result);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("directory_entry::last_write_time", _Error, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD file_time_type last_write_time(error_code& _Ec) const noexcept {
|
|
file_time_type _Result;
|
|
_Ec = _Make_ec(_Last_write_time(_Result));
|
|
return _Result;
|
|
}
|
|
|
|
private:
|
|
_NODISCARD _File_status_and_error _Get_any_status(const __std_fs_stats_flags _Flags) const noexcept {
|
|
_File_status_and_error _Result;
|
|
__std_fs_stats _Stats;
|
|
|
|
if (_Bitmask_includes_all(_Cached_data._Available, _Flags)) {
|
|
_Result._Error = __std_win_error::_Success;
|
|
_Result._Status._Refresh(__std_win_error::_Success, _Cached_data);
|
|
} else {
|
|
const auto _Error =
|
|
__std_fs_get_stats(_Path.c_str(), &_Stats, _Flags, _Cached_data._Symlink_hint_attributes());
|
|
_Result._Error = _Error;
|
|
_Result._Status._Refresh(_Error, _Stats);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
public:
|
|
_NODISCARD file_status status() const {
|
|
const auto _Result = _Get_any_status(_Status_stats_flags);
|
|
if (_Result._Not_good()) {
|
|
_Throw_fs_error("directory_entry::status", _Result._Error, _Path);
|
|
}
|
|
|
|
return _Result._Status;
|
|
}
|
|
|
|
_NODISCARD file_status status(error_code& _Ec) const noexcept {
|
|
const auto _Result = _Get_any_status(_Status_stats_flags);
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Status;
|
|
}
|
|
|
|
_NODISCARD file_status symlink_status() const {
|
|
const auto _Result = _Get_any_status(_Symlink_status_stats_flags);
|
|
if (_Result._Not_good()) {
|
|
_Throw_fs_error("directory_entry::symlink_status", _Result._Error, _Path);
|
|
}
|
|
|
|
return _Result._Status;
|
|
}
|
|
|
|
_NODISCARD file_status symlink_status(error_code& _Ec) const noexcept {
|
|
const auto _Result = _Get_any_status(_Symlink_status_stats_flags);
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Status;
|
|
}
|
|
|
|
_NODISCARD bool operator==(const directory_entry& _Rhs) const noexcept {
|
|
return _Path == _Rhs._Path;
|
|
}
|
|
|
|
#if _HAS_CXX20
|
|
_NODISCARD strong_ordering operator<=>(const directory_entry& _Rhs) const noexcept {
|
|
return _Path <=> _Rhs._Path;
|
|
}
|
|
#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv
|
|
_NODISCARD bool operator!=(const directory_entry& _Rhs) const noexcept {
|
|
return _Path != _Rhs._Path;
|
|
}
|
|
|
|
_NODISCARD bool operator<(const directory_entry& _Rhs) const noexcept {
|
|
return _Path < _Rhs._Path;
|
|
}
|
|
|
|
_NODISCARD bool operator<=(const directory_entry& _Rhs) const noexcept {
|
|
return _Path <= _Rhs._Path;
|
|
}
|
|
|
|
_NODISCARD bool operator>(const directory_entry& _Rhs) const noexcept {
|
|
return _Path > _Rhs._Path;
|
|
}
|
|
|
|
_NODISCARD bool operator>=(const directory_entry& _Rhs) const noexcept {
|
|
return _Path >= _Rhs._Path;
|
|
}
|
|
#endif // ^^^ !_HAS_CXX20 ^^^
|
|
|
|
// [fs.dir.entry.io], inserter
|
|
template <class _Elem, class _Traits>
|
|
friend _STD basic_ostream<_Elem, _Traits>& operator<<( // TRANSITION, VSO-570323
|
|
_STD basic_ostream<_Elem, _Traits>& _Ostr, const directory_entry& _Entry) { // TRANSITION, VSO-570323
|
|
// insert a directory_entry into a stream
|
|
return _Ostr << _Entry.path();
|
|
}
|
|
|
|
private:
|
|
void _Refresh(const __std_fs_find_data& _Data) noexcept {
|
|
_Cached_data._Attributes = _Data._Attributes;
|
|
_Cached_data._Reparse_point_tag = _Data._Reparse_point_tag;
|
|
_Cached_data._Available = __std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_Reparse_tag;
|
|
if (!_Bitmask_includes_any(_Data._Attributes, __std_fs_file_attr::_Reparse_point)) {
|
|
_Cached_data._File_size = (static_cast<uintmax_t>(_Data._File_size_high) << 32)
|
|
+ static_cast<uintmax_t>(_Data._File_size_low);
|
|
_CSTD memcpy(
|
|
&_Cached_data._Last_write_time, &_Data._Last_write_time, sizeof(_Cached_data._Last_write_time));
|
|
_Cached_data._Available |= __std_fs_stats_flags::_File_size | __std_fs_stats_flags::_Last_write_time;
|
|
}
|
|
}
|
|
|
|
_NODISCARD static __std_win_error _Refresh(__std_fs_stats& _Stats, const filesystem::path& _Path) noexcept {
|
|
const auto _Error = __std_fs_get_stats(_Path.c_str(), &_Stats, __std_fs_stats_flags::_All_data);
|
|
if (_Error == __std_win_error::_Success) {
|
|
if (_Bitmask_includes_any(_Stats._Attributes, __std_fs_file_attr::_Reparse_point)) {
|
|
_Stats._Available = __std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_Reparse_tag;
|
|
} else {
|
|
_Stats._Available = __std_fs_stats_flags::_All_data;
|
|
}
|
|
} else {
|
|
_Stats._Available = __std_fs_stats_flags::_None;
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
friend _Dir_enum_impl;
|
|
friend _Recursive_dir_enum_impl;
|
|
friend void _Copy_impl(
|
|
const directory_entry& _From, const _STD filesystem::path& _To, copy_options _Options, error_code& _Ec);
|
|
|
|
__std_fs_stats _Cached_data;
|
|
filesystem::path _Path;
|
|
};
|
|
|
|
_EXPORT_STD enum class directory_options { none = 0, follow_directory_symlink = 1, skip_permission_denied = 2 };
|
|
_BITMASK_OPS(_EXPORT_STD, directory_options)
|
|
|
|
_EXPORT_STD _NODISCARD inline bool exists(const path& _Target, error_code& _Ec) noexcept;
|
|
|
|
struct _Dir_enum_impl {
|
|
_NODISCARD static __std_win_error _Advance_and_reset_if_no_more_files(shared_ptr<_Dir_enum_impl>& _Ptr) {
|
|
auto& _Impl = *_Ptr;
|
|
__std_fs_find_data _Data;
|
|
do {
|
|
const auto _Error = __std_fs_directory_iterator_advance(_Impl._Dir._Handle, &_Data);
|
|
if (_Error == __std_win_error::_No_more_files) {
|
|
_Ptr.reset();
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
if (_Error != __std_win_error::_Success) {
|
|
return _Error;
|
|
}
|
|
} while (_Is_dot_or_dotdot(_Data));
|
|
_Impl._Refresh(_Data); // can throw
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_NODISCARD static __std_win_error _Skip_dots(
|
|
__std_fs_dir_handle _Dir_handle, __std_fs_find_data& _Data) noexcept {
|
|
while (_Is_dot_or_dotdot(_Data)) {
|
|
const auto _Error = __std_fs_directory_iterator_advance(_Dir_handle, &_Data);
|
|
if (_Error != __std_win_error::_Success) {
|
|
return _Error;
|
|
}
|
|
}
|
|
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_NODISCARD static __std_win_error _Open_dir(
|
|
path& _Path, const directory_options _Options_arg, _Find_file_handle& _Dir, __std_fs_find_data& _Data) {
|
|
const size_t _Null_term_len = _CSTD wcslen(_Path.c_str());
|
|
if (_Null_term_len == 0 || _Null_term_len != _Path.native().size()) {
|
|
return __std_win_error::_File_not_found;
|
|
}
|
|
|
|
const path _Original_path = _Path;
|
|
_Path /= L"*"sv;
|
|
auto _Error = _Dir._Open(_Path.c_str(), &_Data);
|
|
if (_Error == __std_win_error::_Success) {
|
|
return _Skip_dots(_Dir._Handle, _Data);
|
|
}
|
|
|
|
if (_Error == __std_win_error::_Access_denied
|
|
&& _Bitmask_includes_any(_Options_arg, directory_options::skip_permission_denied)) {
|
|
_Error = __std_win_error::_No_more_files;
|
|
} else if (_Error == __std_win_error::_File_not_found) {
|
|
error_code _Ignored; // When exists() returns true, that implies that the error_code is successful.
|
|
// When exists() returns false, we don't want to interfere with _Open_dir()'s behavior,
|
|
// as it's going to return __std_win_error::_File_not_found.
|
|
if (_STD filesystem::exists(_Original_path, _Ignored)) {
|
|
_Error = __std_win_error::_No_more_files; // Handle empty volumes, see GH-4291
|
|
}
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
struct _Creator { // factored out part common to recursive and non-recursive implementation
|
|
path _Path;
|
|
_Find_file_handle _Dir;
|
|
__std_fs_find_data _Find_data;
|
|
|
|
struct _Create_status {
|
|
bool _Should_create_impl;
|
|
__std_win_error _Error;
|
|
};
|
|
|
|
_Create_status _Status;
|
|
|
|
_Creator(const path& _Path_arg, const directory_options _Options) : _Path(_Path_arg) {
|
|
const auto _Error = _Open_dir(_Path, _Options, _Dir, _Find_data);
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Status = {true, __std_win_error::_Success};
|
|
} else if (_Error == __std_win_error::_No_more_files) {
|
|
_Status = {false, __std_win_error::_Success};
|
|
} else {
|
|
_Status = {false, _Error};
|
|
}
|
|
}
|
|
};
|
|
|
|
void _Refresh(const __std_fs_find_data& _Data) {
|
|
_Entry._Refresh(_Data);
|
|
_Entry._Path.replace_filename(wstring_view{_Data._File_name});
|
|
}
|
|
|
|
explicit _Dir_enum_impl(_Creator&& _Create_data, const directory_options = {})
|
|
: _Dir(_STD move(_Create_data._Dir)) {
|
|
// directory_options provided, but unused to keep signature identical to recursive_directory_iterator
|
|
_Entry._Path = _STD move(_Create_data._Path);
|
|
_Refresh(_Create_data._Find_data);
|
|
}
|
|
|
|
template <class _Dir_enum_kind>
|
|
_NODISCARD static __std_win_error _Initialize_dir_enum(
|
|
shared_ptr<_Dir_enum_kind>& _Impl, const path& _Path, const directory_options _Options = {}) {
|
|
_Creator _Create_data(_Path, _Options);
|
|
if (_Create_data._Status._Should_create_impl) {
|
|
_Impl = _STD make_shared<_Dir_enum_kind>(_STD move(_Create_data), _Options);
|
|
}
|
|
return _Create_data._Status._Error;
|
|
}
|
|
|
|
directory_entry _Entry;
|
|
_Find_file_handle _Dir;
|
|
};
|
|
|
|
_EXPORT_STD class directory_iterator;
|
|
_EXPORT_STD class recursive_directory_iterator;
|
|
|
|
struct _Directory_entry_proxy {
|
|
_NODISCARD directory_entry operator*() && noexcept {
|
|
return _STD move(_Entry);
|
|
}
|
|
|
|
private:
|
|
friend directory_iterator;
|
|
friend recursive_directory_iterator;
|
|
|
|
explicit _Directory_entry_proxy(const directory_entry& _Entry_arg) : _Entry(_Entry_arg) {}
|
|
|
|
directory_entry _Entry;
|
|
};
|
|
|
|
_EXPORT_STD class directory_iterator {
|
|
public:
|
|
using iterator_category = input_iterator_tag;
|
|
using value_type = directory_entry;
|
|
using difference_type = ptrdiff_t;
|
|
using pointer = const directory_entry*;
|
|
using reference = const directory_entry&;
|
|
|
|
// [fs.dir.itr.members], member functions
|
|
directory_iterator() noexcept = default;
|
|
explicit directory_iterator(const path& _Path) {
|
|
const auto _Error = _Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("directory_iterator::directory_iterator", _Error, _Path);
|
|
}
|
|
}
|
|
|
|
directory_iterator(const path& _Path, const directory_options _Options) {
|
|
const auto _Error = _Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path, _Options);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("directory_iterator::directory_iterator", _Error, _Path);
|
|
}
|
|
}
|
|
|
|
directory_iterator(const path& _Path, error_code& _Ec) {
|
|
_Ec = _Make_ec(_Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path));
|
|
}
|
|
|
|
directory_iterator(const path& _Path, const directory_options _Options, error_code& _Ec) {
|
|
_Ec = _Make_ec(_Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path, _Options));
|
|
}
|
|
|
|
directory_iterator(const directory_iterator&) noexcept = default; // strengthened
|
|
directory_iterator(directory_iterator&&) noexcept = default;
|
|
~directory_iterator() noexcept = default;
|
|
|
|
directory_iterator& operator=(const directory_iterator&) noexcept = default; // strengthened
|
|
directory_iterator& operator=(directory_iterator&&) noexcept = default;
|
|
|
|
_NODISCARD const directory_entry& operator*() const noexcept /* strengthened */ {
|
|
return _Impl->_Entry;
|
|
}
|
|
|
|
_NODISCARD const directory_entry* operator->() const noexcept /* strengthened */ {
|
|
return &**this;
|
|
}
|
|
|
|
directory_iterator& operator++() {
|
|
const auto _Error = _Dir_enum_impl::_Advance_and_reset_if_no_more_files(_Impl);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("directory_iterator::operator++", _Error);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
directory_iterator& increment(error_code& _Ec) {
|
|
_Ec = _Make_ec(_Dir_enum_impl::_Advance_and_reset_if_no_more_files(_Impl));
|
|
return *this;
|
|
}
|
|
|
|
// other members as required by [input.iterators]:
|
|
_NODISCARD bool operator==(const directory_iterator& _Rhs) const noexcept /* strengthened */ {
|
|
return _Impl == _Rhs._Impl;
|
|
}
|
|
|
|
#if !_HAS_CXX20
|
|
_NODISCARD bool operator!=(const directory_iterator& _Rhs) const noexcept /* strengthened */ {
|
|
return _Impl != _Rhs._Impl;
|
|
}
|
|
#endif // !_HAS_CXX20
|
|
|
|
#if _HAS_CXX20
|
|
_NODISCARD bool operator==(default_sentinel_t) const noexcept {
|
|
return !_Impl;
|
|
}
|
|
#endif // _HAS_CXX20
|
|
|
|
_Directory_entry_proxy operator++(int) {
|
|
_Directory_entry_proxy _Proxy(**this);
|
|
++*this;
|
|
return _Proxy;
|
|
}
|
|
|
|
_NODISCARD bool _At_end() const noexcept {
|
|
return !_Impl;
|
|
}
|
|
|
|
private:
|
|
shared_ptr<_Dir_enum_impl> _Impl;
|
|
};
|
|
|
|
_EXPORT_STD _NODISCARD inline directory_iterator begin(directory_iterator _Iter) noexcept {
|
|
return _Iter;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline directory_iterator end(directory_iterator) noexcept {
|
|
return {};
|
|
}
|
|
|
|
struct _Should_recurse_result {
|
|
bool _Should_recurse;
|
|
__std_win_error _Error;
|
|
};
|
|
|
|
struct _Recursive_dir_enum_impl : _Dir_enum_impl {
|
|
vector<_Find_file_handle> _Stack;
|
|
directory_options _Options = {};
|
|
bool _Recursion_pending = true;
|
|
|
|
_NODISCARD _Should_recurse_result _Should_recurse() const noexcept {
|
|
bool _Should_recurse = false;
|
|
__std_win_error _Error = __std_win_error::_Success;
|
|
if (_Recursion_pending) {
|
|
if (_Entry._Is_symlink_or_junction()) {
|
|
if (_Bitmask_includes_any(_Options, directory_options::follow_directory_symlink)) {
|
|
// check for broken symlink/junction
|
|
__std_fs_stats _Target_stats;
|
|
constexpr auto _Flags =
|
|
__std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_Follow_symlinks;
|
|
_Error = __std_fs_get_stats(
|
|
_Entry._Path.c_str(), &_Target_stats, _Flags, _Entry._Cached_data._Attributes);
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Should_recurse =
|
|
_Bitmask_includes_any(_Target_stats._Attributes, __std_fs_file_attr::_Directory);
|
|
} else if (__std_is_file_not_found(_Error)
|
|
|| (_Error == __std_win_error::_Access_denied
|
|
&& _Bitmask_includes_any(_Options, directory_options::skip_permission_denied))) {
|
|
// skip broken symlinks and permission denied (when configured)
|
|
_Error = __std_win_error::_Success;
|
|
}
|
|
}
|
|
} else {
|
|
_Should_recurse = _Entry._Has_cached_attribute(__std_fs_file_attr::_Directory);
|
|
}
|
|
}
|
|
|
|
return {_Should_recurse, _Error};
|
|
}
|
|
|
|
_NODISCARD __std_win_error _Advance_and_skip_dots(__std_fs_find_data& _Data) noexcept {
|
|
const auto _Error = __std_fs_directory_iterator_advance(_Dir._Handle, &_Data);
|
|
if (_Error != __std_win_error::_Success) {
|
|
return _Error;
|
|
}
|
|
|
|
return _Skip_dots(_Dir._Handle, _Data);
|
|
}
|
|
|
|
_NODISCARD static __std_win_error _Pop_and_reset_if_no_more_files(shared_ptr<_Recursive_dir_enum_impl>& _Ptr) {
|
|
__std_win_error _Error;
|
|
auto& _Impl = *_Ptr;
|
|
__std_fs_find_data _Data;
|
|
|
|
_Impl._Recursion_pending = true;
|
|
|
|
do {
|
|
if (_Impl._Stack.empty()) {
|
|
_Error = __std_win_error::_Success;
|
|
break;
|
|
}
|
|
|
|
_Impl._Dir = _STD move(_Impl._Stack.back());
|
|
_Impl._Stack.pop_back();
|
|
_Impl._Entry._Path._Remove_filename_and_separator();
|
|
_Error = _Impl._Advance_and_skip_dots(_Data);
|
|
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Impl._Refresh(_Data);
|
|
return __std_win_error::_Success;
|
|
}
|
|
} while (_Error == __std_win_error::_No_more_files);
|
|
|
|
_Ptr.reset();
|
|
return _Error;
|
|
}
|
|
|
|
_NODISCARD static __std_win_error _Advance_and_reset_if_no_more_files(
|
|
shared_ptr<_Recursive_dir_enum_impl>& _Ptr) {
|
|
auto& _Impl = *_Ptr;
|
|
__std_fs_find_data _Data;
|
|
auto [_Should_recurse, _Error] = _Impl._Should_recurse();
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Ptr.reset();
|
|
return _Error;
|
|
}
|
|
|
|
if (_Should_recurse) {
|
|
_Impl._Stack.push_back(_STD move(_Impl._Dir));
|
|
_Error = _Open_dir(_Impl._Entry._Path, _Impl._Options, _Impl._Dir, _Data);
|
|
} else {
|
|
_Error = _Impl._Advance_and_skip_dots(_Data);
|
|
}
|
|
|
|
_Impl._Recursion_pending = true;
|
|
for (;; _Error = _Impl._Advance_and_skip_dots(_Data)) {
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Impl._Refresh(_Data);
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
if (_Error != __std_win_error::_No_more_files) {
|
|
break;
|
|
}
|
|
|
|
// no more files at this level, see if we can pop
|
|
if (_Impl._Stack.empty()) { // nothing to pop, clear the error, reset and return
|
|
_Error = __std_win_error::_Success;
|
|
break;
|
|
}
|
|
|
|
_Impl._Dir = _STD move(_Impl._Stack.back());
|
|
_Impl._Stack.pop_back();
|
|
_Impl._Entry._Path._Remove_filename_and_separator();
|
|
}
|
|
|
|
_Ptr.reset();
|
|
return _Error;
|
|
}
|
|
|
|
_Recursive_dir_enum_impl(_Dir_enum_impl::_Creator&& _Create_data, const directory_options _Options_arg)
|
|
: _Dir_enum_impl(_STD move(_Create_data)), _Options(_Options_arg) {}
|
|
};
|
|
|
|
_EXPORT_STD class recursive_directory_iterator {
|
|
public:
|
|
using iterator_category = input_iterator_tag;
|
|
using value_type = directory_entry;
|
|
using difference_type = ptrdiff_t;
|
|
using pointer = const directory_entry*;
|
|
using reference = const directory_entry&;
|
|
|
|
// [fs.rec.dir.itr.members], constructors and destructor
|
|
|
|
recursive_directory_iterator() noexcept = default;
|
|
explicit recursive_directory_iterator(const path& _Path) {
|
|
const auto _Error = _Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("recursive_directory_iterator::recursive_directory_iterator", _Error, _Path);
|
|
}
|
|
}
|
|
|
|
recursive_directory_iterator(const path& _Path, const directory_options _Options) {
|
|
const auto _Error = _Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path, _Options);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("recursive_directory_iterator::recursive_directory_iterator", _Error, _Path);
|
|
}
|
|
}
|
|
|
|
recursive_directory_iterator(const path& _Path, const directory_options _Options, error_code& _Ec) {
|
|
_Ec = _Make_ec(_Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path, _Options));
|
|
}
|
|
|
|
recursive_directory_iterator(const path& _Path, error_code& _Ec) {
|
|
_Ec = _Make_ec(_Dir_enum_impl::_Initialize_dir_enum(_Impl, _Path));
|
|
}
|
|
|
|
recursive_directory_iterator(const recursive_directory_iterator&) noexcept = default; // strengthened
|
|
recursive_directory_iterator(recursive_directory_iterator&&) noexcept = default;
|
|
~recursive_directory_iterator() noexcept = default;
|
|
|
|
// [fs.rec.dir.itr.members], observers
|
|
_NODISCARD directory_options options() const noexcept /* strengthened */ {
|
|
return _Impl->_Options;
|
|
}
|
|
_NODISCARD int depth() const noexcept /* strengthened */ {
|
|
// NT uses uint32_t to store the length of the path
|
|
// that allows us 2^31 wchar_t per path.
|
|
// A directory name should be at least 1 character, otherwise
|
|
// adjacent directory separators will be treated as one.
|
|
// Hence, we can only get to 2^30 entries in the stack.
|
|
return static_cast<int>(_Impl->_Stack.size());
|
|
}
|
|
_NODISCARD bool recursion_pending() const noexcept /* strengthened */ {
|
|
return _Impl->_Recursion_pending;
|
|
}
|
|
|
|
_NODISCARD const directory_entry& operator*() const noexcept /* strengthened */ {
|
|
return _Impl->_Entry;
|
|
}
|
|
|
|
_NODISCARD const directory_entry* operator->() const noexcept /* strengthened */ {
|
|
return &**this;
|
|
}
|
|
|
|
// [fs.rec.dir.itr.members], modifiers
|
|
recursive_directory_iterator& operator=(recursive_directory_iterator&&) noexcept = default;
|
|
recursive_directory_iterator& operator=(const recursive_directory_iterator&) noexcept = default; // strengthened
|
|
|
|
recursive_directory_iterator& operator++() {
|
|
const auto _Error = _Recursive_dir_enum_impl::_Advance_and_reset_if_no_more_files(_Impl);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("recursive_directory_iterator::operator++", _Error);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
recursive_directory_iterator& increment(error_code& _Ec) {
|
|
_Ec = _Make_ec(_Recursive_dir_enum_impl::_Advance_and_reset_if_no_more_files(_Impl));
|
|
return *this;
|
|
}
|
|
|
|
void pop() {
|
|
const auto _Error = _Recursive_dir_enum_impl::_Pop_and_reset_if_no_more_files(_Impl);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("recursive_directory_iterator::pop", _Error);
|
|
}
|
|
}
|
|
|
|
void pop(error_code& _Ec) {
|
|
_Ec = _Make_ec(_Recursive_dir_enum_impl::_Pop_and_reset_if_no_more_files(_Impl));
|
|
}
|
|
|
|
void disable_recursion_pending() noexcept {
|
|
_Impl->_Recursion_pending = false;
|
|
}
|
|
|
|
// other members as required by [input.iterators]:
|
|
_NODISCARD bool operator==(const recursive_directory_iterator& _Rhs) const noexcept {
|
|
return _Impl == _Rhs._Impl;
|
|
}
|
|
|
|
#if !_HAS_CXX20
|
|
_NODISCARD bool operator!=(const recursive_directory_iterator& _Rhs) const noexcept {
|
|
return _Impl != _Rhs._Impl;
|
|
}
|
|
#endif // !_HAS_CXX20
|
|
|
|
#if _HAS_CXX20
|
|
_NODISCARD bool operator==(default_sentinel_t) const noexcept {
|
|
return !_Impl;
|
|
}
|
|
#endif // _HAS_CXX20
|
|
|
|
_Directory_entry_proxy operator++(int) {
|
|
_Directory_entry_proxy _Proxy(**this);
|
|
++*this;
|
|
return _Proxy;
|
|
}
|
|
|
|
private:
|
|
shared_ptr<_Recursive_dir_enum_impl> _Impl;
|
|
};
|
|
|
|
_EXPORT_STD _NODISCARD inline recursive_directory_iterator begin(recursive_directory_iterator _Iter) noexcept {
|
|
return _Iter;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline recursive_directory_iterator end(recursive_directory_iterator) noexcept {
|
|
return {};
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path absolute(const path& _Input, error_code& _Ec) {
|
|
// normalize path according to system semantics, without touching the disk
|
|
// calls GetFullPathNameW
|
|
_Ec.clear(); // for exception safety
|
|
path _Result;
|
|
if (!_Input._Text.empty()) {
|
|
_Result._Text.resize(__std_fs_max_path);
|
|
for (;;) {
|
|
const auto _Requested_size = static_cast<unsigned long>(_Result._Text.size());
|
|
const auto _Full_path_result =
|
|
__std_fs_get_full_path_name(_Input._Text.c_str(), _Requested_size, _Result._Text.data());
|
|
_Result._Text.resize(_Full_path_result._Size);
|
|
if (_Full_path_result._Size < _Requested_size) {
|
|
_Ec = _Make_ec(_Full_path_result._Error);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path absolute(const path& _Input) {
|
|
// normalize path according to system semantics, without touching the disk
|
|
// calls GetFullPathNameW
|
|
error_code _Ec;
|
|
path _Result(_STD filesystem::absolute(_Input, _Ec));
|
|
if (_Ec) {
|
|
_Throw_fs_error("absolute", _Ec, _Input);
|
|
}
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Canonical(path& _Result, const wstring& _Text) { // pre: _Result.empty()
|
|
if (_Text.empty()) {
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
auto _Name_kind = __std_fs_volume_name_kind::_Dos;
|
|
{
|
|
__std_win_error _Err;
|
|
const _Fs_file _Handle(_Text.c_str(), __std_access_rights::_File_read_attributes,
|
|
__std_fs_file_flags::_Backup_semantics, &_Err);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Result._Text.resize(__std_fs_max_path);
|
|
for (;;) {
|
|
const auto _Requested_size = static_cast<unsigned long>(_Result._Text.size());
|
|
const auto _Final_path_result = __std_fs_get_final_path_name_by_handle(
|
|
_Handle._Raw, _Result._Text.data(), _Requested_size, _Name_kind);
|
|
_Err = _Final_path_result._Error;
|
|
if (_Final_path_result._Size == 0) {
|
|
if (_Err == __std_win_error::_Path_not_found && _Name_kind == __std_fs_volume_name_kind::_Dos) {
|
|
// maybe there is no DOS name for the supplied path, retry with NT path
|
|
_Name_kind = __std_fs_volume_name_kind::_Nt;
|
|
continue;
|
|
}
|
|
|
|
_Result._Text.clear();
|
|
return _Err;
|
|
}
|
|
|
|
_Result._Text.resize(_Final_path_result._Size);
|
|
if (_Final_path_result._Size < _Requested_size) {
|
|
break;
|
|
}
|
|
}
|
|
} // close _Handle
|
|
|
|
if (_Name_kind == __std_fs_volume_name_kind::_Dos) {
|
|
if (_Is_drive_prefix_with_slash_slash_question(_Result._Text)) {
|
|
// the result contains a \\?\ prefix but is just a drive letter, strip the \\?\ prefix
|
|
_Result._Text.erase(0, 4);
|
|
} else if (_Result._Text._Starts_with(LR"(\\?\UNC\)"sv)) {
|
|
// the result contains a \\?\UNC\ prefix, replace with the simpler \\ prefix
|
|
_Result._Text.erase(2, 6); // chop out ?\UNC\ leaving two preferred-separators
|
|
}
|
|
} else { // result is in the NT namespace, so apply the DOS to NT namespace prefix
|
|
_Result._Text.insert(0, LR"(\\?\GLOBALROOT)"sv);
|
|
}
|
|
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path canonical(const path& _Input) {
|
|
// resolve the final path according to system semantics, by opening the file
|
|
// calls GetFinalPathNameByHandleW
|
|
path _Result;
|
|
const auto _Err = _Canonical(_Result, _Input.native());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("canonical", _Err, _Input);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path canonical(const path& _Input, error_code& _Ec) {
|
|
// resolve the final path according to system semantics, by opening the file
|
|
// calls GetFinalPathNameByHandleW
|
|
_Ec.clear(); // for exception safety
|
|
path _Result;
|
|
_Ec = _Make_ec(_Canonical(_Result, _Input.native()));
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD inline unique_ptr<wchar_t[]> _Get_cleaned_symlink_target(const path& _To) noexcept {
|
|
// transforms /s in the root-name to \s, and all other directory-separators into single \s
|
|
// example: a/\/b -> a\b
|
|
// example: //server/a////////b////////c////////d -> \\server\a\b\c\d
|
|
const auto& _To_str = _To.native();
|
|
// protected from overflow by wstring's max_size cap:
|
|
unique_ptr<wchar_t[]> _Cleaned_link(::new (nothrow) wchar_t[_To_str.size() + 1]);
|
|
if (!_Cleaned_link) {
|
|
return _Cleaned_link;
|
|
}
|
|
|
|
const auto _First = _To_str.c_str();
|
|
const auto _Last = _First + _To_str.size();
|
|
auto _Next = _Find_root_name_end(_First, _Last);
|
|
auto _Dest = _STD replace_copy_if(_First, _Next, _Cleaned_link.get(), _Is_slash, L'\\');
|
|
for (;;) {
|
|
const wchar_t _Ch = *_Next;
|
|
if (_Is_slash(_Ch)) {
|
|
*_Dest = L'\\';
|
|
do {
|
|
++_Next;
|
|
} while (_Is_slash(*_Next));
|
|
} else {
|
|
*_Dest = _Ch;
|
|
if (_Ch == L'\0') {
|
|
break;
|
|
}
|
|
|
|
++_Next;
|
|
}
|
|
|
|
++_Dest;
|
|
}
|
|
|
|
return _Cleaned_link;
|
|
}
|
|
|
|
_EXPORT_STD inline void create_directory_symlink(const path& _To, const path& _New_symlink) {
|
|
// create a symlink for a directory, _New_symlink -> _To
|
|
const auto _Cleaned = _Get_cleaned_symlink_target(_To);
|
|
if (!_Cleaned) {
|
|
_Xbad_alloc();
|
|
}
|
|
|
|
// note reversed parameter order:
|
|
const auto _Err = __std_fs_create_directory_symbolic_link(_New_symlink.c_str(), _Cleaned.get());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("create_directory_symlink", _Err, _To, _New_symlink);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void create_directory_symlink(
|
|
const path& _To, const path& _New_symlink, error_code& _Ec) noexcept {
|
|
// create a symlink for a directory, _New_symlink -> _To
|
|
const auto _Cleaned = _Get_cleaned_symlink_target(_To);
|
|
if (_Cleaned) {
|
|
// note reversed parameter order:
|
|
_Ec = _Make_ec(__std_fs_create_directory_symbolic_link(_New_symlink.c_str(), _Cleaned.get()));
|
|
} else {
|
|
_Ec = _STD make_error_code(errc::not_enough_memory);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void create_hard_link(const path& _To, const path& _New_hard_link) {
|
|
// create a hard link for a file, _New_hard_link -> _To
|
|
// note reversed parameter order:
|
|
const auto _Err = __std_fs_create_hard_link(_New_hard_link.c_str(), _To.c_str());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("create_hard_link", _Err, _To, _New_hard_link);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void create_hard_link(const path& _To, const path& _New_hard_link, error_code& _Ec) noexcept {
|
|
// create a hard link for a file, _New_hard_link -> _To
|
|
// note reversed parameter order:
|
|
_Ec = _Make_ec(__std_fs_create_hard_link(_New_hard_link.c_str(), _To.c_str()));
|
|
}
|
|
|
|
_EXPORT_STD inline void create_symlink(const path& _To, const path& _New_symlink) {
|
|
// create a symlink for a file, _New_symlink -> _To
|
|
const auto _Cleaned = _Get_cleaned_symlink_target(_To);
|
|
if (!_Cleaned) {
|
|
_Xbad_alloc();
|
|
}
|
|
|
|
// note reversed parameter order:
|
|
const auto _Err = __std_fs_create_symbolic_link(_New_symlink.c_str(), _Cleaned.get());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("create_symlink", _Err, _To, _New_symlink);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void create_symlink(const path& _To, const path& _New_symlink, error_code& _Ec) noexcept {
|
|
// create a symlink for a file, _New_symlink -> _To
|
|
const auto _Cleaned = _Get_cleaned_symlink_target(_To);
|
|
if (_Cleaned) {
|
|
// note reversed parameter order:
|
|
_Ec = _Make_ec(__std_fs_create_symbolic_link(_New_symlink.c_str(), _Cleaned.get()));
|
|
} else {
|
|
_Ec = _STD make_error_code(errc::not_enough_memory);
|
|
}
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Read_reparse_data(
|
|
const _Fs_file& _Handle, unique_ptr<char[]>& _Buffer_unique_ptr) noexcept {
|
|
constexpr auto _Buffer_size = 16 * 1024 + sizeof(wchar_t); // MAXIMUM_REPARSE_DATA_BUFFER_SIZE + sizeof(wchar_t)
|
|
|
|
_Buffer_unique_ptr.reset(::new (nothrow) char[_Buffer_size]);
|
|
if (!_Buffer_unique_ptr) {
|
|
return __std_win_error::_Not_enough_memory;
|
|
}
|
|
|
|
const auto _Buffer = reinterpret_cast<__std_fs_reparse_data_buffer*>(_Buffer_unique_ptr.get());
|
|
const __std_win_error _Err = __std_fs_read_reparse_data_buffer(_Handle._Raw, _Buffer, _Buffer_size);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Read_symlink(const path& _Symlink_path, path& _Result) {
|
|
__std_win_error _Err;
|
|
unique_ptr<char[]> _Buffer_unique_ptr;
|
|
{
|
|
const _Fs_file _Handle(_Symlink_path.c_str(), __std_access_rights::_File_read_attributes,
|
|
__std_fs_file_flags::_Backup_semantics | __std_fs_file_flags::_Open_reparse_point, &_Err);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Err = _Read_reparse_data(_Handle, _Buffer_unique_ptr);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
} // Close _Handle
|
|
|
|
const auto _Buffer = reinterpret_cast<__std_fs_reparse_data_buffer*>(_Buffer_unique_ptr.get());
|
|
unsigned short _Length;
|
|
wchar_t* _Offset;
|
|
_Err = __std_fs_read_name_from_reparse_data_buffer(_Buffer, &_Offset, &_Length);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Result._Text.assign(_Offset, _Length);
|
|
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path read_symlink(const path& _Symlink_path, error_code& _Ec) {
|
|
_Ec.clear();
|
|
path _Result;
|
|
_Ec = _Make_ec(_Read_symlink(_Symlink_path, _Result));
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path read_symlink(const path& _Symlink_path) {
|
|
path _Result;
|
|
const auto _Err = _Read_symlink(_Symlink_path, _Result);
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("read_symlink", _Err, _Symlink_path);
|
|
}
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Copy_symlink(const path& _Symlink, const path& _New_symlink) noexcept {
|
|
__std_win_error _Err;
|
|
unique_ptr<char[]> _Buffer_unique_ptr;
|
|
bool _Is_directory;
|
|
{
|
|
const _Fs_file _Handle(_Symlink.c_str(), __std_access_rights::_File_read_attributes,
|
|
__std_fs_file_flags::_Backup_semantics | __std_fs_file_flags::_Open_reparse_point, &_Err);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Err = _Read_reparse_data(_Handle, _Buffer_unique_ptr);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
unsigned long _File_attributes;
|
|
_Err = __std_fs_get_file_attributes_by_handle(_Handle._Raw, &_File_attributes);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Is_directory = (_File_attributes & static_cast<unsigned long>(__std_fs_file_attr::_Directory)) != 0;
|
|
} // Close _Handle
|
|
|
|
const auto _Buffer = reinterpret_cast<__std_fs_reparse_data_buffer*>(_Buffer_unique_ptr.get());
|
|
|
|
// LWG-3744: `copy_symlink(junction, new_symlink)`'s behavior is unclear
|
|
// `read_symlink(junction)` should be allowed, but `copy_symlink(junction)` is not.
|
|
if (__std_fs_is_junction_from_reparse_data_buffer(_Buffer)) {
|
|
_Err = __std_win_error::_Reparse_tag_invalid;
|
|
return _Err;
|
|
}
|
|
|
|
unsigned short _Length;
|
|
wchar_t* _Offset;
|
|
_Err = __std_fs_read_name_from_reparse_data_buffer(_Buffer, &_Offset, &_Length);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Offset[_Length] = L'\0';
|
|
|
|
if (_Is_directory) {
|
|
_Err = __std_fs_create_directory_symbolic_link(_New_symlink.c_str(), _Offset);
|
|
} else {
|
|
_Err = __std_fs_create_symbolic_link(_New_symlink.c_str(), _Offset);
|
|
}
|
|
|
|
return _Err;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Copy_junction(const path& _Junction, const path& _New_junction) noexcept {
|
|
__std_win_error _Err;
|
|
unique_ptr<char[]> _Buffer_unique_ptr;
|
|
{
|
|
const _Fs_file _Handle(_Junction.c_str(), __std_access_rights::_File_read_attributes,
|
|
__std_fs_file_flags::_Backup_semantics | __std_fs_file_flags::_Open_reparse_point, &_Err);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
|
|
_Err = _Read_reparse_data(_Handle, _Buffer_unique_ptr);
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
} // Close _Handle
|
|
|
|
const auto _Buffer = reinterpret_cast<__std_fs_reparse_data_buffer*>(_Buffer_unique_ptr.get());
|
|
|
|
const auto _Create_dir_res = __std_fs_create_directory(_New_junction.c_str());
|
|
|
|
if (_Create_dir_res._Error != __std_win_error::_Success) {
|
|
return _Create_dir_res._Error;
|
|
} else if (!_Create_dir_res._Created) {
|
|
return __std_win_error::_Already_exists;
|
|
}
|
|
|
|
struct _NODISCARD _Delete_directory_scope_guard {
|
|
const wchar_t* _Path;
|
|
~_Delete_directory_scope_guard() {
|
|
if (_Path) {
|
|
(void) __std_fs_remove(_Path);
|
|
}
|
|
}
|
|
};
|
|
_Delete_directory_scope_guard _Delete_directory{_New_junction.c_str()};
|
|
|
|
_Fs_file _To_handle{_New_junction.c_str(), __std_access_rights::_File_write_attributes,
|
|
__std_fs_file_flags::_Backup_semantics, &_Err};
|
|
if (_Err != __std_win_error::_Success) {
|
|
return _Err;
|
|
}
|
|
_Err = __std_fs_write_reparse_data_buffer(_To_handle._Raw, _Buffer);
|
|
if (_Err == __std_win_error::_Success) {
|
|
// don't delete the directory if we succeeded in making it a junction
|
|
_Delete_directory._Path = nullptr;
|
|
}
|
|
|
|
return _Err;
|
|
}
|
|
|
|
_EXPORT_STD inline void copy_symlink(const path& _Symlink, const path& _New_symlink, error_code& _Ec) {
|
|
_Ec = _Make_ec(_Copy_symlink(_Symlink, _New_symlink));
|
|
}
|
|
|
|
_EXPORT_STD inline void copy_symlink(const path& _Symlink, const path& _New_symlink) {
|
|
const auto _Err = _Copy_symlink(_Symlink, _New_symlink);
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("copy_symlink", _Err, _Symlink, _New_symlink);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline bool copy_file(
|
|
const path& _From, const path& _To, const copy_options _Options, error_code& _Ec) noexcept
|
|
/* strengthened */ {
|
|
// copy a file _From -> _To according to _Options
|
|
const auto _Result =
|
|
__std_fs_copy_file(_From.c_str(), _To.c_str(), static_cast<__std_fs_copy_options>(_Options));
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Copied;
|
|
}
|
|
|
|
_EXPORT_STD inline bool copy_file(const path& _From, const path& _To, const copy_options _Options) {
|
|
// copy a file _From -> _To according to _Options
|
|
const auto _Result =
|
|
__std_fs_copy_file(_From.c_str(), _To.c_str(), static_cast<__std_fs_copy_options>(_Options));
|
|
if (_Result._Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("copy_file", _Result._Error, _From, _To);
|
|
}
|
|
|
|
return _Result._Copied;
|
|
}
|
|
|
|
_EXPORT_STD inline bool copy_file(const path& _From, const path& _To, error_code& _Ec) noexcept /* strengthened */ {
|
|
// copy a file _From -> _To, failing if the destination exists
|
|
return _STD filesystem::copy_file(_From, _To, copy_options::none, _Ec);
|
|
}
|
|
|
|
_EXPORT_STD inline bool copy_file(const path& _From, const path& _To) {
|
|
// copy a file _From -> _To, failing if the destination exists
|
|
return _STD filesystem::copy_file(_From, _To, copy_options::none);
|
|
}
|
|
|
|
_NODISCARD inline pair<__std_win_error, bool> _Equivalent(
|
|
const wchar_t* const _Lhs, const wchar_t* const _Rhs) noexcept {
|
|
__std_fs_file_id _Left_id;
|
|
__std_fs_file_id _Right_id;
|
|
auto _Last_error = __std_fs_get_file_id(&_Left_id, _Lhs);
|
|
if (_Last_error != __std_win_error::_Success) {
|
|
return {_Last_error, false};
|
|
}
|
|
|
|
_Last_error = __std_fs_get_file_id(&_Right_id, _Rhs);
|
|
if (_Last_error != __std_win_error::_Success) {
|
|
return {_Last_error, false};
|
|
}
|
|
|
|
return {__std_win_error::_Success, _CSTD memcmp(&_Left_id, &_Right_id, sizeof(__std_fs_file_id)) == 0};
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool equivalent(const path& _Lhs, const path& _Rhs) {
|
|
// test if the paths _Lhs and _Rhs refer to the same file
|
|
const auto _Result = _Equivalent(_Lhs.c_str(), _Rhs.c_str());
|
|
if (_Result.first != __std_win_error::_Success) {
|
|
_Throw_fs_error("equivalent", _Result.first, _Lhs, _Rhs);
|
|
}
|
|
|
|
return _Result.second;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool equivalent(const path& _Lhs, const path& _Rhs, error_code& _Ec) noexcept {
|
|
// test if the paths _Lhs and _Rhs refer to the same file
|
|
const auto _Result = _Equivalent(_Lhs.c_str(), _Rhs.c_str());
|
|
_Ec = _Make_ec(_Result.first);
|
|
return _Result.second;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_status status(const path& _Path);
|
|
_EXPORT_STD _NODISCARD inline file_status status(const path& _Path, error_code& _Ec) noexcept;
|
|
_EXPORT_STD _NODISCARD inline file_status symlink_status(const path& _Path);
|
|
_EXPORT_STD _NODISCARD inline file_status symlink_status(const path& _Path, error_code& _Ec) noexcept;
|
|
|
|
_EXPORT_STD _NODISCARD inline bool exists(const path& _Target, error_code& _Ec) noexcept {
|
|
const auto _Type = _STD filesystem::status(_Target, _Ec).type();
|
|
if (_Type != file_type::none) {
|
|
_Ec.clear();
|
|
return _Type != file_type::not_found;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool exists(const path& _Target) {
|
|
error_code _Ec;
|
|
const bool _Result = _STD filesystem::exists(_Target, _Ec);
|
|
if (_Ec) {
|
|
_Throw_fs_error("exists", _Ec, _Target);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _File_size(const path& _Path, uintmax_t& _Result) noexcept {
|
|
__std_fs_stats _Stats;
|
|
const auto _Error = __std_fs_get_stats(
|
|
_Path.c_str(), &_Stats, __std_fs_stats_flags::_Follow_symlinks | __std_fs_stats_flags::_File_size);
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Result = _Stats._File_size;
|
|
} else {
|
|
_Result = static_cast<uintmax_t>(-1);
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline uintmax_t file_size(const path& _Path) {
|
|
uintmax_t _Result;
|
|
const auto _Error = _File_size(_Path, _Result);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("file_size", _Error, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline uintmax_t file_size(const path& _Path, error_code& _Ec) noexcept {
|
|
uintmax_t _Result;
|
|
_Ec = _Make_ec(_File_size(_Path, _Result));
|
|
return _Result;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Hard_link_count(const path& _Path, uintmax_t& _Result) noexcept {
|
|
__std_fs_stats _Stats;
|
|
const auto _Error = __std_fs_get_stats(
|
|
_Path.c_str(), &_Stats, __std_fs_stats_flags::_Follow_symlinks | __std_fs_stats_flags::_Link_count);
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Result = _Stats._Link_count;
|
|
} else {
|
|
_Result = static_cast<uintmax_t>(-1);
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline uintmax_t hard_link_count(const path& _Target) {
|
|
// get the number of hard links to _Target
|
|
uintmax_t _Result;
|
|
const auto _Err = _Hard_link_count(_Target.c_str(), _Result);
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("hard_link_count", _Err, _Target);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline uintmax_t hard_link_count(const path& _Target, error_code& _Ec) noexcept {
|
|
// get the number of hard links to _Target
|
|
uintmax_t _Result;
|
|
_Ec = _Make_ec(_Hard_link_count(_Target.c_str(), _Result));
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_block_file(const path&) noexcept /* strengthened */ {
|
|
// tests whether the input path is a block special file (never on Windows)
|
|
return false;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_block_file(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether the input path is a block special file (never on Windows)
|
|
(void) _STD filesystem::status(_Path, _Ec);
|
|
return false; // note status sets _Ec to an error on nonexistent input
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_character_file(const path&) noexcept /* strengthened */ {
|
|
// tests whether the input path is a character special file (never on Windows)
|
|
return false;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_character_file(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether the input path is a character special file (never on Windows)
|
|
(void) _STD filesystem::status(_Path, _Ec);
|
|
return false; // note status sets _Ec to an error on nonexistent input
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_directory(const path& _Path) {
|
|
// tests whether _Path is a directory
|
|
return _STD filesystem::is_directory(_STD filesystem::status(_Path));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_directory(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether _Path is a directory
|
|
return _STD filesystem::is_directory(_STD filesystem::status(_Path, _Ec));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_empty(const path& _Path, error_code& _Ec) {
|
|
// test whether _Path refers to a zero sized file or empty directory
|
|
constexpr auto _Flags = __std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_File_size
|
|
| __std_fs_stats_flags::_Follow_symlinks;
|
|
__std_fs_stats _Stats;
|
|
const auto _Error = __std_fs_get_stats(_Path.c_str(), &_Stats, _Flags);
|
|
_Ec = _Make_ec(_Error);
|
|
if (_Error != __std_win_error::_Success) {
|
|
return false;
|
|
}
|
|
|
|
if ((_Stats._Attributes & __std_fs_file_attr::_Directory) == __std_fs_file_attr{}) {
|
|
return _Stats._File_size == 0;
|
|
} else {
|
|
directory_iterator _Iter(_Path, _Ec);
|
|
return !_Ec && _Iter._At_end();
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_empty(const path& _Path) {
|
|
// test whether _Path refers to a zero sized file or empty directory
|
|
error_code _Ec;
|
|
const bool _Result = is_empty(_Path, _Ec);
|
|
if (_Ec) {
|
|
_Throw_fs_error("is_empty", _Ec, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_fifo(const path&) noexcept /* strengthened */ {
|
|
// tests whether the input path is a fifo (never on Windows)
|
|
return false;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_fifo(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether the input path is a fifo (never on Windows)
|
|
(void) _STD filesystem::status(_Path, _Ec);
|
|
return false; // note status sets _Ec to an error on nonexistent input
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_other(const path& _Path) {
|
|
// tests whether _Path is an "other" file (such as a junction)
|
|
return _STD filesystem::is_other(_STD filesystem::status(_Path));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_other(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether _Path is an "other" file (such as a junction)
|
|
return _STD filesystem::is_other(_STD filesystem::status(_Path, _Ec));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_regular_file(const path& _Path) {
|
|
// tests whether _Path is a regular file
|
|
return _STD filesystem::is_regular_file(_STD filesystem::status(_Path));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_regular_file(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether _Path is a regular file
|
|
return _STD filesystem::is_regular_file(_STD filesystem::status(_Path, _Ec));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_socket(const path&) noexcept /* strengthened */ {
|
|
// tests whether the input path is a socket (never on Windows)
|
|
return false;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_socket(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether the input path is a socket (never on Windows)
|
|
(void) _STD filesystem::status(_Path, _Ec);
|
|
return false; // note status sets _Ec to an error on nonexistent input
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_symlink(const path& _Path) {
|
|
// tests whether _Path is a symlink
|
|
return _STD filesystem::is_symlink(_STD filesystem::symlink_status(_Path));
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool is_symlink(const path& _Path, error_code& _Ec) noexcept {
|
|
// tests whether _Path is a symlink
|
|
return _STD filesystem::is_symlink(_STD filesystem::symlink_status(_Path, _Ec));
|
|
}
|
|
|
|
_EXPORT_STD inline bool remove(const path& _Target) {
|
|
// remove file _Target (even if it is a directory); returns whether the file was removed
|
|
// note !exists(_Target) is not an error, and merely returns false
|
|
const auto _Result = __std_fs_remove(_Target.c_str());
|
|
if (_Result._Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("remove", _Result._Error, _Target);
|
|
}
|
|
|
|
return _Result._Removed;
|
|
}
|
|
|
|
_EXPORT_STD inline bool remove(const path& _Target, error_code& _Ec) noexcept {
|
|
// remove file _Target (even if it is a directory); returns whether the file was removed
|
|
// note !exists(_Target) is not an error, and merely returns false
|
|
const auto _Result = __std_fs_remove(_Target.c_str());
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Removed;
|
|
}
|
|
|
|
_EXPORT_STD inline void rename(const path& _Old_p, const path& _New_p) {
|
|
// rename _Old_p to _New_p, overwriting _New_p if it is an existing non-directory file
|
|
// the standard explicitly allows an implementation to not replace _New_p if it is a directory,
|
|
// and we take advantage of that here to be able to use MoveFileEx(... MOVEFILE_REPLACE_EXISTING)
|
|
const auto _Err = __std_fs_rename(_Old_p.c_str(), _New_p.c_str());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("rename", _Err, _Old_p, _New_p);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void rename(const path& _Old_p, const path& _New_p, error_code& _Ec) noexcept {
|
|
// rename _Old_p to _New_p, overwriting _New_p if it is an existing non-directory file
|
|
// the standard explicitly allows an implementation to not replace _New_p if it is a directory,
|
|
// and we take advantage of that here to be able to use MoveFileEx(... MOVEFILE_REPLACE_EXISTING)
|
|
_Ec = _Make_ec(__std_fs_rename(_Old_p.c_str(), _New_p.c_str()));
|
|
}
|
|
|
|
_EXPORT_STD inline void resize_file(const path& _Target, const uintmax_t _New_size) {
|
|
// set the size of _Target to _New_size
|
|
const auto _Err = __std_fs_resize_file(_Target.c_str(), _New_size);
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("resize_file", _Err, _Target);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void resize_file(const path& _Target, const uintmax_t _New_size, error_code& _Ec) noexcept {
|
|
// set the size of _Target to _New_size
|
|
_Ec = _Make_ec(__std_fs_resize_file(_Target.c_str(), _New_size));
|
|
}
|
|
|
|
_EXPORT_STD struct space_info {
|
|
uintmax_t capacity;
|
|
uintmax_t free;
|
|
uintmax_t available;
|
|
|
|
#if _HAS_CXX20
|
|
_NODISCARD friend constexpr bool operator==(const space_info&, const space_info&) noexcept = default;
|
|
#endif // _HAS_CXX20
|
|
};
|
|
|
|
_EXPORT_STD _NODISCARD inline space_info space(const path& _Target) {
|
|
// get capacity information for the volume on which the file _Target resides
|
|
space_info _Result;
|
|
const auto _Last_error = __std_fs_space(_Target.c_str(), &_Result.available, &_Result.capacity, &_Result.free);
|
|
if (_Last_error != __std_win_error::_Success) {
|
|
_Throw_fs_error("space", _Last_error, _Target);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline space_info space(const path& _Target, error_code& _Ec) noexcept {
|
|
// get capacity information for the volume on which the file _Target resides
|
|
space_info _Result;
|
|
_Ec = _Make_ec(__std_fs_space(_Target.c_str(), &_Result.available, &_Result.capacity, &_Result.free));
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline bool status_known(const file_status _Status) noexcept {
|
|
return _Status.type() != file_type::none;
|
|
}
|
|
|
|
_NODISCARD inline _File_status_and_error _Get_any_status(
|
|
const path& _Path, const __std_fs_stats_flags _Flags) noexcept {
|
|
_File_status_and_error _Result;
|
|
__std_fs_stats _Stats;
|
|
|
|
const auto _Error = __std_fs_get_stats(_Path.c_str(), &_Stats, _Flags);
|
|
_Result._Error = _Error;
|
|
_Result._Status._Refresh(_Error, _Stats);
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_status status(const path& _Path, error_code& _Ec) noexcept {
|
|
const auto _Result = _Get_any_status(_Path, _Status_stats_flags);
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Status;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_status status(const path& _Path) {
|
|
const auto _Result = _Get_any_status(_Path, _Status_stats_flags);
|
|
if (_Result._Not_good()) {
|
|
_Throw_fs_error("status", _Result._Error, _Path);
|
|
}
|
|
return _Result._Status;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_status symlink_status(const path& _Path, error_code& _Ec) noexcept {
|
|
const auto _Result = _Get_any_status(_Path, _Symlink_status_stats_flags);
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Status;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_status symlink_status(const path& _Path) {
|
|
const auto _Result = _Get_any_status(_Path, _Symlink_status_stats_flags);
|
|
if (_Result._Not_good()) {
|
|
_Throw_fs_error("symlink_status", _Result._Error, _Path);
|
|
}
|
|
|
|
return _Result._Status;
|
|
}
|
|
|
|
_EXPORT_STD inline bool create_directory(const path& _Path) {
|
|
const auto _Result = __std_fs_create_directory(_Path.c_str());
|
|
if (_Result._Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("create_directory", _Result._Error, _Path);
|
|
}
|
|
|
|
return _Result._Created;
|
|
}
|
|
|
|
_EXPORT_STD inline bool create_directory(const path& _Path, error_code& _Ec) noexcept {
|
|
const auto _Result = __std_fs_create_directory(_Path.c_str());
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Created;
|
|
}
|
|
|
|
_EXPORT_STD inline bool create_directory(const path& _Path, const path& _Existing_p) {
|
|
const auto _Result = __std_fs_create_directory(_Path.c_str());
|
|
if (_Result._Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("create_directory", _Result._Error, _Path, _Existing_p);
|
|
}
|
|
|
|
return _Result._Created;
|
|
}
|
|
|
|
_EXPORT_STD inline bool create_directory(const path& _Path, const path&, error_code& _Ec) noexcept {
|
|
const auto _Result = __std_fs_create_directory(_Path.c_str());
|
|
_Ec = _Make_ec(_Result._Error);
|
|
return _Result._Created;
|
|
}
|
|
|
|
_EXPORT_STD inline bool create_directories(const path& _Path, error_code& _Ec) {
|
|
if (_Path.empty()) {
|
|
_Ec = _Make_ec(__std_win_error::_Path_not_found);
|
|
return false;
|
|
}
|
|
|
|
_Ec.clear(); // for exception safety
|
|
const wstring& _Text = _Path.native();
|
|
wstring _Tmp;
|
|
_Tmp.reserve(_Text.size());
|
|
auto _Cursor = _Text.data();
|
|
const auto _End = _Text.data() + _Text.size();
|
|
auto _Root_path_end = _Find_relative_path(_Cursor, _End);
|
|
if (_Root_path_end != _Cursor && _End - _Root_path_end >= 3 && _Is_drive_prefix(_Root_path_end)
|
|
&& _Is_slash(_Root_path_end[2])) {
|
|
// \\?\ prefixes may have a drive letter suffix Windows will reject, strip
|
|
_Root_path_end += 2;
|
|
}
|
|
|
|
_Tmp.append(_Cursor, _Root_path_end);
|
|
_Cursor = _Root_path_end;
|
|
|
|
// When creating directories, sometimes we get error reports on earlier directories.
|
|
// Consider a case like X:\cat\dog\elk, where we get the following errors:
|
|
// X: ERROR_ACCESS_DENIED
|
|
// X:\cat ERROR_ALREADY_EXISTS
|
|
// X:\cat\dog ERROR_ACCESS_DENIED
|
|
// X:\cat\dog\elk ERROR_FILE_NOT_FOUND
|
|
// Here, the previous access denied error prevented us from creating a parent directory,
|
|
// and the subsequent ERROR_FILE_NOT_FOUND is not the interesting error for the user.
|
|
// Therefore:
|
|
// If the last directory creation reports success, we return success.
|
|
// If the last directory creation fails, we return the most recent non-file-not-found error.
|
|
// If there is no such non-file-not-found error, we return the most recent error.
|
|
|
|
bool _Created_last = false;
|
|
__std_win_error _Error = __std_win_error::_Success;
|
|
__std_win_error _Most_recent_not_file_not_found = __std_win_error::_Success;
|
|
while (_Cursor != _End) {
|
|
const auto _Added_end = _STD find_if(_STD find_if_not(_Cursor, _End, _Is_slash), _End, _Is_slash);
|
|
_Tmp.append(_Cursor, _Added_end);
|
|
const auto _Create_result = __std_fs_create_directory(_Tmp.c_str());
|
|
_Error = _Create_result._Error;
|
|
_Created_last = _Create_result._Created;
|
|
if (_Error != __std_win_error::_Success && !__std_is_file_not_found(_Error)) {
|
|
_Most_recent_not_file_not_found = _Error;
|
|
}
|
|
|
|
_Cursor = _Added_end;
|
|
}
|
|
|
|
if (_Error != __std_win_error::_Success && _Most_recent_not_file_not_found != __std_win_error::_Success) {
|
|
_Error = _Most_recent_not_file_not_found;
|
|
}
|
|
|
|
_Ec = _Make_ec(_Error);
|
|
return _Created_last;
|
|
}
|
|
|
|
_EXPORT_STD inline bool create_directories(const path& _Path) {
|
|
error_code _Ec;
|
|
const bool _Result = _STD filesystem::create_directories(_Path, _Ec);
|
|
if (_Ec) {
|
|
_Throw_fs_error("create_directories", _Ec, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
inline constexpr int _Remove_all_retry_count = 10;
|
|
|
|
inline void _Remove_all_dir(const path& _Path, error_code& _Ec, uintmax_t& _Removed_count) {
|
|
// remove _Path, including any contents
|
|
for (directory_iterator _It(_Path, _Ec);; _It.increment(_Ec)) { // remove nonempty directory contents
|
|
if (_Ec) {
|
|
if (_Ec.category() != _STD system_category()
|
|
|| !__std_is_file_not_found(static_cast<__std_win_error>(_Ec.value()))) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (_It._At_end()) {
|
|
break;
|
|
}
|
|
|
|
const auto& _Subpath = _It->path();
|
|
const auto _Substatus = _It->symlink_status(_Ec);
|
|
if (_Ec) {
|
|
return;
|
|
}
|
|
|
|
if (_Substatus.type() == file_type::directory) {
|
|
_Remove_all_dir(_Subpath, _Ec, _Removed_count);
|
|
} else {
|
|
_Removed_count += _STD filesystem::remove(_Subpath, _Ec);
|
|
}
|
|
|
|
if (_Ec) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int _Retry = 0; _Retry < _Remove_all_retry_count; ++_Retry) {
|
|
// retry up to _Remove_all_retry_count for resilience against
|
|
// A/V tools, search indexers, backup tools, etc.
|
|
const auto _Path_remove_result = __std_fs_remove(_Path.c_str());
|
|
_Removed_count += _Path_remove_result._Removed;
|
|
_Ec = _Make_ec(_Path_remove_result._Error);
|
|
if (_Path_remove_result._Error != __std_win_error::_Directory_not_empty
|
|
&& _Path_remove_result._Error != __std_win_error::_Access_denied) {
|
|
// ERROR_DIRECTORY_NOT_EMPTY if we're waiting for handles to children to be closed,
|
|
// ERROR_ACCESS_DENIED if the directory we're targeting itself is marked for deletion.
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline uintmax_t remove_all(const path& _Path, error_code& _Ec) {
|
|
// remove _Path, including any contents
|
|
_Ec.clear(); // for exception safety
|
|
const auto _First_remove_result = __std_fs_remove(_Path.c_str());
|
|
uintmax_t _Removed_count = _First_remove_result._Removed;
|
|
_Ec = _Make_ec(_First_remove_result._Error);
|
|
if (_First_remove_result._Error == __std_win_error::_Directory_not_empty) {
|
|
_Remove_all_dir(_Path, _Ec, _Removed_count);
|
|
}
|
|
|
|
if (_Ec) {
|
|
_Removed_count = static_cast<uintmax_t>(-1);
|
|
}
|
|
|
|
return _Removed_count;
|
|
}
|
|
|
|
_EXPORT_STD inline uintmax_t remove_all(const path& _Path) {
|
|
error_code _Ec;
|
|
const auto _Removed_count = _STD filesystem::remove_all(_Path, _Ec);
|
|
if (_Ec) {
|
|
_Throw_fs_error("remove_all", _Ec, _Path);
|
|
}
|
|
|
|
return _Removed_count;
|
|
}
|
|
|
|
_NODISCARD inline __std_win_error _Last_write_time(const path& _Path, file_time_type& _Result) noexcept {
|
|
__std_fs_stats _Stats;
|
|
const auto _Error = __std_fs_get_stats(
|
|
_Path.c_str(), &_Stats, __std_fs_stats_flags::_Follow_symlinks | __std_fs_stats_flags::_Last_write_time);
|
|
if (_Error == __std_win_error::_Success) {
|
|
_Result = file_time_type{file_time_type::duration{_Stats._Last_write_time}};
|
|
} else {
|
|
_Result = (file_time_type::min)();
|
|
}
|
|
|
|
return _Error;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_time_type last_write_time(const path& _Path) {
|
|
file_time_type _Result;
|
|
const auto _Error = _Last_write_time(_Path, _Result);
|
|
if (_Error != __std_win_error::_Success) {
|
|
_Throw_fs_error("last_write_time", _Error, _Path);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline file_time_type last_write_time(const path& _Path, error_code& _Ec) noexcept {
|
|
file_time_type _Result;
|
|
_Ec = _Make_ec(_Last_write_time(_Path, _Result));
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD inline void last_write_time(const path& _Target, const file_time_type _New_time) {
|
|
// set the last write time of _Target to _New_time
|
|
const auto _Err = __std_fs_set_last_write_time(_New_time.time_since_epoch().count(), _Target.c_str());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("last_write_time", _Err, _Target);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void last_write_time(
|
|
const path& _Target, const file_time_type _New_time, error_code& _Ec) noexcept {
|
|
// set the last write time of _Target to _New_time
|
|
_Ec = _Make_ec(__std_fs_set_last_write_time(_New_time.time_since_epoch().count(), _Target.c_str()));
|
|
}
|
|
|
|
_EXPORT_STD enum class perm_options { replace = 0x1, add = 0x2, remove = 0x4, nofollow = 0x8 };
|
|
|
|
_BITMASK_OPS(_EXPORT_STD, perm_options)
|
|
|
|
_NODISCARD inline __std_win_error _Permissions(
|
|
const path& _Target, const perms _Perms, perm_options _Options) noexcept {
|
|
bool _Readonly;
|
|
const bool _Follow_symlinks = (_Options & perm_options::nofollow) == perm_options{};
|
|
_Options &= ~perm_options::nofollow;
|
|
const auto _Write_perms = _Perms & perms::_All_write;
|
|
switch (_Options) {
|
|
case perm_options::replace:
|
|
// always apply FILE_ATTRIBUTE_READONLY according to _Perms
|
|
_Readonly = _Write_perms == perms::none;
|
|
break;
|
|
case perm_options::add:
|
|
if (_Write_perms == perms::none) {
|
|
// if we aren't adding any write bits, then we won't change
|
|
// FILE_ATTRIBUTE_READONLY, so there's nothing to do
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_Readonly = false;
|
|
break;
|
|
case perm_options::remove:
|
|
if (_Write_perms != perms::_All_write) {
|
|
// if we aren't removing all write bits, then we won't change
|
|
// FILE_ATTRIBUTE_READONLY, so there's nothing to do
|
|
return __std_win_error::_Success;
|
|
}
|
|
|
|
_Readonly = true;
|
|
break;
|
|
case perm_options::nofollow: // avoid C4061
|
|
default:
|
|
return __std_win_error::_Invalid_parameter;
|
|
}
|
|
|
|
return __std_fs_change_permissions(_Target.c_str(), _Follow_symlinks, _Readonly);
|
|
}
|
|
|
|
_EXPORT_STD inline void permissions(
|
|
const path& _Target, const perms _Perms, const perm_options _Options = perm_options::replace) {
|
|
const auto _Err = _Permissions(_Target, _Perms, _Options);
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("permissions", _Err, _Target);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void permissions(
|
|
const path& _Target, const perms _Perms, const perm_options _Options, error_code& _Ec) noexcept {
|
|
_Ec = _Make_ec(_Permissions(_Target, _Perms, _Options));
|
|
}
|
|
|
|
_EXPORT_STD inline void permissions(const path& _Target, const perms _Perms, error_code& _Ec) noexcept {
|
|
_STD filesystem::permissions(_Target, _Perms, perm_options::replace, _Ec);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path temp_directory_path(error_code& _Ec) {
|
|
// get a location suitable for temporary storage, and verify that it is a directory
|
|
_Ec.clear(); // for exception safety
|
|
path _Result;
|
|
_Result._Text.resize(__std_fs_temp_path_max);
|
|
const auto _Temp_result = __std_fs_get_temp_path(_Result._Text.data());
|
|
_Result._Text.resize(_Temp_result._Size);
|
|
if (_Temp_result._Error == __std_win_error::_Max) { // path could be retrieved, but was not a directory
|
|
_Ec = _STD make_error_code(errc::not_a_directory);
|
|
} else {
|
|
_Ec = _Make_ec(_Temp_result._Error);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path temp_directory_path() {
|
|
// get a location suitable for temporary storage, and verify that it is a directory
|
|
error_code _Ec; // unusual arrangement to allow thrown error_code to have generic_category()
|
|
path _Result(_STD filesystem::temp_directory_path(_Ec));
|
|
if (_Ec) {
|
|
_Throw_fs_error("temp_directory_path", _Ec, _Result);
|
|
}
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path current_path(error_code& _Ec) {
|
|
_Ec.clear(); // for exception safety
|
|
path _Result;
|
|
_Result._Text.resize(__std_fs_max_path);
|
|
for (;;) {
|
|
const auto _Requested_size = static_cast<unsigned long>(_Result._Text.size());
|
|
const auto _Temp_result = __std_fs_get_current_path(_Requested_size, _Result._Text.data());
|
|
_Result._Text.resize(_Temp_result._Size);
|
|
if (_Temp_result._Size < _Requested_size) {
|
|
_Ec = _Make_ec(_Temp_result._Error);
|
|
return _Result;
|
|
}
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path current_path() {
|
|
error_code _Ec;
|
|
path _Result(_STD filesystem::current_path(_Ec));
|
|
if (_Ec) {
|
|
_Throw_fs_error("current_path()", _Ec);
|
|
}
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD inline void current_path(const path& _To, error_code& _Ec) noexcept { // set the current path
|
|
_Ec = _Make_ec(__std_fs_set_current_path(_To._Text.data()));
|
|
}
|
|
|
|
_EXPORT_STD inline void current_path(const path& _To) { // set the current path
|
|
const auto _Err = __std_fs_set_current_path(_To._Text.data());
|
|
if (_Err != __std_win_error::_Success) {
|
|
_Throw_fs_error("current_path(const path&)", _Err, _To);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path weakly_canonical(const path& _Input, error_code& _Ec) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
_Ec.clear(); // for exception safety
|
|
|
|
path _Temp;
|
|
|
|
{
|
|
const auto _Err = _Canonical(_Temp, _Input.native());
|
|
|
|
if (_Err == __std_win_error::_Success) {
|
|
return _Temp;
|
|
}
|
|
|
|
if (!__std_is_file_not_found(_Err)) {
|
|
_Ec = _Make_ec(_Err);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
const path _Normalized = _Input.lexically_normal();
|
|
|
|
path _Result = _Normalized.root_path();
|
|
|
|
const path _Normalized_relative = _Normalized.relative_path();
|
|
|
|
bool _Call_canonical = true;
|
|
|
|
for (const auto& _Elem : _Normalized_relative) {
|
|
_Result /= _Elem;
|
|
|
|
if (_Call_canonical) {
|
|
_Temp.clear();
|
|
|
|
const auto _Err = _Canonical(_Temp, _Result.native());
|
|
|
|
if (_Err == __std_win_error::_Success) {
|
|
_Result = _STD move(_Temp);
|
|
} else if (__std_is_file_not_found(_Err)) {
|
|
_Call_canonical = false;
|
|
} else {
|
|
_Ec = _Make_ec(_Err);
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path weakly_canonical(const path& _Input) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
error_code _Ec;
|
|
|
|
path _Result = _STD filesystem::weakly_canonical(_Input, _Ec);
|
|
|
|
if (_Ec) {
|
|
_Throw_fs_error("weakly_canonical", _Ec, _Input);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path proximate(
|
|
const path& _Path, const path& _Base = _STD filesystem::current_path()) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
const path _Weakly_canonical_path = _STD filesystem::weakly_canonical(_Path);
|
|
const path _Weakly_canonical_base = _STD filesystem::weakly_canonical(_Base);
|
|
return _Weakly_canonical_path.lexically_proximate(_Weakly_canonical_base);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path proximate(const path& _Path, const path& _Base, error_code& _Ec) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
const path _Weakly_canonical_path = _STD filesystem::weakly_canonical(_Path, _Ec);
|
|
|
|
if (_Ec) {
|
|
return {};
|
|
}
|
|
|
|
const path _Weakly_canonical_base = _STD filesystem::weakly_canonical(_Base, _Ec);
|
|
|
|
if (_Ec) {
|
|
return {};
|
|
}
|
|
|
|
return _Weakly_canonical_path.lexically_proximate(_Weakly_canonical_base);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path proximate(const path& _Path, error_code& _Ec) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
const path _Base = _STD filesystem::current_path(_Ec);
|
|
// N4950 [fs.op.proximate]/1 incorrectly calls current_path()
|
|
if (_Ec) {
|
|
return {};
|
|
}
|
|
|
|
return _STD filesystem::proximate(_Path, _Base, _Ec);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path relative(
|
|
const path& _Path, const path& _Base = _STD filesystem::current_path()) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
const path _Weakly_canonical_path = _STD filesystem::weakly_canonical(_Path);
|
|
const path _Weakly_canonical_base = _STD filesystem::weakly_canonical(_Base);
|
|
return _Weakly_canonical_path.lexically_relative(_Weakly_canonical_base);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path relative(const path& _Path, const path& _Base, error_code& _Ec) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
const path _Weakly_canonical_path = _STD filesystem::weakly_canonical(_Path, _Ec);
|
|
|
|
if (_Ec) {
|
|
return {};
|
|
}
|
|
|
|
const path _Weakly_canonical_base = _STD filesystem::weakly_canonical(_Base, _Ec);
|
|
|
|
if (_Ec) {
|
|
return {};
|
|
}
|
|
|
|
return _Weakly_canonical_path.lexically_relative(_Weakly_canonical_base);
|
|
}
|
|
|
|
_EXPORT_STD _NODISCARD inline path relative(const path& _Path, error_code& _Ec) {
|
|
// eventually calls GetFinalPathNameByHandleW
|
|
const path _Base = _STD filesystem::current_path(_Ec);
|
|
// N4950 [fs.op.relative]/1 incorrectly calls current_path()
|
|
if (_Ec) {
|
|
return {};
|
|
}
|
|
|
|
return _STD filesystem::relative(_Path, _Base, _Ec);
|
|
}
|
|
|
|
inline void _Copy_impl(
|
|
const directory_entry& _From, const path& _To, const copy_options _Options, error_code& _Ec) {
|
|
// implement copy, does not clear _Ec for callers
|
|
// Standard quotes herein are relative to N4950
|
|
// The following parts of LWG-3057 are implemented:
|
|
// * guarding equivalent() from nonexistent to
|
|
// * replacing unspecified recursion prevention tag with copy_options::directories_only
|
|
// Other parts of LWG-3057 remain under discussion in the committee and are not yet implemented.
|
|
// (In particular, changes to existing destination flags, and error handling).
|
|
const bool _Flink = (_Options & (copy_options::skip_symlinks | copy_options::copy_symlinks))
|
|
!= copy_options::none; // create_symlinks intentionally removed by LWG-3057
|
|
const auto _Fstat = _From._Get_any_status(_Flink ? _Symlink_status_stats_flags : _Status_stats_flags);
|
|
if (_Fstat._Error != __std_win_error::_Success) { // report an error if exists(f) is false
|
|
_Ec = _Make_ec(_Fstat._Error);
|
|
return;
|
|
}
|
|
|
|
const bool _Tlink =
|
|
(_Options & (copy_options::create_symlinks | copy_options::skip_symlinks)) != copy_options::none;
|
|
const auto _Tstat = _Get_any_status(_To, _Tlink ? _Symlink_status_stats_flags : _Status_stats_flags);
|
|
if (_Tstat._Not_good()) {
|
|
_Ec = _Make_ec(_Tstat._Error);
|
|
return;
|
|
}
|
|
|
|
if (_STD filesystem::exists(_Tstat._Status)) {
|
|
if (_STD filesystem::equivalent(_From, _To, _Ec)) { // report an error if equivalent(from, to) is true
|
|
_Ec = _STD make_error_code(errc::file_exists);
|
|
return;
|
|
}
|
|
|
|
if (_Ec) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const bool _Fstat_is_other =
|
|
_STD filesystem::is_other(_Fstat._Status) && _Fstat._Status.type() != file_type::junction;
|
|
const bool _Tstat_is_other =
|
|
_STD filesystem::is_other(_Tstat._Status) && _Tstat._Status.type() != file_type::junction;
|
|
if (_Fstat_is_other || _Tstat_is_other) {
|
|
// report an error if is_other(f) || is_other(t) is true, and it's not a junction
|
|
_Ec = _STD make_error_code(errc::operation_not_supported);
|
|
return;
|
|
}
|
|
|
|
if (_STD filesystem::is_directory(_Fstat._Status) && _STD filesystem::is_regular_file(_Tstat._Status)) {
|
|
// report an error if is_directory(f) && is_regular_file(t) is true
|
|
_Ec = _STD make_error_code(errc::file_exists);
|
|
return;
|
|
}
|
|
|
|
if (_Fstat._Status.type() == file_type::junction) {
|
|
if ((_Options & copy_options::skip_symlinks) != copy_options::none) {
|
|
return;
|
|
}
|
|
|
|
// _Options includes copy_options::copy_symlinks,
|
|
// since _Fstat is only allowed to be a symbolic link when either skip_symlinks or copy_symlinks
|
|
if (!_STD filesystem::exists(_Tstat._Status)) {
|
|
_Ec = _Make_ec(_Copy_junction(_From, _To));
|
|
}
|
|
}
|
|
|
|
if (_STD filesystem::is_symlink(_Fstat._Status)) {
|
|
if ((_Options & copy_options::skip_symlinks) != copy_options::none) {
|
|
return;
|
|
}
|
|
|
|
// _Options includes copy_options::copy_symlinks,
|
|
// since _Fstat is only allowed to be a symbolic link when either skip_symlinks or copy_symlinks
|
|
if (!_STD filesystem::exists(_Tstat._Status)) {
|
|
_STD filesystem::copy_symlink(_From, _To, _Ec);
|
|
return;
|
|
}
|
|
|
|
// otherwise report an error
|
|
_Ec = _STD make_error_code(errc::operation_not_supported);
|
|
return;
|
|
}
|
|
|
|
if (_STD filesystem::is_regular_file(_Fstat._Status)) {
|
|
if ((_Options & copy_options::directories_only) != copy_options::none) {
|
|
return;
|
|
}
|
|
|
|
if ((_Options & copy_options::create_symlinks) != copy_options::none) {
|
|
// Otherwise, if (condition) then create a symbolic link to the source file
|
|
_STD filesystem::create_symlink(_From, _To, _Ec);
|
|
return;
|
|
}
|
|
|
|
if ((_Options & copy_options::create_hard_links) != copy_options::none) {
|
|
// Otherwise, if (condition) then create a hard link to the source file
|
|
_STD filesystem::create_hard_link(_From, _To, _Ec);
|
|
return;
|
|
}
|
|
|
|
if (_STD filesystem::is_directory(_Tstat._Status)) {
|
|
// Otherwise, if is_directory(t), then copy_file(from, to / from.filename(), options)
|
|
_STD filesystem::copy_file(_From, _To / _From.path().filename(), _Options, _Ec);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, copy_file(_From, _To, _Options)
|
|
_STD filesystem::copy_file(_From, _To, _Options, _Ec);
|
|
return;
|
|
}
|
|
|
|
// The following condition modified by LWG-3057:
|
|
if (_STD filesystem::is_directory(_Fstat._Status)) {
|
|
if ((_Options & copy_options::create_symlinks) != copy_options::none) {
|
|
_Ec = _STD make_error_code(errc::is_a_directory);
|
|
return;
|
|
}
|
|
|
|
_STD filesystem::create_directory(_To, _From, _Ec);
|
|
if (_Ec) {
|
|
return;
|
|
}
|
|
|
|
// Note LWG-3057 uses directories_only as the flag, instead of an unspecified copy_options value:
|
|
if ((_Options & copy_options::recursive) != copy_options::none
|
|
|| (_Options & copy_options::directories_only) == copy_options::none) {
|
|
for (directory_iterator _It(_From, _Ec);; _It.increment(_Ec)) {
|
|
if (_Ec || _It._At_end()) {
|
|
return;
|
|
}
|
|
|
|
// if ((options & copy_options::recursive) != copy_options::none ||
|
|
// !is_directory(linkf ? symlink_status(x.path()) : status(x.path())))
|
|
// copy(x.path(), to/x.path().filename(), options);
|
|
bool _Recurse = (_Options & copy_options::recursive) != copy_options::none;
|
|
if (!_Recurse) {
|
|
const auto _Child_status_result =
|
|
_It->_Get_any_status(_Flink ? _Symlink_status_stats_flags : _Status_stats_flags);
|
|
if (_Child_status_result._Error != __std_win_error::_Success) {
|
|
_Ec = _Make_ec(_Child_status_result._Error);
|
|
return;
|
|
}
|
|
|
|
_Recurse = !_STD filesystem::is_directory(_Child_status_result._Status);
|
|
}
|
|
|
|
if (_Recurse) {
|
|
_Copy_impl(*_It, _To / _It->path().filename(), _Options, _Ec);
|
|
if (_Ec) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, no effects.
|
|
}
|
|
|
|
_EXPORT_STD inline void copy(const path& _From, const path& _To, const copy_options _Options, error_code& _Ec) {
|
|
const directory_entry _From_dir(_From, _Ec);
|
|
if (_Ec) { // report an error if exists(f) is false
|
|
return;
|
|
}
|
|
|
|
_Copy_impl(_From_dir, _To, _Options, _Ec);
|
|
}
|
|
|
|
_EXPORT_STD inline void copy(const path& _From, const path& _To, const copy_options _Options) {
|
|
error_code _Ec;
|
|
_STD filesystem::copy(_From, _To, _Options, _Ec);
|
|
if (_Ec) {
|
|
_Throw_fs_error("copy", _Ec, _From, _To);
|
|
}
|
|
}
|
|
|
|
_EXPORT_STD inline void copy(const path& _From, const path& _To, error_code& _Ec) {
|
|
return _STD filesystem::copy(_From, _To, copy_options::none, _Ec);
|
|
}
|
|
|
|
_EXPORT_STD inline void copy(const path& _From, const path& _To) {
|
|
return _STD filesystem::copy(_From, _To, copy_options::none);
|
|
}
|
|
} // namespace filesystem
|
|
|
|
template <>
|
|
struct hash<filesystem::path> {
|
|
using _ARGUMENT_TYPE_NAME _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS = filesystem::path;
|
|
using _RESULT_TYPE_NAME _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS = size_t;
|
|
_NODISCARD _STATIC_CALL_OPERATOR size_t operator()(const filesystem::path& _Path) _CONST_CALL_OPERATOR noexcept {
|
|
return _STD filesystem::hash_value(_Path);
|
|
}
|
|
};
|
|
|
|
#if _HAS_CXX20
|
|
namespace ranges {
|
|
template <>
|
|
inline constexpr bool enable_borrowed_range<filesystem::directory_iterator> = true;
|
|
template <>
|
|
inline constexpr bool enable_borrowed_range<filesystem::recursive_directory_iterator> = true;
|
|
template <>
|
|
inline constexpr bool enable_view<filesystem::directory_iterator> = true;
|
|
template <>
|
|
inline constexpr bool enable_view<filesystem::recursive_directory_iterator> = true;
|
|
} // namespace ranges
|
|
#endif // _HAS_CXX20
|
|
|
|
_STD_END
|
|
|
|
#pragma pop_macro("new")
|
|
_STL_RESTORE_CLANG_WARNINGS
|
|
#pragma warning(pop)
|
|
#pragma pack(pop)
|
|
#endif // ^^^ _HAS_CXX17 ^^^
|
|
#endif // _STL_COMPILER_PREPROCESSOR
|
|
#endif // _FILESYSTEM_
|