Author: Charles Zablit Date: 2026-07-17T17:09:16+02:00 New Revision: d7aacfc940c3f1553973f1ebf7d11a6513fda1ed
URL: https://github.com/llvm/llvm-project/commit/d7aacfc940c3f1553973f1ebf7d11a6513fda1ed DIFF: https://github.com/llvm/llvm-project/commit/d7aacfc940c3f1553973f1ebf7d11a6513fda1ed.diff LOG: [lldb] Support Windows paths in AF_UNIX domain-socket URIs (#206985) On Windows an `AF_UNIX` socket path is a native filesystem path. Placed directly in a unix-connect:// URI it isn't a valid `host:port` authority, so `URI::Parse` confuses the drive letter colon as a port separator and connection setup fails. This patch converts the path in the [RFC 8089](https://datatracker.ietf.org/doc/html/rfc8089) file-URI form instead which parses as an ordinary URI with an empty host. DomainSocket converts between the native path and this URI form at its boundaries. Both are no-ops on POSIX: behavior is unchanged, and `URI::Parse` and the GDB-remote consumers are left untouched. rdar://180736036 Added: Modified: lldb/include/lldb/Host/common/DomainSocket.h lldb/source/Host/common/DomainSocket.cpp lldb/unittests/Host/SocketTest.cpp lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationServerLLGSTest.cpp lldb/unittests/Utility/UriParserTest.cpp Removed: ################################################################################ diff --git a/lldb/include/lldb/Host/common/DomainSocket.h b/lldb/include/lldb/Host/common/DomainSocket.h index 4d2b657cf88e4..65850e6777593 100644 --- a/lldb/include/lldb/Host/common/DomainSocket.h +++ b/lldb/include/lldb/Host/common/DomainSocket.h @@ -45,6 +45,20 @@ class DomainSocket : public Socket { static llvm::Expected<std::unique_ptr<DomainSocket>> FromBoundNativeSocket(NativeSocket sockfd, bool should_close); + /// Convert between a native filesystem path and the path component of a + /// domain-socket URI. This is a pure, content-driven string transformation + /// (independent of the build host) so that, for example, a Unix build of + /// LLDB remote debugging a Windows target can still interpret Windows paths. + /// A drive-letter path (e.g. "C:\\dir\\sock") is not a valid URI authority, + /// so it is carried in the RFC 8089 file-URI form "/C:/dir/sock" (leading + /// slash, forward slashes). A UNC path (e.g. "\\\\server\\share") only needs + /// its backslashes replaced to become the URI path "//server/share". Any + /// other path is already a valid URI path and is returned unchanged. + /// @{ + static std::string NativePathToURIPath(llvm::StringRef path); + static std::string URIPathToNativePath(llvm::StringRef path); + /// @} + protected: DomainSocket(SocketProtocol protocol); DomainSocket(SocketProtocol protocol, NativeSocket socket, bool should_close); diff --git a/lldb/source/Host/common/DomainSocket.cpp b/lldb/source/Host/common/DomainSocket.cpp index 988b8238a3cce..28ec1b7d25dae 100644 --- a/lldb/source/Host/common/DomainSocket.cpp +++ b/lldb/source/Host/common/DomainSocket.cpp @@ -12,10 +12,12 @@ #include <lldb/Host/linux/AbstractSocket.h> #endif +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Errno.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include <algorithm> #include <cstddef> #include <memory> @@ -32,6 +34,41 @@ using namespace lldb_private; static const int kDomain = AF_UNIX; static const int kType = SOCK_STREAM; +std::string DomainSocket::NativePathToURIPath(llvm::StringRef path) { + // A drive-letter path (e.g. "C:\\dir\\sock") is not a valid URI authority, so + // it is carried in the RFC 8089 URI form "/C:/dir/sock". + if (path.size() >= 2 && llvm::isAlpha(path[0]) && path[1] == ':') { + std::string uri_path = "/" + path.str(); + std::replace(uri_path.begin(), uri_path.end(), '\\', '/'); + return uri_path; + } + // A UNC path (e.g. "\\\\server\\share") is already anchored by its leading + // slashes. + if (path.starts_with("\\\\")) { + std::string uri_path = path.str(); + std::replace(uri_path.begin(), uri_path.end(), '\\', '/'); + return uri_path; + } + return path.str(); +} + +std::string DomainSocket::URIPathToNativePath(llvm::StringRef path) { + // Reverse of the drive-letter mapping: "/C:/dir/sock" -> "C:\\dir\\sock". + if (path.size() >= 3 && path[0] == '/' && llvm::isAlpha(path[1]) && + path[2] == ':') { + std::string native = path.drop_front().str(); + std::replace(native.begin(), native.end(), '/', '\\'); + return native; + } + // Reverse of the UNC mapping: "//server/share" -> "\\\\server\\share". + if (path.starts_with("//")) { + std::string native = path.str(); + std::replace(native.begin(), native.end(), '/', '\\'); + return native; + } + return path.str(); +} + static bool SetSockAddr(llvm::StringRef name, const size_t name_offset, sockaddr_un *saddr_un, socklen_t &saddr_un_len) { if (name.size() + name_offset > sizeof(saddr_un->sun_path)) @@ -79,9 +116,10 @@ DomainSocket::DomainSocket(SocketProtocol protocol, NativeSocket socket, } Status DomainSocket::Connect(llvm::StringRef name) { + std::string native_name = URIPathToNativePath(name); sockaddr_un saddr_un; socklen_t saddr_un_len; - if (!SetSockAddr(name, GetNameOffset(), &saddr_un, saddr_un_len)) + if (!SetSockAddr(native_name, GetNameOffset(), &saddr_un, saddr_un_len)) return Status::FromErrorString("Failed to set socket address"); Status error; @@ -97,12 +135,13 @@ Status DomainSocket::Connect(llvm::StringRef name) { } Status DomainSocket::Listen(llvm::StringRef name, int backlog) { + std::string native_name = URIPathToNativePath(name); sockaddr_un saddr_un; socklen_t saddr_un_len; - if (!SetSockAddr(name, GetNameOffset(), &saddr_un, saddr_un_len)) + if (!SetSockAddr(native_name, GetNameOffset(), &saddr_un, saddr_un_len)) return Status::FromErrorString("Failed to set socket address"); - DeleteSocketFile(name); + DeleteSocketFile(native_name); Status error; m_socket = CreateSocket(kDomain, kType, 0, error); @@ -175,9 +214,9 @@ std::string DomainSocket::GetRemoteConnectionURI() const { if (name.empty()) return name; - return llvm::formatv( - "{0}://{1}", - GetNameOffset() == 0 ? "unix-connect" : "unix-abstract-connect", name); + if (GetNameOffset() == 0) + return llvm::formatv("unix-connect://{0}", NativePathToURIPath(name)); + return llvm::formatv("unix-abstract-connect://{0}", name); } std::vector<std::string> DomainSocket::GetListeningConnectionURI() const { @@ -191,7 +230,8 @@ std::vector<std::string> DomainSocket::GetListeningConnectionURI() const { if (::getsockname(m_socket, (struct sockaddr *)&addr, &addr_len) != 0) return {}; - return {llvm::formatv("unix-connect://{0}", addr.sun_path)}; + return { + llvm::formatv("unix-connect://{0}", NativePathToURIPath(addr.sun_path))}; } llvm::Expected<std::unique_ptr<DomainSocket>> diff --git a/lldb/unittests/Host/SocketTest.cpp b/lldb/unittests/Host/SocketTest.cpp index 45dcce83ca0a7..cdc02e96f40b7 100644 --- a/lldb/unittests/Host/SocketTest.cpp +++ b/lldb/unittests/Host/SocketTest.cpp @@ -161,9 +161,12 @@ TEST_F(SocketTest, DomainListenGetListeningConnectionURI) { ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded()); ASSERT_TRUE(listen_socket_up->IsValid()); - ASSERT_THAT( - listen_socket_up->GetListeningConnectionURI(), - testing::ElementsAre(llvm::formatv("unix-connect://{0}", Path).str())); + std::string expected_uri = + llvm::formatv("unix-connect://{0}", + DomainSocket::NativePathToURIPath(Path)) + .str(); + ASSERT_THAT(listen_socket_up->GetListeningConnectionURI(), + testing::ElementsAre(expected_uri)); } TEST_F(SocketTest, DomainMainLoopAccept) { @@ -392,12 +395,36 @@ TEST_F(SocketTest, DomainGetConnectURI) { CreateDomainConnectedSockets(domain_path, &socket_a_up, &socket_b_up); std::string uri(socket_a_up->GetRemoteConnectionURI()); - EXPECT_EQ((URI{"unix-connect", "", std::nullopt, domain_path}), + std::string expected_path = DomainSocket::NativePathToURIPath(domain_path); + EXPECT_EQ((URI{"unix-connect", "", std::nullopt, expected_path}), URI::Parse(uri)); EXPECT_EQ(socket_b_up->GetRemoteConnectionURI(), ""); } +TEST_F(SocketTest, DomainSocketPathURIConversion) { + // Paths that are already valid URI paths (no drive letter) are unchanged. + EXPECT_EQ(DomainSocket::NativePathToURIPath("/tmp/foo"), "/tmp/foo"); + EXPECT_EQ(DomainSocket::URIPathToNativePath("/tmp/foo"), "/tmp/foo"); + + // A Windows drive-letter path round-trips through the RFC 8089 "/C:/..." + // URI form. + EXPECT_EQ(DomainSocket::NativePathToURIPath("C:\\dir\\sock"), "/C:/dir/sock"); + EXPECT_EQ(DomainSocket::URIPathToNativePath("/C:/dir/sock"), "C:\\dir\\sock"); + EXPECT_EQ(DomainSocket::URIPathToNativePath( + DomainSocket::NativePathToURIPath("C:\\dir\\sock")), + "C:\\dir\\sock"); + + // A Windows UNC path round-trips by swapping backslashes for forward slashes. + EXPECT_EQ(DomainSocket::NativePathToURIPath("\\\\server\\share\\sock"), + "//server/share/sock"); + EXPECT_EQ(DomainSocket::URIPathToNativePath("//server/share/sock"), + "\\\\server\\share\\sock"); + EXPECT_EQ(DomainSocket::URIPathToNativePath( + DomainSocket::NativePathToURIPath("\\\\server\\share\\sock")), + "\\\\server\\share\\sock"); +} + TEST_F(SocketTest, DomainSocketFromBoundNativeSocket) { if (!HostSupportsDomainSockets()) GTEST_SKIP() << "Domain sockets unavailable"; diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationServerLLGSTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationServerLLGSTest.cpp index b4704606e8692..9018cbc83f5d5 100644 --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationServerLLGSTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationServerLLGSTest.cpp @@ -31,6 +31,15 @@ TEST(GDBRemoteCommunicationServerLLGSTest, LLGSArgToURL) { EXPECT_EQ(LLGSArgToURL("unix-abstract://foo", false), "unix-abstract-accept://foo"); + // LLGS legacy listen URLs carrying a Windows drive-letter path (in RFC 8089 + // "/C:/..." form) should be converted, keeping the path intact. + EXPECT_EQ(LLGSArgToURL("unix:///C:/Users/a/foo", false), + "unix-accept:///C:/Users/a/foo"); + + // Same for a Windows UNC path (in "//server/share/..." form). + EXPECT_EQ(LLGSArgToURL("unix:////server/share/foo", false), + "unix-accept:////server/share/foo"); + // LLGS listen host:port pairs should be converted to listen:// EXPECT_EQ(LLGSArgToURL("127.0.0.1:1234", false), "listen://127.0.0.1:1234"); EXPECT_EQ(LLGSArgToURL("[::1]:1234", false), "listen://[::1]:1234"); diff --git a/lldb/unittests/Utility/UriParserTest.cpp b/lldb/unittests/Utility/UriParserTest.cpp index 9923d3a9af936..da3f5a978dcea 100644 --- a/lldb/unittests/Utility/UriParserTest.cpp +++ b/lldb/unittests/Utility/UriParserTest.cpp @@ -24,6 +24,16 @@ TEST(UriParserTest, LongPath) { URI::Parse("x://y/abc/def/xyz")); } +TEST(UriParserTest, WindowsDriveLetterPath) { + EXPECT_EQ((URI{"unix-connect", "", std::nullopt, "/C:/Users/a/sock"}), + URI::Parse("unix-connect:///C:/Users/a/sock")); +} + +TEST(UriParserTest, WindowsUNCPath) { + EXPECT_EQ((URI{"unix-connect", "", std::nullopt, "//server/share/sock"}), + URI::Parse("unix-connect:////server/share/sock")); +} + TEST(UriParserTest, TypicalPortPathIPv4) { EXPECT_EQ((URI{"connect", "192.168.100.132", 5432, "/"}), URI::Parse("connect://192.168.100.132:5432/")); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
