[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