diff --git a/autotest/UnixSocket/UnixSocket.cpp b/autotest/UnixSocket/UnixSocket.cpp index eedb320fe..c40b8611d 100644 --- a/autotest/UnixSocket/UnixSocket.cpp +++ b/autotest/UnixSocket/UnixSocket.cpp @@ -2,50 +2,63 @@ using namespace Upp; +String GetSocketPath() +{ + String temp; +#ifdef PLATFORM_WIN32 + temp = GetEnv("TEMP"); +#else + temp = GetTempPath(); +#endif + return AppendFileName(temp, "upp-unixsocket.socket"); +} + CONSOLE_APP_MAIN { -#ifdef PLATFORM_POSIX - StdLogSetup(LOG_COUT|LOG_FILE); - String path = Format("/tmp/upp-unixsocket-test-%d", getpid()); - - Socket server, client; - - // Test server listen - if(!server.ListenFileSystem(path)) { - LOG("Server listen failed: " << server.GetErrorDesc()); - Exit(1); - } - - // Test client connect - if(!client.ConnectFileSystem(path)) { - LOG("Client connect failed: " << client.GetErrorDesc()); - Exit(1); - } - - // Test data exchange - String test_data = "Hello, world!"; - client.Put(test_data + "\n"); - - Socket accepted; - if(!accepted.Accept(server)) { - LOG("Accept failed: " << accepted.GetErrorDesc()); - Exit(1); - } - - String received = accepted.GetLine(); - DUMP(received); - - ASSERT(received == test_data); - - // Test peer PID (on supported platforms) - int pid = accepted.GetPeerPid(); - DUMP(pid); - if(pid != -1) - ASSERT(pid == getpid()); // Should be our own process in this test + String path = GetSocketPath(); - LOG("=========== OK"); + try { + Socket server, client; -#endif -} \ No newline at end of file + DeleteFile(path); // "unlink" existing FS socket if possible + + // Test server listen + if(!server.ListenFileSystem(path, 5, false)) { + throw Exc("Server listen failed: " << server.GetErrorDesc()); + } + + // Test client connect + if(!client.ConnectFileSystem(path)) { + throw Exc("Client connect failed: " << client.GetErrorDesc()); + } + + // Test data exchange + String test_data = "Hello, world!"; + client.Put(test_data + "\n"); + + Socket accepted; + if(!accepted.Accept(server)) { + throw Exc("Accept failed: " << accepted.GetErrorDesc()); + } + + String received = accepted.GetLine(); + DUMP(received); + + ASSERT(received == test_data); + + // Test peer PID (on supported platforms) + int pid = accepted.GetPeerPid(); + DUMP(pid); + if(pid != -1) + ASSERT(pid == getpid()); // Should be our own process in this test + + LOG("=========== OK"); + } + catch(const Exc& e) + { + LOG(e); + SetExitCode(1); + } +} diff --git a/reference/UnixSocketClient/UnixSocketClient.cpp b/reference/UnixSocketClient/UnixSocketClient.cpp index 2e7cd8b5e..f168ef1f3 100644 --- a/reference/UnixSocketClient/UnixSocketClient.cpp +++ b/reference/UnixSocketClient/UnixSocketClient.cpp @@ -3,26 +3,31 @@ using namespace Upp; // Start reference/UnixSocketServer before starting this program +String GetSocketPath() +{ + String temp; +#ifdef PLATFORM_WIN32 + temp = GetEnv("TEMP"); +#else + temp = GetTempPath(); +#endif + return AppendFileName(temp, "upp-unixsocket.socket"); +} + +String Request(const String r) +{ + Socket s; + if(!s.ConnectFileSystem(GetSocketPath())) { + Cout() << "Unable to connect to server!\n"; + SetExitCode(1); + return String(); + } + s.Put(r + '\n'); + return s.GetLine(); +} CONSOLE_APP_MAIN { -#ifdef PLATFORM_POSIX - auto Request = [](const String& r) - { - Socket s; - if(!s.ConnectFileSystem("/tmp/upp-unixsocket.sock")) { - Cout() << "Unable to connect to server!\n"; - SetExitCode(1); - return String(); - } - s.Put(r + '\n'); - return s.GetLine(); - }; - Cout() << Request("time") << '\n'; Cout() << Request("33") << '\n'; -#else - Cout() << "This example requires a POSIX compliant operating system...\r\n" - SetExitCode(1); -#endif } diff --git a/reference/UnixSocketServer/UnixSocketServer.cpp b/reference/UnixSocketServer/UnixSocketServer.cpp index 6c41ebe97..1d9f9eb68 100644 --- a/reference/UnixSocketServer/UnixSocketServer.cpp +++ b/reference/UnixSocketServer/UnixSocketServer.cpp @@ -2,13 +2,23 @@ using namespace Upp; +String GetSocketPath() +{ + String temp; +#ifdef PLATFORM_WIN32 + temp = GetEnv("TEMP"); +#else + temp = GetTempPath(); +#endif + return AppendFileName(temp, "upp-unixsocket.socket"); +} + CONSOLE_APP_MAIN { -#ifdef PLATFORM_POSIX - const String& path = "/tmp/upp-unixsocket.sock"; - Socket server; - if(!server.ListenFileSystem(path, 5)) { + String path = GetSocketPath(); + DeleteFile(path); // "unlink" existing file system socket + if(!server.ListenFileSystem(path, 5, false)) { // Reuse option is not available on Windows Cout() << "Unable to initialize server socket!\n"; SetExitCode(1); return; @@ -26,8 +36,4 @@ CONSOLE_APP_MAIN s.Put("\n"); } } -#else - Cout() << "This example requires a POSIX compliant operating system...\r\n" - SetExitCode(1); -#endif } diff --git a/uppsrc/Core/Inet.h b/uppsrc/Core/Inet.h index c4bd9442d..37084eea6 100644 --- a/uppsrc/Core/Inet.h +++ b/uppsrc/Core/Inet.h @@ -171,11 +171,10 @@ class Socket : NoCopy { static int GetErrorCode(); static void Init(); - -#ifdef PLATFORM_POSIX // Unix domain socket support + + // Unix domain socket support bool NixConnect(const String& path, bool abstract); bool NixListen(const String& path, int n, bool reuse, bool abstract); -#endif Socket(const Socket&); @@ -217,13 +216,11 @@ public: void Close(); void Shutdown(); -#ifdef PLATFORM_POSIX int GetPeerPid() const; bool ConnectFileSystem(const String& path); bool ConnectAbstract(const String& path); bool ListenFileSystem(const String& path, int listen_count = 5, bool reuse = true); bool ListenAbstract(const String& path, int listen_count = 5, bool reuse = true); -#endif void NoDelay(); void Linger(int msecs); diff --git a/uppsrc/Core/Socket.cpp b/uppsrc/Core/Socket.cpp index 252bbcaf5..4ba0b653a 100644 --- a/uppsrc/Core/Socket.cpp +++ b/uppsrc/Core/Socket.cpp @@ -3,7 +3,11 @@ #ifdef PLATFORM_WIN32 #include #include +#ifdef AF_UNIX // Unix domain (AF_UNIX) socket support, Windows 10+ +#include #endif +#endif + #ifdef PLATFORM_POSIX #include @@ -984,40 +988,43 @@ void Socket::Clear() Reset(); } -#ifdef PLATFORM_POSIX - -static bool sSetUnixSockType(Socket& s, const String& path, sockaddr_un& addr, bool abstract) +static socklen_t sSetUnixSockType(Socket& s, const String& path, sockaddr_un& addr, bool abstract) { - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - +#ifdef AF_UNIX + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; #ifndef PLATFORM_LINUX - if(abstract) { - s.SetSockError("SetUnixSockType", - -1, "Abstract socket is not supported on this platform"); - return false; - } + if(abstract) { + s.SetSockError("SetUnixSockType", + -1, "Abstract socket is not supported on this platform"); + return 0; + } #endif + const int len = path.GetLength(); - if(abstract) { - addr.sun_path[0] = '\0'; - if(path.GetLength() > 0) { - ASSERT(path.GetLength() < sizeof(addr.sun_path) - 1); - strncpy(addr.sun_path + 1, ~path, sizeof(addr.sun_path) - 2); - return true; - } - } - else { - if(path.GetLength() > 0) { - ASSERT(path.GetLength() < sizeof(addr.sun_path)); - strncpy(addr.sun_path, ~path, sizeof(addr.sun_path) - 1); - addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; - return true; - } - } - s.SetSockError("SetUnixSockType", - -1, "Failed to set unix domain socket type"); - return false; + if(abstract) { + if(len <= 0 || len > int(sizeof(addr.sun_path) - 1)) { + s.SetSockError("SetUnixSockType", -1, "Abstract socket name too long"); + return 0; + } + addr.sun_path[0] = '\0'; + memcpy(addr.sun_path + 1, ~path, len); + return offsetof(sockaddr_un, sun_path) + 1 + len; + } + else { + if(len <= 0 || len >= int(sizeof(addr.sun_path))) { + s.SetSockError("SetUnixSockType", -1, "Unix socket path too long"); + return 0; + } + memcpy(addr.sun_path, ~path, len); + addr.sun_path[len] = '\0'; + return offsetof(sockaddr_un, sun_path) + len + 1; + } +#else + s.SetSockError("SetUnixSockType", + -1, "Unix domain socket is not supported on this platform"); +#endif + return 0; } int Socket::GetPeerPid() const @@ -1048,21 +1055,27 @@ bool Socket::NixConnect(const String& path, bool abstract) Init(); Reset(); +#ifdef AF_UNIX if(!Open(AF_UNIX, SOCK_STREAM, 0)) return false; struct sockaddr_un addr; - if(!sSetUnixSockType(*this, path, addr, abstract)) + socklen_t addrlen = 0; + if((addrlen = sSetUnixSockType(*this, path, addr, abstract)) == 0) return false; - if(connect(socket, (sockaddr *) &addr, sizeof(addr)) == 0 || - GetErrorCode() == EINPROGRESS || GetErrorCode() == EWOULDBLOCK) { + if(connect(socket, (sockaddr *) &addr, addrlen) == 0 || + findarg(GetErrorCode(), SOCKERR(EINPROGRESS), SOCKERR(EWOULDBLOCK)) >= 0) { mode = Socket::CONNECT; return true; } SetSockError("connect", -1, strerror(GetErrorCode())); Close(); +#else + SetSockError("NixConnect", + -1, "Unix domain socket is not supported on this platform"); +#endif return false; } @@ -1082,11 +1095,13 @@ bool Socket::NixListen(const String& path, int n, bool reuse, bool abstract) Init(); Reset(); +#ifdef AF_UNIX if(!Open(AF_UNIX, SOCK_STREAM, 0)) return false; struct sockaddr_un addr; - if(!sSetUnixSockType(*this, path, addr, abstract)) + socklen_t addrlen = 0; + if((addrlen = sSetUnixSockType(*this, path, addr, abstract)) == 0) return false; if(reuse) { @@ -1094,7 +1109,7 @@ bool Socket::NixListen(const String& path, int n, bool reuse, bool abstract) setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char *) &optval, sizeof(optval)); } - if(bind(socket, (const sockaddr *) &addr, sizeof(addr))) { + if(bind(socket, (const sockaddr *) &addr, addrlen)) { SetSockError(Format("bind(path=%s)", path)); return false; } @@ -1105,6 +1120,12 @@ bool Socket::NixListen(const String& path, int n, bool reuse, bool abstract) } return true; + +#else + SetSockError("NixListen", + -1, "Unix domain socket is not supported on this platform"); + return false; +#endif } bool Socket::ListenFileSystem(const String& path, int listen_count, bool reuse) @@ -1117,9 +1138,6 @@ bool Socket::ListenAbstract(const String& path, int listen_count, bool reuse) return NixListen(path, listen_count, reuse, true); } -#endif - - int SocketWaitEvent::Wait(int timeout) { FD_ZERO(read); diff --git a/uppsrc/Core/src.tpp/Socket_en-us.tpp b/uppsrc/Core/src.tpp/Socket_en-us.tpp index fbe5cd5bf..0479f567d 100644 --- a/uppsrc/Core/src.tpp/Socket_en-us.tpp +++ b/uppsrc/Core/src.tpp/Socket_en-us.tpp @@ -34,11 +34,10 @@ tring]_[* GetHostName]()&] [s4; &] [s5;:Socket`:`:GetPeerPid`(`)const: [@(0.0.255) int] [* GetPeerPid]() [@(0.0.255) const]&] -[s6; POSIX only&] [s2;%% Returns the process ID (pid) of the peer on success, `-1 on failure. On non`-blocking mode, make sure that socket is actually connected or accepted. This is only available on unix domain -(local) sockets.&] +(local) sockets and will fail with return code `-1 on Windows.&] [s3; &] [s4; &] [s5;:Socket`:`:GetDone`(`)const: [@(0.0.255) int]_[* GetDone]()_[@(0.0.255) const]&] @@ -120,8 +119,7 @@ pAddrInfo][@(0.0.255) `&]_[*@3 info])&] [s3;%% &] [s4; &] [s5;:Upp`:`:Socket`:`:ConnectFileSystem`(const String`&`): [@(0.0.255) bool] -[* ConnectFileSystem]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 path])&] -[s6;%% POSIX only&] +[* ConnectFileSystem]([@(0.0.255) const] String[@(0.0.255) `&] [@3 path])&] [s2;%% Connects socket to a Unix domain server bound at the given file system [%-*@3 path]. The path must exist on the file system. Returns true if connection is successful (blocking mode) or connection @@ -138,8 +136,8 @@ and does not correspond to a file system path. Returns true if connection is successful (blocking mode) or connection is in progress (non blocking mode). Abstract sockets exist only in kernel memory and disappear when processes exit. On non`-Linux -POSIX systems, this function will fail and set the socket into -error state. &] +systems, this function will fail and set the socket into error +state. &] [s3; &] [s4; &] [s5;:Socket`:`:WaitConnect`(`): [@(0.0.255) bool]_[* WaitConnect]()&] @@ -169,15 +167,23 @@ for ipv6`=`=true.&] [s3;%% &] [s4; &] [s5;:Upp`:`:Socket`:`:ListenFileSystem`(const String`&`,int`,bool`): [@(0.0.255) bool] -[* ListenFileSystem]([@(0.0.255) const ]String[@(0.0.255) `&] [*@3 path], -[@(0.0.255) int] [*@3 listen`_count] [@(0.0.255) `=] [@3 5], [@(0.0.255) bool] -[*@3 reuse] [@(0.0.255) `=] [@(0.0.255) true])&] -[s6; POSIX only&] +[* ListenFileSystem(][*@(0.0.255) const ][* String][*@(0.0.255) `&][* +][*@3 path][* , ][*@(0.0.255) int][* ][*@3 listen`_count][* ][*@(0.0.255) `=][* +][*@3 5][* , ][*@(0.0.255) bool][* ][*@3 reuse][* ][*@(0.0.255) `=][* ][*@(0.0.255) true][* )]&] [s2;%% Creates a Unix domain server socket bound to the given file system [%-*@3 path]. [%-*@3 listen`_count] specifies the maximum number of pending connections in the queue. [%-*@3 reuse] indicates whether the socket should allow reuse of the address if it already -exists. returns true if the listen is successful.&] +exists. returns true if the listen is successful. &] +[s2;%% &] +[s2;%% Notes:&] +[s2;i150;O0;%% [%-*@3 reuse] option is not supported on Windows and +if specified the listen will fail and set the socket into error +state. &] +[s2;i150;O0;%% Client code is responsible for the socket [%-*@3 path]’s +lifetime. For filesystem`-based Unix domain sockets, the pathname +must be unlinked explicitly after shutdown, as it is not removed +automatically when the socket is closed.&] [s3; &] [s4; &] [s5;:Upp`:`:Socket`:`:ListenAbstract`(const String`&`,int`,bool`): [@(0.0.255) bool] @@ -191,8 +197,8 @@ not correspond to a file system path). [%-*@3 listen`_count] specifies the maximum number of pending connections. [%-*@3 reuse] indicates whether the abstract socket name can be reused. Abstract sockets exist only in kernel memory and disappear when processes exit. -On non`-Linux POSIX systems, this method will fail and set the -socket into error state.&] +On non`-Linux systems, this method will fail and set the socket +into error state.&] [s3; &] [s4;%% &] [s5;:Socket`:`:Accept`(Socket`&`): [@(0.0.255) bool]_[* Accept]([_^topic`:`/`/Core`/src`/TcpSocket`$en`-us`#TcpSocket`:`:class^ S