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

via libcxx-commits libcxx-commits at lists.llvm.org
Fri Jul 31 02:28:51 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Yuriy Chernyshov (georgthegreat)

<details>
<summary>Changes</summary>

On Windows, `std::filesystem::is_socket()` (and any following-`status()` query such as `exists()`) throws `filesystem_error` when called on an AF_UNIX (Unix domain) socket file:

```
filesystem error: in posix_stat: failed to determine attributes for the
specified path: The file cannot be accessed by the system. ["daemon.socket"]
```

### Root cause

An AF_UNIX socket file is a reparse point (`IO_REPARSE_TAG_AF_UNIX`). The non-symlink-following `stat()` path opens it via `CreateFileW` *without* `FILE_FLAG_OPEN_REPARSE_POINT`, which tries to follow the reparse point. Since a socket has no target to follow, `CreateFileW`/`GetFileInformationByHandle*` fail with `ERROR_CANT_ACCESS_FILE`, and `create_file_status` reports a hard error instead of `file_type::socket`.

For comparison, CPython's `os.stat()` handles this correctly by opening with `FILE_FLAG_OPEN_REPARSE_POINT` and mapping `IO_REPARSE_TAG_AF_UNIX` to `S_IFSOCK`.

### Fix (`libcxx/src/filesystem/posix_compat.h`)

- Recognize the `IO_REPARSE_TAG_AF_UNIX` reparse tag as `_S_IFSOCK` in `stat_handle`, and tolerate the follow-up `FileStandardInfo`/`GetFileInformationByHandle` queries failing for such sockets.
- In `stat_file`, when the follow open (or stat) fails on a reparse point, retry with `FILE_FLAG_OPEN_REPARSE_POINT` so the socket file itself can be stat'd — matching `os.stat()`.

---
Full diff: https://github.com/llvm/llvm-project/pull/213233.diff


3 Files Affected:

- (modified) libcxx/src/filesystem/posix_compat.h (+44-8) 
- (modified) libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.is_socket/is_socket.pass.cpp (+24) 
- (modified) libcxx/test/support/filesystem_test_helper.h (+39) 


``````````diff
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;

``````````

</details>


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


More information about the libcxx-commits mailing list