I have implemented the feedback from Jonathan Wakely in this patch.
Changes in v3:
* Include <ntdef.h> for REPARSE_DATA_BUFFER and removed
the manual definition of REPARSE_DATA_BUFFER.
* Replace path_starts_with_dotdot with path_find_dotdot_component
which returns an iterator to the first component that is ".."
or the end iterator.
* Wrap the inner fs::exists call inside a Windows-specific ifdef.
* Use PRREPARSE_DATA_BUFFER instead of REPARSE_DATA_BUFFER*.
* Move the helper functions for __gnu_posix::stat into a new
namespace called __detail.
Changes in v2:
* Wrapped Windows specific code in an ifdef.
libstdc++-v3/Changelog:
* src/c++17/fs_ops.cc:
Include <winioctl.h> for FSCTL_GET_REPARSE_POINT.
Include <ntdef.h> for REPARSE_DATA_BUFFER.
(path_find_dotdot_component): New helper function for fs::absolute.
(fs::absolute): Call GetFullPathNameW on part of path before ".."
and then append the rest to the result.
(fs::canonical): Don't use lexically_normal in call to fs::absolute.
Don't check if path exists if it contains "..".
Check that component of path exists if it isn't empty or "." or "..".
(windows_create_symlink): New helper function for fs::create_symlink
and fs::create_directory_symlink.
(fs::create_directory_symlink): Call windows_create_symlink on Windows.
(fs::create_symlink): Call windows_create_symlink on Windows.
(auto_win_file_handle::auto_win_file_handle): Add follow_symlink
parameter to control whether the handle should open the symlink
or the target, with a default value of true.
(windows_read_symlink_handle): New helper function
for fs::read_symlink.
(fs::read_symlink): Call windows_read_symlink_handle on Windows.
(fs::remove): Call RemoveDirectoryW only for directories, and
DeleteFileW for regular files, but attempt both for symlinks.
(fs::remove_all): Return immediately if path is empty.
Check if path points to a symlink, and if so, remove the
symlink using fs::remove.
* src/filesystem/ops-common.h:
Define S_IFLNK and S_ISLNK.
Create helper namespace __detail.
(__detail::__open_for_stat): New helper function for stat and lstat.
(__detail::__is_handle_symlink): New helper function for
stat, lstat, and fs::read_symlink.
(__detail::__stat_windows): New helper function for stat and lstat.
(__gnu_posix::stat, __gnu_posix::lstat): Use __stat_windows to properly
follow or not follow symlinks, and check if file is a symlink.
* testsuite/27_io/filesystem/operations/canonical.cc (test03):
Use fs::create_directory_symlink instead of fs::create_symlink.
* testsuite/27_io/filesystem/operations/copy.cc (test02):
Create a symlink to temporary file instead of ".".
Use fs::exists(symlink_status()) instead of fs::exists for symlinks.
* testsuite/27_io/filesystem/operations/weakly_canonical.cc (test01):
Use fs::create_directory_symlink instead of fs::create_symlink.
* testsuite/util/testsuite_fs.h: Do not define NO_SYMLINKS on Windows.
---
libstdc++-v3/src/c++17/fs_ops.cc | 267 +++++++++++++++++-
libstdc++-v3/src/filesystem/ops-common.h | 81 +++++-
.../27_io/filesystem/operations/canonical.cc | 2 +-
.../27_io/filesystem/operations/copy.cc | 7 +-
.../filesystem/operations/weakly_canonical.cc | 2 +-
libstdc++-v3/testsuite/util/testsuite_fs.h | 2 +
6 files changed, 341 insertions(+), 20 deletions(-)
diff --git a/libstdc++-v3/src/c++17/fs_ops.cc b/libstdc++-v3/src/c++17/fs_ops.cc
index 4f188153ae3..97cdc5ab2dc 100644
--- a/libstdc++-v3/src/c++17/fs_ops.cc
+++ b/libstdc++-v3/src/c++17/fs_ops.cc
@@ -56,6 +56,8 @@
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
+# include <winioctl.h> // FSCTL_GET_REPARSE_POINT
+# include <ntdef.h> // REPARSE_DATA_BUFFER
#endif
#define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem {
@@ -78,6 +80,24 @@ fs::absolute(const path& p)
return ret;
}
+#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
+namespace
+{
+ fs::path::iterator
+ path_find_dotdot_component(const fs::path& p)
+ {
+ for (auto it = p.begin(); it != p.end(); ++it)
+ {
+ if (it->native() == L"..")
+ {
+ return it;
+ }
+ }
+ return p.end();
+ }
+}
+#endif
+
fs::path
fs::absolute(const path& p, error_code& ec)
{
@@ -97,6 +117,8 @@ fs::absolute(const path& p, error_code& ec)
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
// s must remain null-terminated
wstring_view s = p.native();
+ path after_dotdot_p;
+ path before_dotdot_p;
if (p.has_root_directory()) // implies !p.has_root_name()
{
@@ -108,6 +130,24 @@ fs::absolute(const path& p, error_code& ec)
s.remove_prefix(std::min(s.length(), pos) - 1);
}
+
+ // GetFullPathNameW does not work correctly with a .. right after a symlink,
+ // so if we have a .. in the path, run GetFullPathNameW on the part before
+ // the dotdot, and then append the rest.
+ auto dotdot_it = path_find_dotdot_component(p);
+ if (dotdot_it != p.end() && dotdot_it != p.begin())
+ {
+ for (auto it = p.begin(); it != dotdot_it; ++it)
+ {
+ before_dotdot_p /= *it;
+ }
+ for (auto it = dotdot_it; it != p.end(); ++it)
+ {
+ after_dotdot_p /= *it;
+ }
+ s = before_dotdot_p.native();
+ }
+
uint32_t len = 1024;
wstring buf;
do
@@ -123,6 +163,9 @@ fs::absolute(const path& p, error_code& ec)
ec = __last_system_error();
else
ret = std::move(buf);
+
+ if (!after_dotdot_p.empty())
+ ret /= after_dotdot_p;
#else
ret = current_path(ec);
ret /= p;
@@ -162,11 +205,7 @@ fs::path
fs::canonical(const path& p, error_code& ec)
{
path result;
-#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
- const path pa = absolute(p.lexically_normal(), ec);
-#else
const path pa = absolute(p, ec);
-#endif
if (ec)
return result;
@@ -192,7 +231,17 @@ fs::canonical(const path& p, error_code& ec)
}
#endif
+#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ // Windows handles relative paths after symlinks incorrectly.
+ // If we have a .. after a symlink, the .. will be cancelled before the
+ // symlink is resolved. For example, if we have baz -> ../bar,
+ // and we have the path dir/foo/baz/../bar, Linux would interpret this as
+ // dir/foo/../bar/../bar = dir/bar, but Windows thinks the path is
+ // dir/foo/bar. This leads to possible false negatives using exists.
+ if (path_find_dotdot_component(pa) == pa.end() && !exists(pa, ec))
+#else
if (!exists(pa, ec))
+#endif
{
if (!ec)
ec = make_error_code(std::errc::no_such_file_or_directory);
@@ -234,7 +283,17 @@ fs::canonical(const path& p, error_code& ec)
{
result /= f;
- if (is_symlink(result, ec))
+ auto st = symlink_status(result, ec);
+#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ if (!exists(st))
+ {
+ if (!ec)
+ ec.assign(ENOENT, std::generic_category());
+ result.clear();
+ return result;
+ }
+#endif
+ if (is_symlink(st))
{
path link = read_symlink(result, ec);
if (!ec)
@@ -644,6 +703,44 @@ fs::create_directory(const path& p, const path& attributes,
#endif
}
+#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
+namespace
+{
+ void
+ windows_create_symlink(const fs::path& to, const fs::path& new_symlink,
+ const fs::file_type target_type,
+ std::error_code& ec) noexcept
+ {
+ auto symlink_flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+ if (target_type == fs::file_type::directory)
+ symlink_flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
+ // Windows can't handle relative symlinks with non-preferred slashes.
+ // Creating the symlink will succeed, but the symlink won't resolve
+ // correctly in later operations.
+ const fs::path* preferred_to = &to;
+ fs::path to2;
+ if (to.native().find(L'/') != std::string::npos)
+ {
+ __try
+ {
+ to2 = to;
+ to2.make_preferred();
+ preferred_to = &to2;
+ }
+ __catch (const std::bad_alloc&)
+ {
+ ec = std::make_error_code(std::errc::not_enough_memory);
+ return;
+ }
+ }
+ if (CreateSymbolicLinkW(new_symlink.c_str(), preferred_to->c_str(),
+ symlink_flags))
+ ec.clear();
+ else
+ ec = std::__last_system_error();
+ }
+}
+#endif
void
fs::create_directory_symlink(const path& to, const path& new_symlink)
@@ -660,7 +757,7 @@ fs::create_directory_symlink(const path& to, const path&
new_symlink,
error_code& ec) noexcept
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
- ec = std::make_error_code(std::errc::function_not_supported);
+ windows_create_symlink(to, new_symlink, file_type::directory, ec);
#else
create_symlink(to, new_symlink, ec);
#endif
@@ -715,6 +812,8 @@ fs::create_symlink(const path& to, const path& new_symlink,
ec.assign(errno, std::generic_category());
else
ec.clear();
+#elif _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ windows_create_symlink(to, new_symlink, file_type::regular, ec);
#else
ec = std::make_error_code(std::errc::function_not_supported);
#endif
@@ -829,10 +928,14 @@ namespace
struct auto_win_file_handle
{
explicit
- auto_win_file_handle(const wchar_t* p, std::error_code& ec) noexcept
+ auto_win_file_handle(const wchar_t* p, std::error_code& ec,
+ const bool follow_symlink = true) noexcept
: handle(CreateFileW(p, 0,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
- 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)),
+ 0, OPEN_EXISTING,
+ (FILE_FLAG_BACKUP_SEMANTICS
+ | (follow_symlink ? 0 :
FILE_FLAG_OPEN_REPARSE_POINT)),
+ 0)),
ec(ec)
{
if (handle == INVALID_HANDLE_VALUE)
@@ -1193,6 +1296,73 @@ fs::proximate(const path& p, const path& base,
error_code& ec)
return result;
}
+#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
+namespace
+{
+ void
+ windows_read_symlink_handle(auto_win_file_handle& link_handle,
+ std::error_code& ec,
+ fs::path& result)
+ {
+ PREPARSE_DATA_BUFFER reparse_buffer = nullptr;
+ std::unique_ptr<char[]> big_buffer;
+
+ // Allocate enough memory on the stack to get the reparse data
+ // plus a 260 character path. Should be sufficient in most cases.
+ // Allocate an extra wchar_t to ensure we can manually null terminate.
+ static constexpr size_t small_buffer_size = sizeof(REPARSE_DATA_BUFFER)
+ + 260 * sizeof(wchar_t);
+ char small_buffer[small_buffer_size + sizeof(wchar_t)];
+ reparse_buffer = reinterpret_cast<PREPARSE_DATA_BUFFER>(&small_buffer[0]);
+ long unsigned int bytes_returned, big_buffer_size;
+
+ // Attempt to get the reparse data with the buffer on the stack
+ // before allocating the exact amount needed on the heap.
+ bool got_reparse_data = DeviceIoControl(link_handle.handle,
+ FSCTL_GET_REPARSE_POINT,
+ nullptr, 0,
+ &small_buffer, small_buffer_size,
+ &bytes_returned, nullptr);
+
+ int last_error = GetLastError();
+ if (!got_reparse_data && last_error == ERROR_MORE_DATA)
+ {
+ big_buffer_size = bytes_returned;
+ big_buffer.reset(new char[big_buffer_size + sizeof(wchar_t)]);
+ got_reparse_data = DeviceIoControl(link_handle.handle,
+ FSCTL_GET_REPARSE_POINT,
+ nullptr, 0,
+ big_buffer.get(), big_buffer_size,
+ &bytes_returned, nullptr);
+ if (!got_reparse_data)
+ {
+ ec = std::__last_system_error();
+ return;
+ }
+
+ reparse_buffer
+ = reinterpret_cast<PREPARSE_DATA_BUFFER>(big_buffer.get());
+
+ }
+ else
+ {
+ if (!got_reparse_data)
+ {
+ ec = std::__last_system_error();
+ return;
+ }
+ }
+
+ ec.clear();
+ auto& symlink_buffer = reparse_buffer->SymbolicLinkReparseBuffer;
+ wchar_t* target_name = &symlink_buffer.PathBuffer[0];
+ target_name += symlink_buffer.PrintNameOffset / sizeof(wchar_t);
+ target_name[symlink_buffer.PrintNameLength / sizeof(wchar_t)] = L'\0';
+ result = target_name;
+ }
+};
+#endif // _GLIBCXX_FILESYSTEM_IS_WINDOWS
+
fs::path
fs::read_symlink(const path& p)
{
@@ -1248,6 +1418,25 @@ fs::path fs::read_symlink(const path& p, error_code& ec)
bufsz *= 2;
}
while (true);
+#elif _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ auto_win_file_handle link_handle(p.c_str(), ec, false);
+ if (!link_handle)
+ return result;
+
+ int is_symlink = __detail::__is_handle_symlink(link_handle.handle);
+ if (is_symlink == -1)
+ {
+ ec = __last_system_error();
+ return result;
+ }
+
+ if (!is_symlink)
+ {
+ ec.assign(EINVAL, std::generic_category());
+ return result;
+ }
+
+ windows_read_symlink_handle(link_handle, ec, result);
#else
ec = std::make_error_code(std::errc::function_not_supported);
#endif
@@ -1291,8 +1480,14 @@ fs::remove(const path& p, error_code& ec) noexcept
auto st = symlink_status(p, ec);
if (exists(st))
{
- if ((is_directory(p, ec) && RemoveDirectoryW(p.c_str()))
- || DeleteFileW(p.c_str()))
+ if ((is_directory(st) || is_symlink(st))
+ && RemoveDirectoryW(p.c_str()))
+ {
+ ec.clear();
+ return true;
+ }
+ else if ((is_regular_file(st) || is_symlink(st))
+ && DeleteFileW(p.c_str()))
{
ec.clear();
return true;
@@ -1320,6 +1515,30 @@ std::uintmax_t
fs::remove_all(const path& p)
{
error_code ec;
+#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ if (p.empty())
+ return 0;
+ // The current opendir implementation on Windows always follows an initial
+ // symlink. Therefore, if remove_all is called on a symlink,
+ // the target is removed. Call remove if we have a symlink.
+ auto p_status = symlink_status(p, ec);
+ if (!exists(p_status))
+ {
+ int err = ec.default_error_condition().value();
+ bool not_found = !ec || is_not_found_errno(err);
+ if (!not_found)
+ _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove all",
+ p, ec));
+ return 0;
+ }
+ if (is_symlink(p_status))
+ {
+ if (!remove(p, ec))
+ _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove all",
+ p, ec));
+ return 1;
+ }
+#endif
uintmax_t count = 0;
recursive_directory_iterator dir(p, directory_options{64|128}, ec);
switch (ec.value()) // N.B. assumes ec.category() == std::generic_category()
@@ -1363,6 +1582,34 @@ fs::remove_all(const path& p)
std::uintmax_t
fs::remove_all(const path& p, error_code& ec)
{
+#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ if (p.empty())
+ {
+ ec.clear();
+ return 0;
+ }
+ // The current opendir implementation on Windows always follows an initial
+ // symlink. Therefore, if remove_all is called on a symlink,
+ // the target is removed. Call remove if we have a symlink.
+ auto p_status = symlink_status(p, ec);
+ if (!exists(p_status))
+ {
+ int err = ec.default_error_condition().value();
+ bool not_found = !ec || is_not_found_errno(err);
+ if (not_found)
+ {
+ ec.clear();
+ return 0;
+ }
+ return -1;
+ }
+ if (is_symlink(p_status))
+ {
+ if (remove(p, ec))
+ return 1;
+ return ec ? -1 : 0;
+ }
+#endif
uintmax_t count = 0;
recursive_directory_iterator dir(p, directory_options{64|128}, ec);
switch (ec.value()) // N.B. assumes ec.category() == std::generic_category()
diff --git a/libstdc++-v3/src/filesystem/ops-common.h
b/libstdc++-v3/src/filesystem/ops-common.h
index 4feacfdb932..36cc99b7e0b 100644
--- a/libstdc++-v3/src/filesystem/ops-common.h
+++ b/libstdc++-v3/src/filesystem/ops-common.h
@@ -105,6 +105,80 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
namespace filesystem
{
+#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
+namespace __detail
+{
+#define S_IFLNK 0xC000
+#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+
+ using stat_type = struct ::__stat64;
+
+ inline HANDLE __open_for_stat(const wchar_t* path, bool following_symlinks)
+ {
+ constexpr auto share_flags
+ = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;
+ auto file_flags = FILE_FLAG_BACKUP_SEMANTICS;
+ if (!following_symlinks)
+ file_flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+ HANDLE handle;
+ handle = CreateFileW(path, 0, share_flags, 0, OPEN_EXISTING, file_flags,
0);
+
+ if (handle == INVALID_HANDLE_VALUE)
+ {
+ // CreateFileW does not set errno.
+ std::error_condition generic_error
+ = std::__last_system_error().default_error_condition();
+ errno = generic_error.value();
+ }
+
+ return handle;
+ }
+
+ // -1 error, 0 not a symlink, 1 a symlink
+ inline int __is_handle_symlink(HANDLE handle)
+ {
+ FILE_ATTRIBUTE_TAG_INFO type_info;
+ if (!GetFileInformationByHandleEx(handle, FileAttributeTagInfo,
+ &type_info, sizeof(type_info)))
+ {
+ errno = std::__last_system_error().default_error_condition().value();
+ return -1;
+ }
+ return type_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT
+ && type_info.ReparseTag == IO_REPARSE_TAG_SYMLINK;
+ }
+
+ inline int __stat_windows(const wchar_t* path, stat_type* buffer,
+ bool following_symlinks)
+ {
+ HANDLE handle = __open_for_stat(path, following_symlinks);
+ if (handle == INVALID_HANDLE_VALUE)
+ return -1;
+ // Manually check for symlink, because _fstat does not.
+ int is_symlink = __is_handle_symlink(handle);
+ if (is_symlink == -1)
+ {
+ CloseHandle(handle);
+ return -1;
+ }
+ int fd = ::_open_osfhandle((intptr_t)handle, _O_RDONLY);
+ if (fd == -1)
+ {
+ CloseHandle(handle);
+ return -1;
+ }
+ int stat_result = ::_fstat64(fd, buffer);
+ if (is_symlink)
+ {
+ // Clear the previous file type.
+ buffer->st_mode &= ~S_IFMT;
+ buffer->st_mode |= S_IFLNK;
+ }
+ ::_close(fd);
+ return stat_result;
+ }
+}
+#endif
namespace __gnu_posix
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
@@ -121,13 +195,10 @@ namespace __gnu_posix
using stat_type = struct ::__stat64;
inline int stat(const wchar_t* path, stat_type* buffer)
- { return ::_wstat64(path, buffer); }
+ { return __detail::__stat_windows(path, buffer, true); }
inline int lstat(const wchar_t* path, stat_type* buffer)
- {
- // FIXME: symlinks not currently supported
- return stat(path, buffer);
- }
+ { return __detail::__stat_windows(path, buffer, false); }
using ::mode_t;
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc
b/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc
index 6eb7ce28928..6005e423852 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc
@@ -118,7 +118,7 @@ test03()
const fs::path baz = dir/"foo//../bar///";
#endif
#else
- fs::create_symlink("../bar", foo/"baz");
+ fs::create_directory_symlink("../bar", foo/"baz");
const fs::path baz = dir/"foo//./baz///";
#endif
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc
b/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc
index 289bef6160b..50c7b0d1577 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc
@@ -68,13 +68,14 @@ test02()
{
#ifndef NO_SYMLINKS
const std::error_code bad_ec = make_error_code(std::errc::invalid_argument);
+ __gnu_test::scoped_file tmp_file;
auto from = __gnu_test::nonexistent_path();
std::error_code ec;
ec = bad_ec;
- fs::create_symlink(".", from, ec);
+ fs::create_symlink(tmp_file.path, from, ec);
VERIFY( !ec );
- VERIFY( fs::exists(from) );
+ VERIFY( fs::exists(symlink_status(from)) );
auto to = __gnu_test::nonexistent_path();
ec = bad_ec;
@@ -97,7 +98,7 @@ test02()
ec = bad_ec;
fs::copy(from, to, fs::copy_options::copy_symlinks, ec);
VERIFY( !ec );
- VERIFY( fs::exists(to) );
+ VERIFY( fs::exists(symlink_status(to)) );
VERIFY( is_symlink(to) );
ec.clear();
diff --git
a/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc
b/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc
index 6085d5568e6..1c63bf4834d 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc
@@ -40,7 +40,7 @@ test01()
fs::path p;
#ifndef NO_SYMLINKS
- fs::create_symlink("../bar", foo/"bar");
+ fs::create_directory_symlink("../bar", foo/"bar");
p = fs::weakly_canonical(dir/"foo//./bar///../biz/.");
VERIFY( p == dirc/"biz/" );
diff --git a/libstdc++-v3/testsuite/util/testsuite_fs.h
b/libstdc++-v3/testsuite/util/testsuite_fs.h
index 9cf400d87db..cc0264b2563 100644
--- a/libstdc++-v3/testsuite/util/testsuite_fs.h
+++ b/libstdc++-v3/testsuite/util/testsuite_fs.h
@@ -43,8 +43,10 @@ namespace test_fs = std::experimental::filesystem;
#endif
#ifndef _GLIBCXX_HAVE_SYMLINK
+#ifndef _GLIBCXX_FILESYSTEM_IS_WINDOWS
#define NO_SYMLINKS
#endif
+#endif
#if !defined (_GLIBCXX_HAVE_SYS_STATVFS_H) \
&& !defined (_GLIBCXX_FILESYSTEM_IS_WINDOWS)
--
2.50.0