[libcxx-commits] [libcxx] Properly implement std::fs::is_socket() on Windows (PR #213233)

Yuriy Chernyshov via libcxx-commits libcxx-commits at lists.llvm.org
Fri Jul 31 08:58:44 PDT 2026


https://github.com/georgthegreat updated https://github.com/llvm/llvm-project/pull/213233

>From 789e0236c464c0506198cea6002ff3890b9985d3 Mon Sep 17 00:00:00 2001
From: Yuriy Chernyshov <thegeorg at yandex-team.com>
Date: Fri, 31 Jul 2026 12:25:41 +0300
Subject: [PATCH 1/4] Properly implement std::fs::is_socket() on Windows

---
 libcxx/src/filesystem/posix_compat.h          | 52 ++++++++++++++++---
 .../fs.op.is_socket/is_socket.pass.cpp        | 24 +++++++++
 libcxx/test/support/filesystem_test_helper.h  | 39 ++++++++++++++
 3 files changed, 107 insertions(+), 8 deletions(-)

diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h
index ddd99d8aaf206..d03580daddddc 100644
--- a/libcxx/src/filesystem/posix_compat.h
+++ b/libcxx/src/filesystem/posix_compat.h
@@ -75,6 +75,12 @@ struct LIBCPP_REPARSE_DATA_BUFFER {
     } GenericReparseBuffer;
   };
 };
+
+// The reparse tag used for AF_UNIX (Unix domain) socket files isn't always
+// present in the Windows SDK headers, so define it ourselves if needed.
+#  ifndef IO_REPARSE_TAG_AF_UNIX
+#    define IO_REPARSE_TAG_AF_UNIX 0x80000023L
+#  endif
 #endif
 
 _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
@@ -161,21 +167,35 @@ inline int stat_handle(HANDLE h, StatT* buf) {
   } else {
     buf->st_mode |= _S_IFREG;
   }
+  bool is_af_unix_socket = false;
   if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
     FILE_ATTRIBUTE_TAG_INFO tag;
     if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag, sizeof(tag)))
       return -1;
-    if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK)
+    if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK) {
       buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFLNK;
+    } else if (tag.ReparseTag == IO_REPARSE_TAG_AF_UNIX) {
+      buf->st_mode      = (buf->st_mode & ~_S_IFMT) | _S_IFSOCK;
+      is_af_unix_socket = true;
+    }
   }
   FILE_STANDARD_INFO standard;
-  if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, sizeof(standard)))
+  if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, sizeof(standard))) {
+    // AF_UNIX (Unix domain) socket files don't support all of the information
+    // queries below; report what we already have (mode/timestamps) instead of
+    // failing outright, matching the behavior of the system stat()/os.stat().
+    if (is_af_unix_socket)
+      return 0;
     return -1;
+  }
   buf->st_nlink = standard.NumberOfLinks;
   buf->st_size  = standard.EndOfFile.QuadPart;
   BY_HANDLE_FILE_INFORMATION info;
-  if (!GetFileInformationByHandle(h, &info))
+  if (!GetFileInformationByHandle(h, &info)) {
+    if (is_af_unix_socket)
+      return 0;
     return -1;
+  }
   buf->st_dev = info.dwVolumeSerialNumber;
   memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4);
   memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4);
@@ -183,11 +203,27 @@ inline int stat_handle(HANDLE h, StatT* buf) {
 }
 
 inline int stat_file(const wchar_t* path, StatT* buf, DWORD flags) {
-  WinHandle h(path, FILE_READ_ATTRIBUTES, flags);
-  if (!h)
-    return -1;
-  int ret = stat_handle(h, buf);
-  return ret;
+  {
+    WinHandle h(path, FILE_READ_ATTRIBUTES, flags);
+    if (h && stat_handle(h, buf) == 0)
+      return 0;
+  }
+  // Some reparse points, such as AF_UNIX (Unix domain) socket files, can't be
+  // opened/queried by following the reparse point (there is no target to
+  // follow). CreateFileW may fail with e.g. ERROR_CANT_ACCESS_FILE, or the
+  // subsequent GetFileInformationByHandle* calls may fail. In that case, fall
+  // back to opening the reparse point itself so that we can still report its
+  // attributes and type. This matches the behavior of the system
+  // stat()/os.stat() on such files.
+  if (!(flags & FILE_FLAG_OPEN_REPARSE_POINT)) {
+    DWORD attributes = GetFileAttributesW(path);
+    if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+      WinHandle hr(path, FILE_READ_ATTRIBUTES, flags | FILE_FLAG_OPEN_REPARSE_POINT);
+      if (hr)
+        return stat_handle(hr, buf);
+    }
+  }
+  return -1;
 }
 
 inline int stat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, 0); }
diff --git a/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp b/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp
index 382685c21dd99..18e5a19675cda 100644
--- a/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp
+++ b/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp
@@ -67,6 +67,29 @@ static void test_exist_not_found()
     assert(is_socket(p) == false);
 }
 
+static void test_is_socket_for_real_socket()
+{
+    // Some platforms don't support creating socket files.
+#if !defined(__FreeBSD__) && !defined(__APPLE__)
+    scoped_test_env env;
+    const path sock = env.create_socket("socket");
+
+    // A bound AF_UNIX socket file must be reported as a socket, without error.
+    // On Windows this is a regression test: the socket file is a reparse point
+    // that cannot be opened by following it, which previously made status()
+    // throw filesystem_error instead of reporting file_type::socket.
+    std::error_code ec = GetTestEC();
+    assert(is_socket(sock, ec) == true);
+    assert(!ec);
+
+    assert(is_socket(sock) == true);
+
+    assert(is_regular_file(sock) == false);
+    assert(is_directory(sock) == false);
+    assert(exists(sock) == true);
+#endif
+}
+
 static void test_is_socket_fails()
 {
     scoped_test_env env;
@@ -94,6 +117,7 @@ int main(int, char**) {
     signature_test();
     is_socket_status_test();
     test_exist_not_found();
+    test_is_socket_for_real_socket();
     test_is_socket_fails();
 
     return 0;
diff --git a/libcxx/test/support/filesystem_test_helper.h b/libcxx/test/support/filesystem_test_helper.h
index 2ad9efb32c60f..45fdafeaf4b2f 100644
--- a/libcxx/test/support/filesystem_test_helper.h
+++ b/libcxx/test/support/filesystem_test_helper.h
@@ -10,7 +10,14 @@
 #else
 #include <io.h>
 #include <direct.h>
+// winsock2.h must be included before windows.h to avoid clashing with the
+// older winsock.h that windows.h would otherwise pull in.
+#include <winsock2.h> // for AF_UNIX sockets
+#include <afunix.h>
 #include <windows.h> // for CreateSymbolicLink, CreateHardLink
+#if defined(_MSC_VER)
+#pragma comment(lib, "ws2_32")
+#endif
 #endif
 
 #include <cassert>
@@ -34,6 +41,8 @@
 # include <sys/socket.h>
 # include <sys/un.h>
 #endif
+// On Windows the AF_UNIX socket headers (winsock2.h/afunix.h) are included
+// above, before windows.h.
 namespace fs = std::filesystem;
 
 namespace utils {
@@ -341,6 +350,36 @@ struct scoped_test_env
     assert(::bind(fd, reinterpret_cast<::sockaddr*>(&address), sizeof(address)) == 0);
     return file;
   }
+#elif defined(_WIN32)
+    // Windows supports AF_UNIX (Unix domain) sockets, which materialize as a
+    // reparse-point file on disk. Winsock must be initialized before use; this
+    // is done once per process by WinsockInit below.
+    std::string create_socket(std::string file) {
+      static const int wsaInit = WinsockInit();
+      (void)wsaInit;
+
+      file = sanitize_path(std::move(file));
+
+      ::sockaddr_un address;
+      address.sun_family = AF_UNIX;
+      assert(file.size() < sizeof(address.sun_path));
+      std::memcpy(address.sun_path, file.c_str(), file.size() + 1);
+
+      SOCKET fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
+      assert(fd != INVALID_SOCKET);
+      assert(::bind(fd, reinterpret_cast<::sockaddr*>(&address), sizeof(address)) == 0);
+      // Intentionally leave the socket open: the file exists on disk as long as
+      // the socket lives, which is sufficient for the duration of the test.
+      return file;
+    }
+
+private:
+    static int WinsockInit() {
+      WSADATA wsaData;
+      return ::WSAStartup(MAKEWORD(2, 2), &wsaData);
+    }
+
+public:
 #endif
 
     fs::path test_root;

>From 51aacdefc6fcc3802e6e25723c1c24506396d698 Mon Sep 17 00:00:00 2001
From: Yuriy Chernyshov <thegeorg at yandex-team.com>
Date: Fri, 31 Jul 2026 12:56:39 +0300
Subject: [PATCH 2/4] clang-format

---
 libcxx/src/filesystem/posix_compat.h          |  10 +-
 .../fs.op.is_socket/is_socket.pass.cpp        | 147 ++--
 libcxx/test/support/filesystem_test_helper.h  | 818 +++++++++---------
 3 files changed, 471 insertions(+), 504 deletions(-)

diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h
index d03580daddddc..ed1149cd13ba1 100644
--- a/libcxx/src/filesystem/posix_compat.h
+++ b/libcxx/src/filesystem/posix_compat.h
@@ -132,14 +132,8 @@ namespace detail {
 class WinHandle {
 public:
   WinHandle(const wchar_t* p, DWORD access, DWORD flags) {
-    h = CreateFileW(
-        p,
-        access,
-        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
-        nullptr,
-        OPEN_EXISTING,
-        FILE_FLAG_BACKUP_SEMANTICS | flags,
-        nullptr);
+    h = CreateFileW(p, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
+                    FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr);
   }
   ~WinHandle() {
     if (h != INVALID_HANDLE_VALUE)
diff --git a/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp b/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp
index 18e5a19675cda..5cfad2f72d1aa 100644
--- a/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp
+++ b/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp
@@ -26,99 +26,94 @@
 namespace fs = std::filesystem;
 using namespace fs;
 
-static void signature_test()
-{
-    file_status s; ((void)s);
-    const path p; ((void)p);
-    std::error_code ec; ((void)ec);
-    ASSERT_NOEXCEPT(is_socket(s));
-    ASSERT_NOEXCEPT(is_socket(p, ec));
-    ASSERT_NOT_NOEXCEPT(is_socket(p));
+static void signature_test() {
+  file_status s;
+  ((void)s);
+  const path p;
+  ((void)p);
+  std::error_code ec;
+  ((void)ec);
+  ASSERT_NOEXCEPT(is_socket(s));
+  ASSERT_NOEXCEPT(is_socket(p, ec));
+  ASSERT_NOT_NOEXCEPT(is_socket(p));
 }
 
-static void is_socket_status_test()
-{
-    struct TestCase {
-        file_type type;
-        bool expect;
-    };
-    const TestCase testCases[] = {
-        {file_type::none, false},
-        {file_type::not_found, false},
-        {file_type::regular, false},
-        {file_type::directory, false},
-        {file_type::symlink, false},
-        {file_type::block, false},
-        {file_type::character, false},
-        {file_type::fifo, false},
-        {file_type::socket, true},
-        {file_type::unknown, false}
-    };
-    for (auto& TC : testCases) {
-        file_status s(TC.type);
-        assert(is_socket(s) == TC.expect);
-    }
+static void is_socket_status_test() {
+  struct TestCase {
+    file_type type;
+    bool expect;
+  };
+  const TestCase testCases[] = {
+      {file_type::none, false}, {file_type::not_found, false}, {file_type::regular, false},
+      {file_type::directory, false},
+      {file_type::symlink, false},
+      {file_type::block, false},
+      {file_type::character, false},
+      {file_type::fifo, false},
+      {file_type::socket, true},
+      {file_type::unknown, false}};
+  for (auto& TC : testCases) {
+    file_status s(TC.type);
+    assert(is_socket(s) == TC.expect);
+  }
 }
 
-static void test_exist_not_found()
-{
-    static_test_env static_env;
-    const path p = static_env.DNE;
-    assert(is_socket(p) == false);
+static void test_exist_not_found() {
+  static_test_env static_env;
+  const path p = static_env.DNE;
+  assert(is_socket(p) == false);
 }
 
-static void test_is_socket_for_real_socket()
-{
-    // Some platforms don't support creating socket files.
+static void test_is_socket_for_real_socket() {
+  // Some platforms don't support creating socket files.
 #if !defined(__FreeBSD__) && !defined(__APPLE__)
-    scoped_test_env env;
-    const path sock = env.create_socket("socket");
-
-    // A bound AF_UNIX socket file must be reported as a socket, without error.
-    // On Windows this is a regression test: the socket file is a reparse point
-    // that cannot be opened by following it, which previously made status()
-    // throw filesystem_error instead of reporting file_type::socket.
-    std::error_code ec = GetTestEC();
-    assert(is_socket(sock, ec) == true);
-    assert(!ec);
-
-    assert(is_socket(sock) == true);
-
-    assert(is_regular_file(sock) == false);
-    assert(is_directory(sock) == false);
-    assert(exists(sock) == true);
+  scoped_test_env env;
+  const path sock = env.create_socket("socket");
+
+  // A bound AF_UNIX socket file must be reported as a socket, without error.
+  // On Windows this is a regression test: the socket file is a reparse point
+  // that cannot be opened by following it, which previously made status()
+  // throw filesystem_error instead of reporting file_type::socket.
+  std::error_code ec = GetTestEC();
+  assert(is_socket(sock, ec) == true);
+  assert(!ec);
+
+  assert(is_socket(sock) == true);
+
+  assert(is_regular_file(sock) == false);
+  assert(is_directory(sock) == false);
+  assert(exists(sock) == true);
 #endif
 }
 
-static void test_is_socket_fails()
-{
-    scoped_test_env env;
+static void test_is_socket_fails() {
+  scoped_test_env env;
 #ifdef _WIN32
-    // Windows doesn't support setting perms::none to trigger failures
-    // reading directories; test using a special inaccessible directory
-    // instead.
-    const path p = GetWindowsInaccessibleDir();
-    if (p.empty())
-        return;
+  // Windows doesn't support setting perms::none to trigger failures
+  // reading directories; test using a special inaccessible directory
+  // instead.
+  const path p = GetWindowsInaccessibleDir();
+  if (p.empty())
+    return;
 #else
-    const path dir = env.create_dir("dir");
-    const path p = env.create_file("dir/file", 42);
-    permissions(dir, perms::none);
+  const path dir = env.create_dir("dir");
+  const path p   = env.create_file("dir/file", 42);
+  permissions(dir, perms::none);
 #endif
 
-    std::error_code ec;
-    assert(is_socket(p, ec) == false);
-    assert(ec);
+  std::error_code ec;
+  assert(is_socket(p, ec) == false);
+  assert(ec);
 
-    TEST_THROWS_TYPE(filesystem_error, is_socket(p));
+  TEST_THROWS_TYPE(filesystem_error, is_socket(p));
 }
 
 int main(int, char**) {
-    signature_test();
-    is_socket_status_test();
-    test_exist_not_found();
-    test_is_socket_for_real_socket();
-    test_is_socket_fails();
+  signature_test();
+  is_socket_status_test();
+  test_exist_not_found();
+  test_is_socket_for_real_socket();
+  test_is_socket_fails();
 
-    return 0;
+  return 0;
 }
diff --git a/libcxx/test/support/filesystem_test_helper.h b/libcxx/test/support/filesystem_test_helper.h
index 45fdafeaf4b2f..324d4aa65bd27 100644
--- a/libcxx/test/support/filesystem_test_helper.h
+++ b/libcxx/test/support/filesystem_test_helper.h
@@ -5,19 +5,19 @@
 
 #include <sys/stat.h> // for stat, mkdir, mkfifo
 #ifndef _WIN32
-#include <unistd.h> // for ftruncate, link, symlink, getcwd, chdir
-#include <sys/statvfs.h>
+#  include <unistd.h> // for ftruncate, link, symlink, getcwd, chdir
+#  include <sys/statvfs.h>
 #else
-#include <io.h>
-#include <direct.h>
+#  include <io.h>
+#  include <direct.h>
 // winsock2.h must be included before windows.h to avoid clashing with the
 // older winsock.h that windows.h would otherwise pull in.
-#include <winsock2.h> // for AF_UNIX sockets
-#include <afunix.h>
-#include <windows.h> // for CreateSymbolicLink, CreateHardLink
-#if defined(_MSC_VER)
-#pragma comment(lib, "ws2_32")
-#endif
+#  include <winsock2.h> // for AF_UNIX sockets
+#  include <afunix.h>
+#  include <windows.h> // for CreateSymbolicLink, CreateHardLink
+#  if defined(_MSC_VER)
+#    pragma comment(lib, "ws2_32")
+#  endif
 #endif
 
 #include <cassert>
@@ -38,8 +38,8 @@
 
 // For creating socket files
 #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32)
-# include <sys/socket.h>
-# include <sys/un.h>
+#  include <sys/socket.h>
+#  include <sys/un.h>
 #endif
 // On Windows the AF_UNIX socket headers (winsock2.h/afunix.h) are included
 // above, before windows.h.
@@ -47,293 +47,278 @@ namespace fs = std::filesystem;
 
 namespace utils {
 #ifdef _WIN32
-    inline int mkdir(const char* path, int mode) { (void)mode; return ::_mkdir(path); }
-    inline int symlink(const char* oldname, const char* newname, bool is_dir) {
-        DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
-        if (CreateSymbolicLinkA(newname, oldname,
-                                flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
-          return 0;
-        if (GetLastError() != ERROR_INVALID_PARAMETER)
-          return 1;
-        return !CreateSymbolicLinkA(newname, oldname, flags);
-    }
-    inline int link(const char *oldname, const char* newname) {
-        return !CreateHardLinkA(newname, oldname, NULL);
-    }
-    inline int setenv(const char *var, const char *val, int overwrite) {
-        (void)overwrite;
-        return ::_putenv((std::string(var) + "=" + std::string(val)).c_str());
-    }
-    inline int unsetenv(const char *var) {
-        return ::_putenv((std::string(var) + "=").c_str());
-    }
-    inline bool space(std::string path, std::uintmax_t &capacity,
-                      std::uintmax_t &free, std::uintmax_t &avail) {
-        ULARGE_INTEGER FreeBytesAvailableToCaller, TotalNumberOfBytes,
-                       TotalNumberOfFreeBytes;
-        if (!GetDiskFreeSpaceExA(path.c_str(), &FreeBytesAvailableToCaller,
-                                 &TotalNumberOfBytes, &TotalNumberOfFreeBytes))
-          return false;
-        capacity = TotalNumberOfBytes.QuadPart;
-        free = TotalNumberOfFreeBytes.QuadPart;
-        avail = FreeBytesAvailableToCaller.QuadPart;
-        assert(capacity > 0);
-        assert(free > 0);
-        assert(avail > 0);
-        return true;
-    }
+inline int mkdir(const char* path, int mode) {
+  (void)mode;
+  return ::_mkdir(path);
+}
+inline int symlink(const char* oldname, const char* newname, bool is_dir) {
+  DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
+  if (CreateSymbolicLinkA(newname, oldname, flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
+    return 0;
+  if (GetLastError() != ERROR_INVALID_PARAMETER)
+    return 1;
+  return !CreateSymbolicLinkA(newname, oldname, flags);
+}
+inline int link(const char* oldname, const char* newname) { return !CreateHardLinkA(newname, oldname, NULL); }
+inline int setenv(const char* var, const char* val, int overwrite) {
+  (void)overwrite;
+  return ::_putenv((std::string(var) + "=" + std::string(val)).c_str());
+}
+inline int unsetenv(const char* var) { return ::_putenv((std::string(var) + "=").c_str()); }
+inline bool space(std::string path, std::uintmax_t& capacity, std::uintmax_t& free, std::uintmax_t& avail) {
+  ULARGE_INTEGER FreeBytesAvailableToCaller, TotalNumberOfBytes, TotalNumberOfFreeBytes;
+  if (!GetDiskFreeSpaceExA(path.c_str(), &FreeBytesAvailableToCaller, &TotalNumberOfBytes, &TotalNumberOfFreeBytes))
+    return false;
+  capacity = TotalNumberOfBytes.QuadPart;
+  free     = TotalNumberOfFreeBytes.QuadPart;
+  avail    = FreeBytesAvailableToCaller.QuadPart;
+  assert(capacity > 0);
+  assert(free > 0);
+  assert(avail > 0);
+  return true;
+}
 #else
-    using ::mkdir;
-    inline int symlink(const char* oldname, const char* newname, bool is_dir) { (void)is_dir; return ::symlink(oldname, newname); }
-    using ::link;
-    using ::setenv;
-    using ::unsetenv;
-    inline bool space(std::string path, std::uintmax_t &capacity,
-                      std::uintmax_t &free, std::uintmax_t &avail) {
-        struct statvfs expect;
-        if (::statvfs(path.c_str(), &expect) == -1)
-          return false;
-        assert(expect.f_bavail > 0);
-        assert(expect.f_bfree > 0);
-        assert(expect.f_bsize > 0);
-        assert(expect.f_blocks > 0);
-        assert(expect.f_frsize > 0);
-        auto do_mult = [&](std::uintmax_t val) {
-            std::uintmax_t fsize = expect.f_frsize;
-            std::uintmax_t new_val = val * fsize;
-            assert(new_val / fsize == val); // Test for overflow
-            return new_val;
-        };
-        capacity = do_mult(expect.f_blocks);
-        free = do_mult(expect.f_bfree);
-        avail = do_mult(expect.f_bavail);
-        return true;
-    }
+using ::mkdir;
+inline int symlink(const char* oldname, const char* newname, bool is_dir) {
+  (void)is_dir;
+  return ::symlink(oldname, newname);
+}
+using ::link;
+using ::setenv;
+using ::unsetenv;
+inline bool space(std::string path, std::uintmax_t& capacity, std::uintmax_t& free, std::uintmax_t& avail) {
+  struct statvfs expect;
+  if (::statvfs(path.c_str(), &expect) == -1)
+    return false;
+  assert(expect.f_bavail > 0);
+  assert(expect.f_bfree > 0);
+  assert(expect.f_bsize > 0);
+  assert(expect.f_blocks > 0);
+  assert(expect.f_frsize > 0);
+  auto do_mult = [&](std::uintmax_t val) {
+    std::uintmax_t fsize   = expect.f_frsize;
+    std::uintmax_t new_val = val * fsize;
+    assert(new_val / fsize == val); // Test for overflow
+    return new_val;
+  };
+  capacity = do_mult(expect.f_blocks);
+  free     = do_mult(expect.f_bfree);
+  avail    = do_mult(expect.f_bavail);
+  return true;
+}
 #endif
 
-    // N.B. libc might define some of the foo[64] identifiers using macros from
-    // foo64 -> foo or vice versa.
+// N.B. libc might define some of the foo[64] identifiers using macros from
+// foo64 -> foo or vice versa.
 #if defined(_WIN32)
-    using off64_t = std::int64_t;
+using off64_t = std::int64_t;
 #elif defined(__MVS__) || defined(__LP64__)
-    using off64_t = ::off_t;
+using off64_t = ::off_t;
 #else
-    using ::off64_t;
+using ::off64_t;
 #endif
 
-    inline FILE* fopen64(const char* pathname, const char* mode) {
-        // Bionic does not distinguish between fopen and fopen64, but fopen64
-        // wasn't added until API 24.
+inline FILE* fopen64(const char* pathname, const char* mode) {
+  // Bionic does not distinguish between fopen and fopen64, but fopen64
+  // wasn't added until API 24.
 #if defined(_WIN32) || defined(__MVS__) || defined(__LP64__) || defined(__BIONIC__)
-        return ::fopen(pathname, mode);
+  return ::fopen(pathname, mode);
 #else
-        return ::fopen64(pathname, mode);
+  return ::fopen64(pathname, mode);
 #endif
-    }
+}
 
-    inline int ftruncate64(int fd, off64_t length) {
+inline int ftruncate64(int fd, off64_t length) {
 #if defined(_WIN32)
-        // _chsize_s sets errno on failure and also returns the error number.
-        return ::_chsize_s(fd, length) ? -1 : 0;
+  // _chsize_s sets errno on failure and also returns the error number.
+  return ::_chsize_s(fd, length) ? -1 : 0;
 #elif defined(__MVS__) || defined(__LP64__)
-        return ::ftruncate(fd, length);
+  return ::ftruncate(fd, length);
 #else
-        return ::ftruncate64(fd, length);
+  return ::ftruncate64(fd, length);
 #endif
-    }
+}
 
-    inline std::string getcwd() {
-        // Assume that path lengths are not greater than this.
-        // This should be fine for testing purposes.
-        char buf[4096];
-        char* ret = ::getcwd(buf, sizeof(buf));
-        assert(ret && "getcwd failed");
-        return std::string(ret);
-    }
+inline std::string getcwd() {
+  // Assume that path lengths are not greater than this.
+  // This should be fine for testing purposes.
+  char buf[4096];
+  char* ret = ::getcwd(buf, sizeof(buf));
+  assert(ret && "getcwd failed");
+  return std::string(ret);
+}
 
-    inline bool exists(std::string const& path) {
-        struct ::stat tmp;
-        return ::stat(path.c_str(), &tmp) == 0;
-    }
+inline bool exists(std::string const& path) {
+  struct ::stat tmp;
+  return ::stat(path.c_str(), &tmp) == 0;
+}
 } // namespace utils
 
-struct scoped_test_env
-{
-    scoped_test_env() : test_root(available_cwd_path()) {
+struct scoped_test_env {
+  scoped_test_env()
+      : test_root(available_cwd_path())
+  {
 #ifdef _WIN32
-        // Windows mkdir can create multiple recursive directories
-        // if needed.
-        std::string cmd = "mkdir " + test_root.string();
+    // Windows mkdir can create multiple recursive directories
+    // if needed.
+    std::string cmd = "mkdir " + test_root.string();
 #else
-        std::string cmd = "mkdir -p " + test_root.string();
+    std::string cmd = "mkdir -p " + test_root.string();
 #endif
-        int ret = std::system(cmd.c_str());
-        assert(ret == 0);
-
-        // Ensure that the root_path is fully resolved, i.e. it contains no
-        // symlinks. The filesystem tests depend on that. We do this after
-        // creating the root_path, because `fs::canonical` requires the
-        // path to exist.
-        test_root = fs::canonical(test_root);
-    }
+    int ret = std::system(cmd.c_str());
+    assert(ret == 0);
+
+    // Ensure that the root_path is fully resolved, i.e. it contains no
+    // symlinks. The filesystem tests depend on that. We do this after
+    // creating the root_path, because `fs::canonical` requires the
+    // path to exist.
+    test_root = fs::canonical(test_root);
+  }
 
-    ~scoped_test_env() {
+  ~scoped_test_env() {
 #ifdef _WIN32
-        std::string cmd = "rmdir /s /q " + test_root.string();
-        int ret = std::system(cmd.c_str());
-        assert(ret == 0);
+    std::string cmd = "rmdir /s /q " + test_root.string();
+    int ret         = std::system(cmd.c_str());
+    assert(ret == 0);
 #else
-#if defined(__MVS__)
-        // The behaviour of chmod -R on z/OS prevents recursive
-        // permission change for directories that do not have read permission.
-        std::string cmd = "find  " + test_root.string() + " -exec chmod 777 {} \\;";
-#else
-        std::string cmd = "chmod -R 777 " + test_root.string();
-#endif // defined(__MVS__)
-        int ret = std::system(cmd.c_str());
+#  if defined(__MVS__)
+    // The behaviour of chmod -R on z/OS prevents recursive
+    // permission change for directories that do not have read permission.
+    std::string cmd = "find  " + test_root.string() + " -exec chmod 777 {} \\;";
+#  else
+    std::string cmd = "chmod -R 777 " + test_root.string();
+#  endif // defined(__MVS__)
+    int ret = std::system(cmd.c_str());
 #  if !defined(_AIX) && !defined(__ANDROID__)
-        // On AIX the chmod command will return non-zero when trying to set
-        // the permissions on a directory that contains a bad symlink. This triggers
-        // the assert, despite being able to delete everything with the following
-        // `rm -r` command.
-        //
-        // Android's chmod was buggy in old OSs, but skipping this assert is
-        // sufficient to ensure that the `rm -rf` succeeds for almost all tests:
-        //  - Android L: chmod aborts after one error
-        //  - Android L and M: chmod -R tries to set permissions of a symlink
-        //    target.
-        // LIBCXX-ANDROID-FIXME: Other fixes to consider: place a toybox chmod
-        // onto old devices, re-enable this assert for devices running Android N
-        // and up, rewrite this chmod+rm in C or C++.
-        assert(ret == 0);
+    // On AIX the chmod command will return non-zero when trying to set
+    // the permissions on a directory that contains a bad symlink. This triggers
+    // the assert, despite being able to delete everything with the following
+    // `rm -r` command.
+    //
+    // Android's chmod was buggy in old OSs, but skipping this assert is
+    // sufficient to ensure that the `rm -rf` succeeds for almost all tests:
+    //  - Android L: chmod aborts after one error
+    //  - Android L and M: chmod -R tries to set permissions of a symlink
+    //    target.
+    // LIBCXX-ANDROID-FIXME: Other fixes to consider: place a toybox chmod
+    // onto old devices, re-enable this assert for devices running Android N
+    // and up, rewrite this chmod+rm in C or C++.
+    assert(ret == 0);
 #  endif
 
-        cmd = "rm -rf " + test_root.string();
-        ret = std::system(cmd.c_str());
-        assert(ret == 0);
+    cmd = "rm -rf " + test_root.string();
+    ret = std::system(cmd.c_str());
+    assert(ret == 0);
 #endif
-    }
+  }
+
+  scoped_test_env(scoped_test_env const&)            = delete;
+  scoped_test_env& operator=(scoped_test_env const&) = delete;
 
-    scoped_test_env(scoped_test_env const &) = delete;
-    scoped_test_env & operator=(scoped_test_env const &) = delete;
-
-    fs::path make_env_path(std::string p) { return sanitize_path(p); }
-
-    std::string sanitize_path(std::string raw) {
-        assert(raw.find("..") == std::string::npos);
-        std::string root = test_root.string();
-        if (root.compare(0, root.size(), raw, 0, root.size()) != 0) {
-            assert(raw.front() != '\\');
-            fs::path tmp(test_root);
-            tmp /= raw;
-            return tmp.string();
-        }
-        return raw;
+  fs::path make_env_path(std::string p) { return sanitize_path(p); }
+
+  std::string sanitize_path(std::string raw) {
+    assert(raw.find("..") == std::string::npos);
+    std::string root = test_root.string();
+    if (root.compare(0, root.size(), raw, 0, root.size()) != 0) {
+      assert(raw.front() != '\\');
+      fs::path tmp(test_root);
+      tmp /= raw;
+      return tmp.string();
     }
+    return raw;
+  }
 
-    // Purposefully using a size potentially larger than off_t here so we can
-    // test the behavior of libc++fs when it is built with _FILE_OFFSET_BITS=64
-    // but the caller is not (std::filesystem also uses uintmax_t rather than
-    // off_t). On a 32-bit system this allows us to create a file larger than
-    // 2GB.
-    std::string create_file(fs::path filename_path, std::uintmax_t size = 0) {
-        std::string filename = sanitize_path(filename_path.string());
-
-        if (size >
-            static_cast<typename std::make_unsigned<utils::off64_t>::type>(
-                std::numeric_limits<utils::off64_t>::max())) {
-            std::fprintf(stderr, "create_file(%s, %ju) too large\n",
-                         filename.c_str(), size);
-            std::abort();
-        }
+  // Purposefully using a size potentially larger than off_t here so we can
+  // test the behavior of libc++fs when it is built with _FILE_OFFSET_BITS=64
+  // but the caller is not (std::filesystem also uses uintmax_t rather than
+  // off_t). On a 32-bit system this allows us to create a file larger than
+  // 2GB.
+  std::string create_file(fs::path filename_path, std::uintmax_t size = 0) {
+    std::string filename = sanitize_path(filename_path.string());
+
+    if (size >
+        static_cast<typename std::make_unsigned<utils::off64_t>::type>(std::numeric_limits<utils::off64_t>::max())) {
+      std::fprintf(stderr, "create_file(%s, %ju) too large\n", filename.c_str(), size);
+      std::abort();
+    }
 
 #if defined(_WIN32) || defined(__MVS__)
 #  define FOPEN_CLOEXEC_FLAG ""
 #else
 #  define FOPEN_CLOEXEC_FLAG "e"
 #endif
-        FILE* file = utils::fopen64(filename.c_str(), "w" FOPEN_CLOEXEC_FLAG);
-        if (file == nullptr) {
-            std::fprintf(stderr, "fopen %s failed: %s\n", filename.c_str(),
-                         std::strerror(errno));
-            std::abort();
-        }
-
-        if (utils::ftruncate64(
-                fileno(file), static_cast<utils::off64_t>(size)) == -1) {
-            std::fprintf(stderr, "ftruncate %s %ju failed: %s\n", filename.c_str(),
-                         size, std::strerror(errno));
-            std::fclose(file);
-            std::abort();
-        }
-
-        std::fclose(file);
-        return filename;
+    FILE* file = utils::fopen64(filename.c_str(), "w" FOPEN_CLOEXEC_FLAG);
+    if (file == nullptr) {
+      std::fprintf(stderr, "fopen %s failed: %s\n", filename.c_str(), std::strerror(errno));
+      std::abort();
     }
 
-    std::string create_dir(fs::path filename_path) {
-        std::string filename = filename_path.string();
-        filename = sanitize_path(std::move(filename));
-        int ret = utils::mkdir(filename.c_str(), 0777); // rwxrwxrwx mode
-        assert(ret == 0);
-        return filename;
+    if (utils::ftruncate64(fileno(file), static_cast<utils::off64_t>(size)) == -1) {
+      std::fprintf(stderr, "ftruncate %s %ju failed: %s\n", filename.c_str(), size, std::strerror(errno));
+      std::fclose(file);
+      std::abort();
     }
 
-    std::string create_file_dir_symlink(fs::path source_path,
-                                        fs::path to_path,
-                                        bool sanitize_source = true,
-                                        bool is_dir = false) {
-        std::string source = source_path.string();
-        std::string to = to_path.string();
-        if (sanitize_source)
-            source = sanitize_path(std::move(source));
-        to = sanitize_path(std::move(to));
-        int ret = utils::symlink(source.c_str(), to.c_str(), is_dir);
-        assert(ret == 0);
-        return to;
-    }
+    std::fclose(file);
+    return filename;
+  }
 
-    std::string create_symlink(fs::path source_path,
-                               fs::path to_path,
-                               bool sanitize_source = true) {
-        return create_file_dir_symlink(source_path, to_path, sanitize_source,
-                                       false);
-    }
+  std::string create_dir(fs::path filename_path) {
+    std::string filename = filename_path.string();
+    filename             = sanitize_path(std::move(filename));
+    int ret              = utils::mkdir(filename.c_str(), 0777); // rwxrwxrwx mode
+    assert(ret == 0);
+    return filename;
+  }
 
-    std::string create_directory_symlink(fs::path source_path,
-                                         fs::path to_path,
-                                         bool sanitize_source = true) {
-        return create_file_dir_symlink(source_path, to_path, sanitize_source,
-                                       true);
-    }
+  std::string
+  create_file_dir_symlink(fs::path source_path, fs::path to_path, bool sanitize_source = true, bool is_dir = false) {
+    std::string source = source_path.string();
+    std::string to     = to_path.string();
+    if (sanitize_source)
+      source = sanitize_path(std::move(source));
+    to      = sanitize_path(std::move(to));
+    int ret = utils::symlink(source.c_str(), to.c_str(), is_dir);
+    assert(ret == 0);
+    return to;
+  }
 
-    std::string create_hardlink(fs::path source_path, fs::path to_path) {
-        std::string source = source_path.string();
-        std::string to = to_path.string();
-        source = sanitize_path(std::move(source));
-        to = sanitize_path(std::move(to));
-        int ret = utils::link(source.c_str(), to.c_str());
-        assert(ret == 0);
-        return to;
-    }
+  std::string create_symlink(fs::path source_path, fs::path to_path, bool sanitize_source = true) {
+    return create_file_dir_symlink(source_path, to_path, sanitize_source, false);
+  }
+
+  std::string create_directory_symlink(fs::path source_path, fs::path to_path, bool sanitize_source = true) {
+    return create_file_dir_symlink(source_path, to_path, sanitize_source, true);
+  }
+
+  std::string create_hardlink(fs::path source_path, fs::path to_path) {
+    std::string source = source_path.string();
+    std::string to     = to_path.string();
+    source             = sanitize_path(std::move(source));
+    to                 = sanitize_path(std::move(to));
+    int ret            = utils::link(source.c_str(), to.c_str());
+    assert(ret == 0);
+    return to;
+  }
 
 #ifndef _WIN32
-    std::string create_fifo(std::string file) {
-        file = sanitize_path(std::move(file));
-        int ret = ::mkfifo(file.c_str(), 0666); // rw-rw-rw- mode
-        assert(ret == 0);
-        return file;
-    }
+  std::string create_fifo(std::string file) {
+    file    = sanitize_path(std::move(file));
+    int ret = ::mkfifo(file.c_str(), 0666); // rw-rw-rw- mode
+    assert(ret == 0);
+    return file;
+  }
 #endif
 
   // Some platforms doesn't support socket files so we shouldn't even
   // allow tests to call this unguarded.
 #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32)
-    std::string create_socket(std::string file) {
-      file = sanitize_path(std::move(file));
+  std::string create_socket(std::string file) {
+    file = sanitize_path(std::move(file));
 
-      ::sockaddr_un address;
-      address.sun_family = AF_UNIX;
+    ::sockaddr_un address;
+    address.sun_family = AF_UNIX;
 
 // If file.size() is too big, try to create a file directly inside
 // /tmp to make sure file path is short enough.
@@ -351,59 +336,59 @@ struct scoped_test_env
     return file;
   }
 #elif defined(_WIN32)
-    // Windows supports AF_UNIX (Unix domain) sockets, which materialize as a
-    // reparse-point file on disk. Winsock must be initialized before use; this
-    // is done once per process by WinsockInit below.
-    std::string create_socket(std::string file) {
-      static const int wsaInit = WinsockInit();
-      (void)wsaInit;
-
-      file = sanitize_path(std::move(file));
-
-      ::sockaddr_un address;
-      address.sun_family = AF_UNIX;
-      assert(file.size() < sizeof(address.sun_path));
-      std::memcpy(address.sun_path, file.c_str(), file.size() + 1);
-
-      SOCKET fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
-      assert(fd != INVALID_SOCKET);
-      assert(::bind(fd, reinterpret_cast<::sockaddr*>(&address), sizeof(address)) == 0);
-      // Intentionally leave the socket open: the file exists on disk as long as
-      // the socket lives, which is sufficient for the duration of the test.
-      return file;
-    }
+  // Windows supports AF_UNIX (Unix domain) sockets, which materialize as a
+  // reparse-point file on disk. Winsock must be initialized before use; this
+  // is done once per process by WinsockInit below.
+  std::string create_socket(std::string file) {
+    static const int wsaInit = WinsockInit();
+    (void)wsaInit;
+
+    file = sanitize_path(std::move(file));
+
+    ::sockaddr_un address;
+    address.sun_family = AF_UNIX;
+    assert(file.size() < sizeof(address.sun_path));
+    std::memcpy(address.sun_path, file.c_str(), file.size() + 1);
+
+    SOCKET fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
+    assert(fd != INVALID_SOCKET);
+    assert(::bind(fd, reinterpret_cast<::sockaddr*>(&address), sizeof(address)) == 0);
+    // Intentionally leave the socket open: the file exists on disk as long as
+    // the socket lives, which is sufficient for the duration of the test.
+    return file;
+  }
 
 private:
-    static int WinsockInit() {
-      WSADATA wsaData;
-      return ::WSAStartup(MAKEWORD(2, 2), &wsaData);
-    }
+  static int WinsockInit() {
+    WSADATA wsaData;
+    return ::WSAStartup(MAKEWORD(2, 2), &wsaData);
+  }
 
 public:
 #endif
 
-    fs::path test_root;
+  fs::path test_root;
 
 private:
-    // This could potentially introduce a filesystem race if multiple
-    // scoped_test_envs were created concurrently in the same test (hence
-    // sharing the same cwd). However, it is fairly unlikely to happen as
-    // we generally don't use scoped_test_env from multiple threads, so
-    // this is deemed acceptable.
-    // The cwd.filename() itself isn't unique across all tests in the suite,
-    // so start the numbering from a hash of the full cwd, to avoid
-    // different tests interfering with each other.
-    static inline fs::path available_cwd_path() {
-        fs::path const cwd = utils::getcwd();
-        fs::path const tmp = fs::temp_directory_path();
-        std::string base = cwd.filename().string();
-        std::size_t i = std::hash<std::string>()(cwd.string());
-        fs::path p = tmp / (base + "-static_env." + std::to_string(i));
-        while (utils::exists(p.string())) {
-            p = tmp / (base + "-static_env." + std::to_string(++i));
-        }
-        return p;
+  // This could potentially introduce a filesystem race if multiple
+  // scoped_test_envs were created concurrently in the same test (hence
+  // sharing the same cwd). However, it is fairly unlikely to happen as
+  // we generally don't use scoped_test_env from multiple threads, so
+  // this is deemed acceptable.
+  // The cwd.filename() itself isn't unique across all tests in the suite,
+  // so start the numbering from a hash of the full cwd, to avoid
+  // different tests interfering with each other.
+  static inline fs::path available_cwd_path() {
+    fs::path const cwd = utils::getcwd();
+    fs::path const tmp = fs::temp_directory_path();
+    std::string base   = cwd.filename().string();
+    std::size_t i      = std::hash<std::string>()(cwd.string());
+    fs::path p         = tmp / (base + "-static_env." + std::to_string(i));
+    while (utils::exists(p.string())) {
+      p = tmp / (base + "-static_env." + std::to_string(++i));
     }
+    return p;
+  }
 };
 
 /// This class generates the following tree:
@@ -425,104 +410,92 @@ struct scoped_test_env
 ///     `-- symlink_to_empty_file -> empty_file
 ///
 class static_test_env {
-    scoped_test_env env_;
+  scoped_test_env env_;
+
 public:
-    static_test_env() {
-        env_.create_symlink("dne", "bad_symlink", false);
-        env_.create_dir("dir1");
-        env_.create_dir("dir1/dir2");
-        env_.create_file("dir1/dir2/afile3");
-        env_.create_dir("dir1/dir2/dir3");
-        env_.create_file("dir1/dir2/dir3/file5");
-        env_.create_file("dir1/dir2/file4");
-        env_.create_directory_symlink("dir3", "dir1/dir2/symlink_to_dir3", false);
-        env_.create_file("dir1/file1");
-        env_.create_file("dir1/file2", 42);
-        env_.create_file("empty_file");
-        env_.create_file("non_empty_file", 42);
-        env_.create_directory_symlink("dir1", "symlink_to_dir", false);
-        env_.create_symlink("empty_file", "symlink_to_empty_file", false);
-    }
+  static_test_env() {
+    env_.create_symlink("dne", "bad_symlink", false);
+    env_.create_dir("dir1");
+    env_.create_dir("dir1/dir2");
+    env_.create_file("dir1/dir2/afile3");
+    env_.create_dir("dir1/dir2/dir3");
+    env_.create_file("dir1/dir2/dir3/file5");
+    env_.create_file("dir1/dir2/file4");
+    env_.create_directory_symlink("dir3", "dir1/dir2/symlink_to_dir3", false);
+    env_.create_file("dir1/file1");
+    env_.create_file("dir1/file2", 42);
+    env_.create_file("empty_file");
+    env_.create_file("non_empty_file", 42);
+    env_.create_directory_symlink("dir1", "symlink_to_dir", false);
+    env_.create_symlink("empty_file", "symlink_to_empty_file", false);
+  }
 
-    const fs::path Root = env_.test_root;
+  const fs::path Root = env_.test_root;
 
-    fs::path makePath(fs::path const& p) const {
-        // env_path is expected not to contain symlinks.
-        fs::path const& env_path = Root;
-        return env_path / p;
-    }
+  fs::path makePath(fs::path const& p) const {
+    // env_path is expected not to contain symlinks.
+    fs::path const& env_path = Root;
+    return env_path / p;
+  }
 
-    const std::vector<fs::path> TestFileList = {
-        makePath("empty_file"),
-        makePath("non_empty_file"),
-        makePath("dir1/file1"),
-        makePath("dir1/file2")
-    };
-
-    const std::vector<fs::path> TestDirList = {
-        makePath("dir1"),
-        makePath("dir1/dir2"),
-        makePath("dir1/dir2/dir3")
-    };
-
-    const fs::path File          = TestFileList[0];
-    const fs::path Dir           = TestDirList[0];
-    const fs::path Dir2          = TestDirList[1];
-    const fs::path Dir3          = TestDirList[2];
-    const fs::path SymlinkToFile = makePath("symlink_to_empty_file");
-    const fs::path SymlinkToDir  = makePath("symlink_to_dir");
-    const fs::path BadSymlink    = makePath("bad_symlink");
-    const fs::path DNE           = makePath("DNE");
-    const fs::path EmptyFile     = TestFileList[0];
-    const fs::path NonEmptyFile  = TestFileList[1];
-    const fs::path CharFile      = "/dev/null"; // Hopefully this exists
-
-    const std::vector<fs::path> DirIterationList = {
-        makePath("dir1/dir2"),
-        makePath("dir1/file1"),
-        makePath("dir1/file2")
-    };
-
-    const std::vector<fs::path> DirIterationListDepth1 = {
-        makePath("dir1/dir2/afile3"),
-        makePath("dir1/dir2/dir3"),
-        makePath("dir1/dir2/symlink_to_dir3"),
-        makePath("dir1/dir2/file4"),
-    };
-
-    const std::vector<fs::path> RecDirIterationList = {
-        makePath("dir1/dir2"),
-        makePath("dir1/file1"),
-        makePath("dir1/file2"),
-        makePath("dir1/dir2/afile3"),
-        makePath("dir1/dir2/dir3"),
-        makePath("dir1/dir2/symlink_to_dir3"),
-        makePath("dir1/dir2/file4"),
-        makePath("dir1/dir2/dir3/file5")
-    };
-
-    const std::vector<fs::path> RecDirFollowSymlinksIterationList = {
-        makePath("dir1/dir2"),
-        makePath("dir1/file1"),
-        makePath("dir1/file2"),
-        makePath("dir1/dir2/afile3"),
-        makePath("dir1/dir2/dir3"),
-        makePath("dir1/dir2/file4"),
-        makePath("dir1/dir2/dir3/file5"),
-        makePath("dir1/dir2/symlink_to_dir3"),
-        makePath("dir1/dir2/symlink_to_dir3/file5"),
-    };
+  const std::vector<fs::path> TestFileList = {
+      makePath("empty_file"), makePath("non_empty_file"), makePath("dir1/file1"), makePath("dir1/file2")};
+
+  const std::vector<fs::path> TestDirList = {makePath("dir1"), makePath("dir1/dir2"), makePath("dir1/dir2/dir3")};
+
+  const fs::path File          = TestFileList[0];
+  const fs::path Dir           = TestDirList[0];
+  const fs::path Dir2          = TestDirList[1];
+  const fs::path Dir3          = TestDirList[2];
+  const fs::path SymlinkToFile = makePath("symlink_to_empty_file");
+  const fs::path SymlinkToDir  = makePath("symlink_to_dir");
+  const fs::path BadSymlink    = makePath("bad_symlink");
+  const fs::path DNE           = makePath("DNE");
+  const fs::path EmptyFile     = TestFileList[0];
+  const fs::path NonEmptyFile  = TestFileList[1];
+  const fs::path CharFile      = "/dev/null"; // Hopefully this exists
+
+  const std::vector<fs::path> DirIterationList = {
+      makePath("dir1/dir2"), makePath("dir1/file1"), makePath("dir1/file2")};
+
+  const std::vector<fs::path> DirIterationListDepth1 = {
+      makePath("dir1/dir2/afile3"),
+      makePath("dir1/dir2/dir3"),
+      makePath("dir1/dir2/symlink_to_dir3"),
+      makePath("dir1/dir2/file4"),
+  };
+
+  const std::vector<fs::path> RecDirIterationList = {
+      makePath("dir1/dir2"), makePath("dir1/file1"), makePath("dir1/file2"), makePath("dir1/dir2/afile3"),
+      makePath("dir1/dir2/dir3"),
+      makePath("dir1/dir2/symlink_to_dir3"),
+      makePath("dir1/dir2/file4"),
+      makePath("dir1/dir2/dir3/file5")};
+
+  const std::vector<fs::path> RecDirFollowSymlinksIterationList = {
+      makePath("dir1/dir2"),
+      makePath("dir1/file1"),
+      makePath("dir1/file2"),
+      makePath("dir1/dir2/afile3"),
+      makePath("dir1/dir2/dir3"),
+      makePath("dir1/dir2/file4"),
+      makePath("dir1/dir2/dir3/file5"),
+      makePath("dir1/dir2/symlink_to_dir3"),
+      makePath("dir1/dir2/symlink_to_dir3/file5"),
+  };
 };
 
 struct CWDGuard {
   std::string oldCwd_;
-  CWDGuard() : oldCwd_(utils::getcwd()) { }
+  CWDGuard()
+      : oldCwd_(utils::getcwd())
+  {}
   ~CWDGuard() {
     int ret = ::chdir(oldCwd_.c_str());
     assert(ret == 0 && "chdir failed");
   }
 
-  CWDGuard(CWDGuard const&) = delete;
+  CWDGuard(CWDGuard const&)            = delete;
   CWDGuard& operator=(CWDGuard const&) = delete;
 };
 
@@ -549,8 +522,7 @@ inline std::error_code GetTestEC(unsigned Idx = 0) {
   return std::make_error_code(GetErrc());
 }
 
-inline bool ErrorIsImp(const std::error_code& ec,
-                       std::vector<std::errc> const& errors) {
+inline bool ErrorIsImp(const std::error_code& ec, std::vector<std::errc> const& errors) {
   std::error_condition cond = ec.default_error_condition();
   for (auto errc : errors) {
     if (cond.value() == static_cast<int>(errc))
@@ -567,16 +539,17 @@ inline bool ErrorIs(const std::error_code& ec, std::errc First, ErrcT... Rest) {
 
 // Provide our own Sleep routine since std::this_thread::sleep_for is not
 // available in single-threaded mode.
-template <class Dur> void SleepFor(Dur dur) {
-    using namespace std::chrono;
+template <class Dur>
+void SleepFor(Dur dur) {
+  using namespace std::chrono;
 #if !_LIBCPP_HAS_MONOTONIC_CLOCK
-    using Clock = system_clock;
+  using Clock = system_clock;
 #else
-    using Clock = steady_clock;
+  using Clock = steady_clock;
 #endif
-    const auto wake_time = Clock::now() + dur;
-    while (Clock::now() < wake_time)
-        ;
+  const auto wake_time = Clock::now() + dur;
+  while (Clock::now() < wake_time)
+    ;
 }
 
 inline fs::perms NormalizeExpectedPerms(fs::perms P) {
@@ -588,8 +561,7 @@ inline fs::perms NormalizeExpectedPerms(fs::perms P) {
   // all users.
   P |= fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read;
   P |= fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec;
-  fs::perms Write =
-      fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write;
+  fs::perms Write = fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write;
   if ((P & Write) != fs::perms::none)
     P |= Write;
 #endif
@@ -604,19 +576,29 @@ struct ExceptionChecker {
   const char* func_name;
   std::string opt_message;
 
-  explicit ExceptionChecker(std::errc first_err, const char* fun_name,
+  explicit ExceptionChecker(std::errc first_err, const char* fun_name, std::string opt_msg = {})
+      : expected_err{first_err},
+        num_paths(0),
+        func_name(fun_name),
+        opt_message(opt_msg)
+  {}
+  explicit ExceptionChecker(fs::path p, std::errc first_err, const char* fun_name, std::string opt_msg = {})
+      : expected_err(first_err),
+        expected_path1(p),
+        num_paths(1),
+        func_name(fun_name),
+        opt_message(opt_msg)
+  {}
+
+  explicit ExceptionChecker(fs::path p1, fs::path p2, std::errc first_err, const char* fun_name,
                             std::string opt_msg = {})
-      : expected_err{first_err}, num_paths(0), func_name(fun_name),
-        opt_message(opt_msg) {}
-  explicit ExceptionChecker(fs::path p, std::errc first_err,
-                            const char* fun_name, std::string opt_msg = {})
-      : expected_err(first_err), expected_path1(p), num_paths(1),
-        func_name(fun_name), opt_message(opt_msg) {}
-
-  explicit ExceptionChecker(fs::path p1, fs::path p2, std::errc first_err,
-                            const char* fun_name, std::string opt_msg = {})
-      : expected_err(first_err), expected_path1(p1), expected_path2(p2),
-        num_paths(2), func_name(fun_name), opt_message(opt_msg) {}
+      : expected_err(first_err),
+        expected_path1(p1),
+        expected_path2(p2),
+        num_paths(2),
+        func_name(fun_name),
+        opt_message(opt_msg)
+  {}
 
   void operator()(fs::filesystem_error const& Err) {
     assert(ErrorIsImp(Err.code(), {expected_err}));
@@ -636,23 +618,17 @@ struct ExceptionChecker {
     if (!opt_message.empty()) {
       additional_msg = opt_message + ": ";
     }
-    auto transform_path = [](const fs::path& p) {
-      return "\"" + p.string() + "\"";
-    };
-    std::string format = [&]() -> std::string {
+    auto transform_path = [](const fs::path& p) { return "\"" + p.string() + "\""; };
+    std::string format  = [&]() -> std::string {
       switch (num_paths) {
       case 0:
-        return format_string("filesystem error: in %s: %s%s", func_name,
-                             additional_msg, message);
+        return format_string("filesystem error: in %s: %s%s", func_name, additional_msg, message);
       case 1:
-        return format_string("filesystem error: in %s: %s%s [%s]", func_name,
-                             additional_msg, message,
+        return format_string("filesystem error: in %s: %s%s [%s]", func_name, additional_msg, message,
                              transform_path(expected_path1).c_str());
       case 2:
-        return format_string("filesystem error: in %s: %s%s [%s] [%s]",
-                             func_name, additional_msg, message,
-                             transform_path(expected_path1).c_str(),
-                             transform_path(expected_path2).c_str());
+        return format_string("filesystem error: in %s: %s%s [%s] [%s]", func_name, additional_msg, message,
+                             transform_path(expected_path1).c_str(), transform_path(expected_path2).c_str());
       default:
         TEST_FAIL("unexpected case");
         return "";
@@ -666,9 +642,8 @@ struct ExceptionChecker {
     }
   }
 
-  ExceptionChecker(ExceptionChecker const&) = delete;
+  ExceptionChecker(ExceptionChecker const&)            = delete;
   ExceptionChecker& operator=(ExceptionChecker const&) = delete;
-
 };
 
 inline fs::path GetWindowsInaccessibleDir() {
@@ -677,28 +652,31 @@ inline fs::path GetWindowsInaccessibleDir() {
   const fs::path dir("C:\\System Volume Information");
   std::error_code ec;
   const fs::path root("C:\\");
-  for (const auto &ent : fs::directory_iterator(root, ec)) {
+  for (const auto& ent : fs::directory_iterator(root, ec)) {
     if (ent != dir)
       continue;
     // Basic sanity checks on the directory_entry
     if (!ent.exists() || !ent.is_directory()) {
-      std::fprintf(stderr, "The expected inaccessible directory \"%s\" was found "
-                           "but doesn't behave as expected, skipping tests "
-                           "regarding it\n", dir.string().c_str());
+      std::fprintf(stderr,
+                   "The expected inaccessible directory \"%s\" was found "
+                   "but doesn't behave as expected, skipping tests "
+                   "regarding it\n", dir.string().c_str());
       return fs::path();
     }
     // Check that it indeed is inaccessible as expected
     (void)fs::exists(ent, ec);
     if (!ec) {
-      std::fprintf(stderr, "The expected inaccessible directory \"%s\" was found "
-                           "but seems to be accessible, skipping tests "
-                           "regarding it\n", dir.string().c_str());
+      std::fprintf(stderr,
+                   "The expected inaccessible directory \"%s\" was found "
+                   "but seems to be accessible, skipping tests "
+                   "regarding it\n", dir.string().c_str());
       return fs::path();
     }
     return ent;
   }
-  std::fprintf(stderr, "No inaccessible directory \"%s\" found, skipping tests "
-                       "regarding it\n", dir.string().c_str());
+  std::fprintf(stderr,
+               "No inaccessible directory \"%s\" found, skipping tests "
+               "regarding it\n", dir.string().c_str());
   return fs::path();
 }
 

>From 5e049da86c4354b042863579767ce62dbe045ad0 Mon Sep 17 00:00:00 2001
From: Yuriy Chernyshov <thegeorg at yandex-team.com>
Date: Fri, 31 Jul 2026 13:21:31 +0300
Subject: [PATCH 3/4] Deslop

---
 libcxx/src/filesystem/posix_compat.h | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h
index ed1149cd13ba1..5e4cbbdd39434 100644
--- a/libcxx/src/filesystem/posix_compat.h
+++ b/libcxx/src/filesystem/posix_compat.h
@@ -209,13 +209,18 @@ inline int stat_file(const wchar_t* path, StatT* buf, DWORD flags) {
   // back to opening the reparse point itself so that we can still report its
   // attributes and type. This matches the behavior of the system
   // stat()/os.stat() on such files.
+  //
+  // We only need this fallback when following was requested (otherwise the open
+  // above already used FILE_FLAG_OPEN_REPARSE_POINT). We can't tell a symlink
+  // (which must be followed) apart from a non-followable reparse point without
+  // the reparse tag, and GetFileAttributesW does not report it, so simply retry
+  // with the reparse-point flag; for a regular file/symlink whose target exists
+  // the first attempt already succeeded, so this second open only happens for
+  // paths the follow-open couldn't handle.
   if (!(flags & FILE_FLAG_OPEN_REPARSE_POINT)) {
-    DWORD attributes = GetFileAttributesW(path);
-    if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
-      WinHandle hr(path, FILE_READ_ATTRIBUTES, flags | FILE_FLAG_OPEN_REPARSE_POINT);
-      if (hr)
-        return stat_handle(hr, buf);
-    }
+    WinHandle hr(path, FILE_READ_ATTRIBUTES, flags | FILE_FLAG_OPEN_REPARSE_POINT);
+    if (hr)
+      return stat_handle(hr, buf);
   }
   return -1;
 }

>From 20232cccd6fcb529b9cab1620b858c88de79d74b Mon Sep 17 00:00:00 2001
From: Yuriy Chernyshov <thegeorg at yandex-team.com>
Date: Fri, 31 Jul 2026 18:58:11 +0300
Subject: [PATCH 4/4] Better layout

---
 libcxx/src/filesystem/posix_compat.h | 86 ++++++++++++++--------------
 1 file changed, 43 insertions(+), 43 deletions(-)

diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h
index 5e4cbbdd39434..e920668cba814 100644
--- a/libcxx/src/filesystem/posix_compat.h
+++ b/libcxx/src/filesystem/posix_compat.h
@@ -132,8 +132,14 @@ namespace detail {
 class WinHandle {
 public:
   WinHandle(const wchar_t* p, DWORD access, DWORD flags) {
-    h = CreateFileW(p, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
-                    FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr);
+    h = CreateFileW(
+        p,
+        access,
+        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+        nullptr,
+        OPEN_EXISTING,
+        FILE_FLAG_BACKUP_SEMANTICS | flags,
+        nullptr);
   }
   ~WinHandle() {
     if (h != INVALID_HANDLE_VALUE)
@@ -161,7 +167,6 @@ inline int stat_handle(HANDLE h, StatT* buf) {
   } else {
     buf->st_mode |= _S_IFREG;
   }
-  bool is_af_unix_socket = false;
   if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
     FILE_ATTRIBUTE_TAG_INFO tag;
     if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag, sizeof(tag)))
@@ -169,65 +174,60 @@ inline int stat_handle(HANDLE h, StatT* buf) {
     if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK) {
       buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFLNK;
     } else if (tag.ReparseTag == IO_REPARSE_TAG_AF_UNIX) {
-      buf->st_mode      = (buf->st_mode & ~_S_IFMT) | _S_IFSOCK;
-      is_af_unix_socket = true;
+      buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFSOCK;
+      // AF_UNIX (Unix domain) socket files don't support the information
+      // queries below; report what we already have (mode/timestamps) instead
+      // of failing outright, matching the behavior of the system
+      // stat()/os.stat().
+      return 0;
     }
   }
   FILE_STANDARD_INFO standard;
-  if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, sizeof(standard))) {
-    // AF_UNIX (Unix domain) socket files don't support all of the information
-    // queries below; report what we already have (mode/timestamps) instead of
-    // failing outright, matching the behavior of the system stat()/os.stat().
-    if (is_af_unix_socket)
-      return 0;
+  if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, sizeof(standard)))
     return -1;
-  }
   buf->st_nlink = standard.NumberOfLinks;
   buf->st_size  = standard.EndOfFile.QuadPart;
   BY_HANDLE_FILE_INFORMATION info;
-  if (!GetFileInformationByHandle(h, &info)) {
-    if (is_af_unix_socket)
-      return 0;
+  if (!GetFileInformationByHandle(h, &info))
     return -1;
-  }
   buf->st_dev = info.dwVolumeSerialNumber;
   memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4);
   memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4);
   return 0;
 }
 
-inline int stat_file(const wchar_t* path, StatT* buf, DWORD flags) {
-  {
-    WinHandle h(path, FILE_READ_ATTRIBUTES, flags);
-    if (h && stat_handle(h, buf) == 0)
-      return 0;
-  }
-  // Some reparse points, such as AF_UNIX (Unix domain) socket files, can't be
-  // opened/queried by following the reparse point (there is no target to
-  // follow). CreateFileW may fail with e.g. ERROR_CANT_ACCESS_FILE, or the
-  // subsequent GetFileInformationByHandle* calls may fail. In that case, fall
-  // back to opening the reparse point itself so that we can still report its
-  // attributes and type. This matches the behavior of the system
-  // stat()/os.stat() on such files.
+inline int stat_file(const wchar_t* path, StatT* buf, bool follow_symlinks) {
+  // Win32 has no open mode that means "follow symlinks, but open any other
+  // reparse point (e.g. an AF_UNIX socket) as itself": omitting
+  // FILE_FLAG_OPEN_REPARSE_POINT follows *every* reparse point, which fails for
+  // ones that have no target to follow, while passing it opens *every* reparse
+  // point as itself, including symlinks we do want to follow.
   //
-  // We only need this fallback when following was requested (otherwise the open
-  // above already used FILE_FLAG_OPEN_REPARSE_POINT). We can't tell a symlink
-  // (which must be followed) apart from a non-followable reparse point without
-  // the reparse tag, and GetFileAttributesW does not report it, so simply retry
-  // with the reparse-point flag; for a regular file/symlink whose target exists
-  // the first attempt already succeeded, so this second open only happens for
-  // paths the follow-open couldn't handle.
-  if (!(flags & FILE_FLAG_OPEN_REPARSE_POINT)) {
-    WinHandle hr(path, FILE_READ_ATTRIBUTES, flags | FILE_FLAG_OPEN_REPARSE_POINT);
-    if (hr)
-      return stat_handle(hr, buf);
+  // So we always open the reparse point itself first. This succeeds for any
+  // existing object regardless of its reparse tag, and lets stat_handle
+  // determine the type from that tag. We then emulate symlink following
+  // ourselves below, only for the objects that actually are symlinks.
+  WinHandle h(path, FILE_READ_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT);
+  if (!h || stat_handle(h, buf) != 0)
+    return -1;
+  // For lstat, or for anything that isn't a symlink, we're already done. Only a
+  // genuine symlink needs to be resolved to its target for stat; do so by
+  // reopening the path without FILE_FLAG_OPEN_REPARSE_POINT so the OS follows
+  // it, and stat'ing the target handle. A failure here (dangling/cyclic/
+  // inaccessible target) is a real error, matching the behavior of the system
+  // stat()/os.stat().
+  if (follow_symlinks && S_ISLNK(buf->st_mode)) {
+    WinHandle ht(path, FILE_READ_ATTRIBUTES, 0);
+    if (!ht)
+      return -1;
+    return stat_handle(ht, buf);
   }
-  return -1;
+  return 0;
 }
 
-inline int stat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, 0); }
+inline int stat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, /*follow_symlinks=*/true); }
 
-inline int lstat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT); }
+inline int lstat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, /*follow_symlinks=*/false); }
 
 inline int fstat(int fd, StatT* buf) {
   HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));



More information about the libcxx-commits mailing list