From 25af8ae17dbfed72f43fea2986fc3da923eb58e0 Mon Sep 17 00:00:00 2001 From: Marc Gurevitx Date: Sat, 27 Apr 2024 17:09:59 +0300 Subject: [PATCH 1/6] Unix domain sockets (only Linux) --- MiniScript-cpp/src/ShellIntrinsics.cpp | 398 +++++++++++++++++++++++++ 1 file changed, 398 insertions(+) diff --git a/MiniScript-cpp/src/ShellIntrinsics.cpp b/MiniScript-cpp/src/ShellIntrinsics.cpp index 7e2c133..6b7ac9f 100644 --- a/MiniScript-cpp/src/ShellIntrinsics.cpp +++ b/MiniScript-cpp/src/ShellIntrinsics.cpp @@ -62,7 +62,11 @@ #define PATHSEP '/' #include #include + #include + #include + #include // for sockaddr_un #endif +#define UDS_DEFAULT_BACKLOG (20) extern "C" { // list of environment variables provided by C standard library: @@ -120,6 +124,100 @@ class RawDataHandleStorage : public RefCountedStorage { size_t dataSize; }; +// RefCountedStorage base class for uds.Connection and uds.Server storages (since they declare similar data). +class UdsBaseHandleStorage : public RefCountedStorage { +public: + // cleanup: Closes the socket. We will call it from uds.Server::close and uds.Connection::close, and also in destructors. + void cleanup() { + if (sockfd < 0) return; + close(sockfd); + sockfd = -1; + } + // unblock: Makes the socket nonblocking. Call it in constructors. + bool unblock() { + int flags = fcntl(sockfd, F_GETFL, 0); + flags |= O_NONBLOCK; + return fcntl(sockfd, F_SETFL, flags) == 0; + } + // isValid: Returns true if the socket is still open. + bool isValid() { + return initedOk and sockfd >= 0; + } + + bool initedOk; + int sockfd; + struct sockaddr_un addr; +}; + +// RefCountedStorage class to wrap a unix domain socket connection data. +class UdsConnectionHandleStorage : public UdsBaseHandleStorage { +public: + UdsConnectionHandleStorage(const char *sockPath) : initedOk(false) { + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) return; + if (!unblock()) return; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockPath, sizeof(addr.sun_path) - 1); + initedOk = true; + } + UdsConnectionHandleStorage(int sockfd, sockaddr_un addr) : initedOk(false), sockfd(sockfd), addr(addr) { + if (!unblock()) return; + initedOk = true; + } + virtual ~UdsConnectionHandleStorage() { cleanup(); } + bool attemptConnect() { + if (!isValid()) return false; + return connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == 0; + } + ssize_t receiveBuf(char *buf, size_t nBytes) { + if (!isValid()) return 0; + ssize_t nReceived = recv(sockfd, buf, nBytes, 0); + if (nReceived == 0) cleanup(); + return nReceived; + } + ssize_t sendBuf(void *buf, size_t nBytes) { + if (!isValid()) return 0; + return send(sockfd, buf, nBytes, 0); + } + + bool initedOk; + int sockfd; + struct sockaddr_un addr; +}; + +// RefCountedStorage class to wrap a unix domain socket server data. +class UdsServerHandleStorage : public UdsBaseHandleStorage { +public: + UdsServerHandleStorage(const char *sockPath, int backlog) : initedOk(false) { + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) return; + if (!unblock()) return; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockPath, sizeof(addr.sun_path) - 1); + unlink(addr.sun_path); + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) return; + if (listen(sockfd, backlog) < 0) return; + initedOk = true; + } + virtual ~UdsServerHandleStorage() { cleanup(); } + UdsConnectionHandleStorage *acceptConnection() { + if (!isValid()) return nullptr; + struct sockaddr_un peer; + socklen_t peerSize = sizeof(peer); + int cfd = accept(sockfd, (struct sockaddr *)&peer, &peerSize); + if (cfd < 0) return nullptr; + UdsConnectionHandleStorage *connStorage = new UdsConnectionHandleStorage(cfd, peer); + if (!connStorage->isValid()) return nullptr; + return connStorage; + } + + bool initedOk; + int sockfd; + struct sockaddr_un addr; +}; + // hidden (unnamed) intrinsics, only accessible via other methods (such as the File module) Intrinsic *i_getcwd = nullptr; Intrinsic *i_chdir = nullptr; @@ -166,6 +264,14 @@ Intrinsic *i_rawDataDouble = nullptr; Intrinsic *i_rawDataSetDouble = nullptr; Intrinsic *i_rawDataUtf8 = nullptr; Intrinsic *i_rawDataSetUtf8 = nullptr; +Intrinsic *i_udsConnect = nullptr; +Intrinsic *i_udsConnectionIsValid = nullptr; +Intrinsic *i_udsConnectionClose = nullptr; +Intrinsic *i_udsConnectionReceive = nullptr; +Intrinsic *i_udsConnectionSend = nullptr; +Intrinsic *i_udsCreateServer = nullptr; +Intrinsic *i_udsServerIsValid = nullptr; +Intrinsic *i_udsServerAccept = nullptr; // Copy a file. Return 0 on success, or some value < 0 on error. static int UnixishCopyFile(const char* source, const char* destination) { @@ -247,6 +353,8 @@ static String ExpandVariables(String path) { static ValueDict& FileHandleClass(); static ValueDict& RawDataType(); +static ValueDict& UdsConnectionType(); +static ValueDict& UdsServerType(); static IntrinsicResult intrinsic_input(Context *context, IntrinsicResult partialResult) { Value prompt = context->GetVar("prompt"); @@ -1288,6 +1396,215 @@ static IntrinsicResult intrinsic_exec(Context *context, IntrinsicResult partialR } #endif +// uds.* + +static IntrinsicResult intrinsic_udsConnect(Context *context, IntrinsicResult partialResult) { + if (partialResult.Done()) { + // This is the initial entry into `uds.connect`. Initialize the storage. + String sockPath = context->GetVar("sockPath").ToString(); + UdsConnectionHandleStorage *storage = new UdsConnectionHandleStorage(sockPath.c_str()); + if (!storage->isValid()) return IntrinsicResult::Null; + // Calculate the final time and end the current invokation. + double timeout = context->GetVar("timeout").DoubleValue(); + double finalTime = context->vm->RunTime() + timeout; + ValueDict data; + data.SetValue("wrapper", Value::NewHandle(storage)); + data.SetValue("finalTime", finalTime); + return IntrinsicResult(data, false); + } + ValueDict data = partialResult.Result().GetDict(); + // Try to connect to a listening server, if any. + Value connWrapper = data.Lookup("wrapper", Value::null); + UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; + if (storage->attemptConnect()) { + ValueDict instance; + instance.SetValue(Value::magicIsA, UdsConnectionType()); + instance.SetValue(_handle, connWrapper); + Value result(instance); + return IntrinsicResult(result); + } + // Check that the final time hasn't been reached yet. + double finalTime = data.Lookup("finalTime", Value::zero).DoubleValue(); + if (context->vm->RunTime() > finalTime) return IntrinsicResult::Null; + // No listening servers, no timeout. Continue waiting. + return IntrinsicResult(data, false); +} + +static IntrinsicResult intrinsic_udsConnectionIsValid(Context *context, IntrinsicResult partialResult) { + Value self = context->GetVar("self"); + Value connWrapper = self.Lookup(_handle); + if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) return IntrinsicResult(Value::Truth(false)); + UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; + return IntrinsicResult(Value::Truth(storage->isValid())); +} + +static IntrinsicResult intrinsic_udsConnectionClose(Context *context, IntrinsicResult partialResult) { + Value self = context->GetVar("self"); + Value connWrapper = self.Lookup(_handle); + if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) RuntimeException("bad UDS connection, no handle").raise(); + UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; + storage->cleanup(); + return IntrinsicResult::Null; +} + +static IntrinsicResult intrinsic_udsConnectionReceive(Context *context, IntrinsicResult partialResult) { + if (partialResult.Done()) { + // This is the initial entry into `uds.Connection::receive`. Check if `bytes` param makes sense. + long nBytes = context->GetVar("bytes").IntValue(); + if (nBytes == 0) RuntimeException("bytes parameter must be nonzero (positive or -1)").raise(); + // Check we're having a valid connection object. + Value self = context->GetVar("self"); + Value connWrapper = self.Lookup(_handle); + if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) RuntimeException("bad UDS connection, no handle").raise(); + UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; + if (!storage->isValid()) RuntimeException("bad UDS connection, perhaps closed socket").raise(); + // Calculate the final time and end the current invokation. + double timeout = context->GetVar("timeout").DoubleValue(); + double finalTime = context->vm->RunTime() + timeout; + // Initialize a RawData result. We do not need the whole RawData object rn, a simple handle is enough. + Value dataWrapper = Value::NewHandle(new RawDataHandleStorage()); + ValueDict data; + data.SetValue("wrapper", connWrapper); + data.SetValue("nBytes", nBytes); + data.SetValue("finalTime", finalTime); + data.SetValue("rawDataWrapper", dataWrapper); + return IntrinsicResult(data, false); + } + bool retNow = false; + ValueDict data = partialResult.Result().GetDict(); + // Load received data to buffer, if available. + long nBytes = data.Lookup("nBytes", -1.0).IntValue(); + Value connWrapper = data.Lookup("wrapper", Value::null); + UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; + Value dataWrapper = data.Lookup("rawDataWrapper", Value::null); + RawDataHandleStorage *dataStorage = (RawDataHandleStorage*)dataWrapper.data.ref; + char buf[1000]; + long nWanted = (nBytes < 0 or nBytes > sizeof(buf)) ? sizeof(buf) : nBytes; + long nReceived = storage->receiveBuf(buf, nWanted); + // If "received" exactly 0 bytes, the connection was closed on the other end. No matter how many bytes we received up till now, return immediately. + if (nReceived == 0) retNow = true; + if (nReceived > 0) { + size_t prevSize = dataStorage->dataSize; + dataStorage->resize(dataStorage->dataSize + nReceived); + if (dataStorage->dataSize == prevSize) { + // Realloc wasn't successfull... + if (dataStorage->dataSize == 0) return IntrinsicResult::Null; + retNow = true; + } else { + // Successfully resized. Copy received data into storage. + unsigned char *d = (unsigned char *)dataStorage->data; + d += prevSize; + memcpy((void *)d, buf, nReceived); + // Decrement nBytes + nBytes -= nReceived; + if (nBytes == 0) { + retNow = true; // we received exactly as many bytes as we wanted... + } else { + data.SetValue("nBytes", nBytes); + } + } + } else if (dataStorage->dataSize > 0) { + retNow = true; // we received nothing but there were previous data... + } + // Check that the final time hasn't been reached yet. + double finalTime = data.Lookup("finalTime", Value::zero).DoubleValue(); + if (context->vm->RunTime() > finalTime) { + if (dataStorage->dataSize == 0) return IntrinsicResult::Null; + retNow = true; + } + if (retNow) { + ValueDict rawData; + rawData.SetValue(Value::magicIsA, RawDataType()); + rawData.SetValue(_handle, dataWrapper); + Value result(rawData); + return IntrinsicResult(result); + } + // No data received, no timeout. Continue waiting. + return IntrinsicResult(data, false); +} + +static IntrinsicResult intrinsic_udsConnectionSend(Context *context, IntrinsicResult partialResult) { + Value self = context->GetVar("self"); + Value connWrapper = self.Lookup(_handle); + if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) RuntimeException("bad UDS connection, no handle").raise(); + UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; + if (!storage->isValid()) RuntimeException("bad UDS connection, perhaps closed socket").raise(); + Value rawData = context->GetVar("rawData"); + long nSent; + if (rawData.type == ValueType::String) { + String s = rawData.GetString(); + if (s.LengthB() == 0) RuntimeException("message length cannot be 0").raise(); + nSent = storage->sendBuf((void *)s.c_str(), s.LengthB()); + } else if (rawData.type == ValueType::Map) { + if (!rawData.IsA(RawDataType(), context->vm)) RuntimeException("message must be RawData or string").raise(); + Value dataWrapper = rawData.Lookup(_handle); + if (dataWrapper.IsNull() or dataWrapper.type != ValueType::Handle) RuntimeException("bad RawData, no handle or length 0").raise(); + RawDataHandleStorage *dataStorage = (RawDataHandleStorage*)dataWrapper.data.ref; + if (dataStorage->dataSize == 0) RuntimeException("message length cannot be 0").raise(); + nSent = storage->sendBuf((void *)dataStorage->data, dataStorage->dataSize); + } else { + RuntimeException("message must be RawData or string").raise(); + } + Value result(nSent); + return IntrinsicResult(result); +} + +static IntrinsicResult intrinsic_udsCreateServer(Context *context, IntrinsicResult partialResult) { + String sockPath = context->GetVar("sockPath").ToString(); + int32_t backlog = context->GetVar("backlog").IntValue(); + if (backlog <= 0) backlog = UDS_DEFAULT_BACKLOG; + UdsServerHandleStorage *storage = new UdsServerHandleStorage(sockPath.c_str(), backlog); + if (!storage->isValid()) return IntrinsicResult::Null; + ValueDict instance; + instance.SetValue(Value::magicIsA, UdsServerType()); + instance.SetValue(_handle, Value::NewHandle(storage)); + Value result(instance); + return IntrinsicResult(result); +} + +static IntrinsicResult intrinsic_udsServerIsValid(Context *context, IntrinsicResult partialResult) { + Value self = context->GetVar("self"); + Value serverWrapper = self.Lookup(_handle); + if (serverWrapper.IsNull() or serverWrapper.type != ValueType::Handle) return IntrinsicResult(Value::Truth(false)); + UdsServerHandleStorage *storage = (UdsServerHandleStorage*)serverWrapper.data.ref; + return IntrinsicResult(Value::Truth(storage->isValid())); +} + +static IntrinsicResult intrinsic_udsServerAccept(Context *context, IntrinsicResult partialResult) { + if (partialResult.Done()) { + // This is the initial entry into `uds.Server::accept`. Check we're having a valid server object. + Value self = context->GetVar("self"); + Value serverWrapper = self.Lookup(_handle); + if (serverWrapper.IsNull() or serverWrapper.type != ValueType::Handle) RuntimeException("bad UDS server, no handle").raise(); + UdsServerHandleStorage *storage = (UdsServerHandleStorage*)serverWrapper.data.ref; + if (!storage->isValid()) RuntimeException("bad UDS server, perhaps closed socket").raise(); + // Calculate the final time and end the current invokation. + double timeout = context->GetVar("timeout").DoubleValue(); + double finalTime = context->vm->RunTime() + timeout; + ValueDict data; + data.SetValue("wrapper", serverWrapper); + data.SetValue("finalTime", finalTime); + return IntrinsicResult(data, false); + } + ValueDict data = partialResult.Result().GetDict(); + // Accept an existing connection request, if any. + Value serverWrapper = data.Lookup("wrapper", Value::null); + UdsServerHandleStorage *storage = (UdsServerHandleStorage*)serverWrapper.data.ref; + UdsConnectionHandleStorage *connStorage = storage->acceptConnection(); + if (connStorage) { + ValueDict connection; + connection.SetValue(Value::magicIsA, UdsConnectionType()); + connection.SetValue(_handle, Value::NewHandle(connStorage)); + Value result(connection); + return IntrinsicResult(result); + } + // Check that the final time hasn't been reached yet. + double finalTime = data.Lookup("finalTime", Value::zero).DoubleValue(); + if (context->vm->RunTime() > finalTime) return IntrinsicResult::Null; + // No connection requests, no timeout. Continue waiting. + return IntrinsicResult(data, false); +} + static bool disallowAssignment(ValueDict& dict, Value key, Value value) { return true; } @@ -1369,6 +1686,42 @@ static IntrinsicResult intrinsic_RawData(Context *context, IntrinsicResult parti return IntrinsicResult(RawDataType()); } +static ValueDict& UdsConnectionType() { + static ValueDict result; + if (result.Count() == 0) { + result.SetValue("isValid", i_udsConnectionIsValid->GetFunc()); + result.SetValue("close", i_udsConnectionClose->GetFunc()); + result.SetValue("receive", i_udsConnectionReceive->GetFunc()); + result.SetValue("send", i_udsConnectionSend->GetFunc()); + } + + return result; +} + +static ValueDict& UdsServerType() { + static ValueDict result; + if (result.Count() == 0) { + result.SetValue("isValid", i_udsServerIsValid->GetFunc()); + result.SetValue("accept", i_udsServerAccept->GetFunc()); + } + + return result; +} + +static IntrinsicResult intrinsic_Uds(Context *context, IntrinsicResult partialResult) { + static ValueDict udsModule; + + if (udsModule.Count() == 0) { + udsModule.SetValue("Connection", UdsConnectionType()); + udsModule.SetValue("Server", UdsServerType()); + udsModule.SetValue("connect", i_udsConnect->GetFunc()); + udsModule.SetValue("createServer", i_udsCreateServer->GetFunc()); + udsModule.SetAssignOverride(disallowAssignment); + } + + return IntrinsicResult(udsModule); +} + static void setEnvVar(const char* key, const char* value) { #if WINDOWS _putenv_s(key, value); @@ -1761,4 +2114,49 @@ void AddShellIntrinsics() { // END RawData methods + // uds (unix domain sockets) + + f = Intrinsic::Create("uds"); + f->code = &intrinsic_Uds; + + i_udsConnect = Intrinsic::Create(""); + i_udsConnect->AddParam("sockPath"); + i_udsConnect->AddParam("timeout", 30); + i_udsConnect->code = &intrinsic_udsConnect; + + i_udsConnectionIsValid = Intrinsic::Create(""); + i_udsConnectionIsValid->AddParam("self"); + i_udsConnectionIsValid->code = &intrinsic_udsConnectionIsValid; + + i_udsConnectionClose = Intrinsic::Create(""); + i_udsConnectionClose->AddParam("self"); + i_udsConnectionClose->code = &intrinsic_udsConnectionClose; + + i_udsConnectionReceive = Intrinsic::Create(""); + i_udsConnectionReceive->AddParam("self"); + i_udsConnectionReceive->AddParam("bytes", -1); + i_udsConnectionReceive->AddParam("timeout", 30); + i_udsConnectionReceive->code = &intrinsic_udsConnectionReceive; + + i_udsConnectionSend = Intrinsic::Create(""); + i_udsConnectionSend->AddParam("self"); + i_udsConnectionSend->AddParam("rawData"); + i_udsConnectionSend->code = &intrinsic_udsConnectionSend; + + i_udsCreateServer = Intrinsic::Create(""); + i_udsCreateServer->AddParam("sockPath"); + i_udsCreateServer->AddParam("backlog", UDS_DEFAULT_BACKLOG); + i_udsCreateServer->code = &intrinsic_udsCreateServer; + + i_udsServerIsValid = Intrinsic::Create(""); + i_udsServerIsValid->AddParam("self"); + i_udsServerIsValid->code = &intrinsic_udsServerIsValid; + + i_udsServerAccept = Intrinsic::Create(""); + i_udsServerAccept->AddParam("self"); + i_udsServerAccept->AddParam("timeout", 30); + i_udsServerAccept->code = &intrinsic_udsServerAccept; + + // END uds + } From d6f9429b3292349487fedf143b2157125fa604b1 Mon Sep 17 00:00:00 2001 From: Marc Gurevitx Date: Sun, 28 Apr 2024 16:21:33 +0300 Subject: [PATCH 2/6] Unix domain sockets (Windows) --- MiniScript-cpp/src/ShellIntrinsics.cpp | 83 +++++++++++++++++++------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/MiniScript-cpp/src/ShellIntrinsics.cpp b/MiniScript-cpp/src/ShellIntrinsics.cpp index 6b7ac9f..1087f04 100644 --- a/MiniScript-cpp/src/ShellIntrinsics.cpp +++ b/MiniScript-cpp/src/ShellIntrinsics.cpp @@ -37,6 +37,7 @@ #include #if _WIN32 || _WIN64 #define WINDOWS 1 + #define WIN32_LEAN_AND_MEAN #include #include #include @@ -46,6 +47,10 @@ #define getcwd _getcwd #define setenv _setenv #define PATHSEP '\\' + #include + #include + #pragma comment(lib, "ws2_32.lib") + #define socklen_t int #else #include #include @@ -125,20 +130,48 @@ class RawDataHandleStorage : public RefCountedStorage { }; // RefCountedStorage base class for uds.Connection and uds.Server storages (since they declare similar data). +// +// +----------------------+ +// | UdsBaseHandleStorage | +// +----------------------+ +// | +----------------------------+ +// +--------->| UdsConnectionHandleStorage | +// | +----------------------------+ +// | +------------------------+ +// +--------->| UdsServerHandleStorage | +// +------------------------+ +// class UdsBaseHandleStorage : public RefCountedStorage { public: - // cleanup: Closes the socket. We will call it from uds.Server::close and uds.Connection::close, and also in destructors. + UdsBaseHandleStorage() : initedOk(false), sockfd(-1) { + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + } + virtual ~UdsBaseHandleStorage() { cleanup(); } + + // cleanup: Closes the socket. Called from uds.Server::close and uds.Connection::close, and in destructors. void cleanup() { if (sockfd < 0) return; +#if WINDOWS + closesocket(sockfd); +#else close(sockfd); +#endif sockfd = -1; } - // unblock: Makes the socket nonblocking. Call it in constructors. + + // unblock: Makes the socket nonblocking. Called in constructors. bool unblock() { +#if WINDOWS + unsigned long mode = 1; + return ioctlsocket(sockfd, FIONBIO, &mode) == 0; +#else int flags = fcntl(sockfd, F_GETFL, 0); flags |= O_NONBLOCK; return fcntl(sockfd, F_SETFL, flags) == 0; +#endif } + // isValid: Returns true if the socket is still open. bool isValid() { return initedOk and sockfd >= 0; @@ -150,46 +183,48 @@ class UdsBaseHandleStorage : public RefCountedStorage { }; // RefCountedStorage class to wrap a unix domain socket connection data. +// sockfd of this class is used for send/recv. class UdsConnectionHandleStorage : public UdsBaseHandleStorage { public: - UdsConnectionHandleStorage(const char *sockPath) : initedOk(false) { + UdsConnectionHandleStorage(const char *sockPath) { sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) return; if (!unblock()) return; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sockPath, sizeof(addr.sun_path) - 1); initedOk = true; } - UdsConnectionHandleStorage(int sockfd, sockaddr_un addr) : initedOk(false), sockfd(sockfd), addr(addr) { + UdsConnectionHandleStorage(int sockfd, sockaddr_un addr) { + this->sockfd = sockfd; + this->addr = addr; if (!unblock()) return; initedOk = true; } - virtual ~UdsConnectionHandleStorage() { cleanup(); } bool attemptConnect() { if (!isValid()) return false; - return connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == 0; + int rc = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)); +#if WINDOWS + return rc == 0 or (rc < 0 and WSAGetLastError() == WSAEISCONN); +#else + return rc == 0; +#endif } - ssize_t receiveBuf(char *buf, size_t nBytes) { + long receiveBuf(char *buf, size_t nBytes) { if (!isValid()) return 0; - ssize_t nReceived = recv(sockfd, buf, nBytes, 0); + long nReceived = recv(sockfd, buf, nBytes, 0); if (nReceived == 0) cleanup(); return nReceived; } - ssize_t sendBuf(void *buf, size_t nBytes) { + long sendBuf(const char *buf, size_t nBytes) { if (!isValid()) return 0; return send(sockfd, buf, nBytes, 0); } - - bool initedOk; - int sockfd; - struct sockaddr_un addr; }; // RefCountedStorage class to wrap a unix domain socket server data. +// sockfd of this class is used for listen/accept. class UdsServerHandleStorage : public UdsBaseHandleStorage { public: - UdsServerHandleStorage(const char *sockPath, int backlog) : initedOk(false) { + UdsServerHandleStorage(const char *sockPath, int backlog) { sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) return; if (!unblock()) return; @@ -201,7 +236,6 @@ class UdsServerHandleStorage : public UdsBaseHandleStorage { if (listen(sockfd, backlog) < 0) return; initedOk = true; } - virtual ~UdsServerHandleStorage() { cleanup(); } UdsConnectionHandleStorage *acceptConnection() { if (!isValid()) return nullptr; struct sockaddr_un peer; @@ -212,10 +246,6 @@ class UdsServerHandleStorage : public UdsBaseHandleStorage { if (!connStorage->isValid()) return nullptr; return connStorage; } - - bool initedOk; - int sockfd; - struct sockaddr_un addr; }; // hidden (unnamed) intrinsics, only accessible via other methods (such as the File module) @@ -1534,14 +1564,14 @@ static IntrinsicResult intrinsic_udsConnectionSend(Context *context, IntrinsicRe if (rawData.type == ValueType::String) { String s = rawData.GetString(); if (s.LengthB() == 0) RuntimeException("message length cannot be 0").raise(); - nSent = storage->sendBuf((void *)s.c_str(), s.LengthB()); + nSent = storage->sendBuf(s.c_str(), s.LengthB()); } else if (rawData.type == ValueType::Map) { if (!rawData.IsA(RawDataType(), context->vm)) RuntimeException("message must be RawData or string").raise(); Value dataWrapper = rawData.Lookup(_handle); if (dataWrapper.IsNull() or dataWrapper.type != ValueType::Handle) RuntimeException("bad RawData, no handle or length 0").raise(); RawDataHandleStorage *dataStorage = (RawDataHandleStorage*)dataWrapper.data.ref; if (dataStorage->dataSize == 0) RuntimeException("message length cannot be 0").raise(); - nSent = storage->sendBuf((void *)dataStorage->data, dataStorage->dataSize); + nSent = storage->sendBuf((const char *)dataStorage->data, dataStorage->dataSize); } else { RuntimeException("message must be RawData or string").raise(); } @@ -2116,6 +2146,13 @@ void AddShellIntrinsics() { // uds (unix domain sockets) +#if WINDOWS + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { + printf("WSAStartup failed, sockets will not work\n"); + } +#endif + f = Intrinsic::Create("uds"); f->code = &intrinsic_Uds; From 69a29660476869a40777af4d6715fa5899798fa7 Mon Sep 17 00:00:00 2001 From: Marc Gurevitx Date: Tue, 11 Jun 2024 00:07:14 +0300 Subject: [PATCH 3/6] Add offset param to UDS connection send() --- MiniScript-cpp/src/ShellIntrinsics.cpp | 71 +++++++++++--------- MiniScript-cpp/testUds.ms | 90 ++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 32 deletions(-) create mode 100644 MiniScript-cpp/testUds.ms diff --git a/MiniScript-cpp/src/ShellIntrinsics.cpp b/MiniScript-cpp/src/ShellIntrinsics.cpp index 1087f04..f73bf8a 100644 --- a/MiniScript-cpp/src/ShellIntrinsics.cpp +++ b/MiniScript-cpp/src/ShellIntrinsics.cpp @@ -172,8 +172,8 @@ class UdsBaseHandleStorage : public RefCountedStorage { #endif } - // isValid: Returns true if the socket is still open. - bool isValid() { + // isOpen: Returns true if the socket is still open. + bool isOpen() { return initedOk and sockfd >= 0; } @@ -200,7 +200,7 @@ class UdsConnectionHandleStorage : public UdsBaseHandleStorage { initedOk = true; } bool attemptConnect() { - if (!isValid()) return false; + if (!isOpen()) return false; int rc = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)); #if WINDOWS return rc == 0 or (rc < 0 and WSAGetLastError() == WSAEISCONN); @@ -209,14 +209,18 @@ class UdsConnectionHandleStorage : public UdsBaseHandleStorage { #endif } long receiveBuf(char *buf, size_t nBytes) { - if (!isValid()) return 0; + if (!isOpen()) return 0; long nReceived = recv(sockfd, buf, nBytes, 0); if (nReceived == 0) cleanup(); return nReceived; } long sendBuf(const char *buf, size_t nBytes) { - if (!isValid()) return 0; - return send(sockfd, buf, nBytes, 0); + if (!isOpen()) return 0; + int flags = 0; +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + return send(sockfd, buf, nBytes, flags); } }; @@ -237,13 +241,13 @@ class UdsServerHandleStorage : public UdsBaseHandleStorage { initedOk = true; } UdsConnectionHandleStorage *acceptConnection() { - if (!isValid()) return nullptr; + if (!isOpen()) return nullptr; struct sockaddr_un peer; socklen_t peerSize = sizeof(peer); int cfd = accept(sockfd, (struct sockaddr *)&peer, &peerSize); if (cfd < 0) return nullptr; UdsConnectionHandleStorage *connStorage = new UdsConnectionHandleStorage(cfd, peer); - if (!connStorage->isValid()) return nullptr; + if (!connStorage->isOpen()) return nullptr; return connStorage; } }; @@ -295,12 +299,12 @@ Intrinsic *i_rawDataSetDouble = nullptr; Intrinsic *i_rawDataUtf8 = nullptr; Intrinsic *i_rawDataSetUtf8 = nullptr; Intrinsic *i_udsConnect = nullptr; -Intrinsic *i_udsConnectionIsValid = nullptr; +Intrinsic *i_udsConnectionIsOpen = nullptr; Intrinsic *i_udsConnectionClose = nullptr; Intrinsic *i_udsConnectionReceive = nullptr; Intrinsic *i_udsConnectionSend = nullptr; Intrinsic *i_udsCreateServer = nullptr; -Intrinsic *i_udsServerIsValid = nullptr; +Intrinsic *i_udsServerIsOpen = nullptr; Intrinsic *i_udsServerAccept = nullptr; // Copy a file. Return 0 on success, or some value < 0 on error. @@ -1433,7 +1437,7 @@ static IntrinsicResult intrinsic_udsConnect(Context *context, IntrinsicResult pa // This is the initial entry into `uds.connect`. Initialize the storage. String sockPath = context->GetVar("sockPath").ToString(); UdsConnectionHandleStorage *storage = new UdsConnectionHandleStorage(sockPath.c_str()); - if (!storage->isValid()) return IntrinsicResult::Null; + if (!storage->isOpen()) return IntrinsicResult::Null; // Calculate the final time and end the current invokation. double timeout = context->GetVar("timeout").DoubleValue(); double finalTime = context->vm->RunTime() + timeout; @@ -1460,12 +1464,12 @@ static IntrinsicResult intrinsic_udsConnect(Context *context, IntrinsicResult pa return IntrinsicResult(data, false); } -static IntrinsicResult intrinsic_udsConnectionIsValid(Context *context, IntrinsicResult partialResult) { +static IntrinsicResult intrinsic_udsConnectionIsOpen(Context *context, IntrinsicResult partialResult) { Value self = context->GetVar("self"); Value connWrapper = self.Lookup(_handle); if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) return IntrinsicResult(Value::Truth(false)); UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; - return IntrinsicResult(Value::Truth(storage->isValid())); + return IntrinsicResult(Value::Truth(storage->isOpen())); } static IntrinsicResult intrinsic_udsConnectionClose(Context *context, IntrinsicResult partialResult) { @@ -1487,7 +1491,7 @@ static IntrinsicResult intrinsic_udsConnectionReceive(Context *context, Intrinsi Value connWrapper = self.Lookup(_handle); if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) RuntimeException("bad UDS connection, no handle").raise(); UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; - if (!storage->isValid()) RuntimeException("bad UDS connection, perhaps closed socket").raise(); + if (!storage->isOpen()) RuntimeException("bad UDS connection, closed socket").raise(); // Calculate the final time and end the current invokation. double timeout = context->GetVar("timeout").DoubleValue(); double finalTime = context->vm->RunTime() + timeout; @@ -1558,20 +1562,22 @@ static IntrinsicResult intrinsic_udsConnectionSend(Context *context, IntrinsicRe Value connWrapper = self.Lookup(_handle); if (connWrapper.IsNull() or connWrapper.type != ValueType::Handle) RuntimeException("bad UDS connection, no handle").raise(); UdsConnectionHandleStorage *storage = (UdsConnectionHandleStorage*)connWrapper.data.ref; - if (!storage->isValid()) RuntimeException("bad UDS connection, perhaps closed socket").raise(); + if (!storage->isOpen()) RuntimeException("bad UDS connection, closed socket").raise(); Value rawData = context->GetVar("rawData"); - long nSent; + int32_t offset = context->GetVar("offset").IntValue(); + if (offset < 0) offset = 0; + long nSent = 0; if (rawData.type == ValueType::String) { String s = rawData.GetString(); - if (s.LengthB() == 0) RuntimeException("message length cannot be 0").raise(); - nSent = storage->sendBuf(s.c_str(), s.LengthB()); + size_t len = s.LengthB(); + if (offset < len) nSent = storage->sendBuf(s.c_str() + offset, len - offset); } else if (rawData.type == ValueType::Map) { if (!rawData.IsA(RawDataType(), context->vm)) RuntimeException("message must be RawData or string").raise(); Value dataWrapper = rawData.Lookup(_handle); if (dataWrapper.IsNull() or dataWrapper.type != ValueType::Handle) RuntimeException("bad RawData, no handle or length 0").raise(); RawDataHandleStorage *dataStorage = (RawDataHandleStorage*)dataWrapper.data.ref; - if (dataStorage->dataSize == 0) RuntimeException("message length cannot be 0").raise(); - nSent = storage->sendBuf((const char *)dataStorage->data, dataStorage->dataSize); + size_t len = dataStorage->dataSize; + if (offset < len) nSent = storage->sendBuf((const char *)dataStorage->data + offset, len - offset); } else { RuntimeException("message must be RawData or string").raise(); } @@ -1584,7 +1590,7 @@ static IntrinsicResult intrinsic_udsCreateServer(Context *context, IntrinsicResu int32_t backlog = context->GetVar("backlog").IntValue(); if (backlog <= 0) backlog = UDS_DEFAULT_BACKLOG; UdsServerHandleStorage *storage = new UdsServerHandleStorage(sockPath.c_str(), backlog); - if (!storage->isValid()) return IntrinsicResult::Null; + if (!storage->isOpen()) return IntrinsicResult::Null; ValueDict instance; instance.SetValue(Value::magicIsA, UdsServerType()); instance.SetValue(_handle, Value::NewHandle(storage)); @@ -1592,12 +1598,12 @@ static IntrinsicResult intrinsic_udsCreateServer(Context *context, IntrinsicResu return IntrinsicResult(result); } -static IntrinsicResult intrinsic_udsServerIsValid(Context *context, IntrinsicResult partialResult) { +static IntrinsicResult intrinsic_udsServerIsOpen(Context *context, IntrinsicResult partialResult) { Value self = context->GetVar("self"); Value serverWrapper = self.Lookup(_handle); if (serverWrapper.IsNull() or serverWrapper.type != ValueType::Handle) return IntrinsicResult(Value::Truth(false)); UdsServerHandleStorage *storage = (UdsServerHandleStorage*)serverWrapper.data.ref; - return IntrinsicResult(Value::Truth(storage->isValid())); + return IntrinsicResult(Value::Truth(storage->isOpen())); } static IntrinsicResult intrinsic_udsServerAccept(Context *context, IntrinsicResult partialResult) { @@ -1607,7 +1613,7 @@ static IntrinsicResult intrinsic_udsServerAccept(Context *context, IntrinsicResu Value serverWrapper = self.Lookup(_handle); if (serverWrapper.IsNull() or serverWrapper.type != ValueType::Handle) RuntimeException("bad UDS server, no handle").raise(); UdsServerHandleStorage *storage = (UdsServerHandleStorage*)serverWrapper.data.ref; - if (!storage->isValid()) RuntimeException("bad UDS server, perhaps closed socket").raise(); + if (!storage->isOpen()) RuntimeException("bad UDS server, closed socket").raise(); // Calculate the final time and end the current invokation. double timeout = context->GetVar("timeout").DoubleValue(); double finalTime = context->vm->RunTime() + timeout; @@ -1719,7 +1725,7 @@ static IntrinsicResult intrinsic_RawData(Context *context, IntrinsicResult parti static ValueDict& UdsConnectionType() { static ValueDict result; if (result.Count() == 0) { - result.SetValue("isValid", i_udsConnectionIsValid->GetFunc()); + result.SetValue("isOpen", i_udsConnectionIsOpen->GetFunc()); result.SetValue("close", i_udsConnectionClose->GetFunc()); result.SetValue("receive", i_udsConnectionReceive->GetFunc()); result.SetValue("send", i_udsConnectionSend->GetFunc()); @@ -1731,7 +1737,7 @@ static ValueDict& UdsConnectionType() { static ValueDict& UdsServerType() { static ValueDict result; if (result.Count() == 0) { - result.SetValue("isValid", i_udsServerIsValid->GetFunc()); + result.SetValue("isOpen", i_udsServerIsOpen->GetFunc()); result.SetValue("accept", i_udsServerAccept->GetFunc()); } @@ -2161,9 +2167,9 @@ void AddShellIntrinsics() { i_udsConnect->AddParam("timeout", 30); i_udsConnect->code = &intrinsic_udsConnect; - i_udsConnectionIsValid = Intrinsic::Create(""); - i_udsConnectionIsValid->AddParam("self"); - i_udsConnectionIsValid->code = &intrinsic_udsConnectionIsValid; + i_udsConnectionIsOpen = Intrinsic::Create(""); + i_udsConnectionIsOpen->AddParam("self"); + i_udsConnectionIsOpen->code = &intrinsic_udsConnectionIsOpen; i_udsConnectionClose = Intrinsic::Create(""); i_udsConnectionClose->AddParam("self"); @@ -2178,6 +2184,7 @@ void AddShellIntrinsics() { i_udsConnectionSend = Intrinsic::Create(""); i_udsConnectionSend->AddParam("self"); i_udsConnectionSend->AddParam("rawData"); + i_udsConnectionSend->AddParam("offset", 0); i_udsConnectionSend->code = &intrinsic_udsConnectionSend; i_udsCreateServer = Intrinsic::Create(""); @@ -2185,9 +2192,9 @@ void AddShellIntrinsics() { i_udsCreateServer->AddParam("backlog", UDS_DEFAULT_BACKLOG); i_udsCreateServer->code = &intrinsic_udsCreateServer; - i_udsServerIsValid = Intrinsic::Create(""); - i_udsServerIsValid->AddParam("self"); - i_udsServerIsValid->code = &intrinsic_udsServerIsValid; + i_udsServerIsOpen = Intrinsic::Create(""); + i_udsServerIsOpen->AddParam("self"); + i_udsServerIsOpen->code = &intrinsic_udsServerIsOpen; i_udsServerAccept = Intrinsic::Create(""); i_udsServerAccept->AddParam("self"); diff --git a/MiniScript-cpp/testUds.ms b/MiniScript-cpp/testUds.ms new file mode 100644 index 0000000..021a461 --- /dev/null +++ b/MiniScript-cpp/testUds.ms @@ -0,0 +1,90 @@ +import "qa" + +SOCKET = "uds.sock" + +_testServer = function(server) + qa.assertEqual server.isOpen, true + + conn = server.accept(60) + qa.assertEqual conn != null, true + + msg = conn.receive + qa.assertEqual msg.utf8, "foo" + + n = conn.send("bar") + qa.assertEqual n, 3 + + msg = conn.receive(1) + qa.assertEqual msg.utf8, "b" + msg = conn.receive(1) + qa.assertEqual msg.utf8, "a" + msg = conn.receive(10, 0) + qa.assertEqual msg.utf8, "mf" + + n = conn.send("hello", 1) + qa.assertEqual n, 4 + + msg = conn.receive + qa.assertEqual msg.utf8, "bye" +end function + +_testClient = function(client) + qa.assertEqual client.isOpen, true + + n = client.send("foo") + qa.assertEqual n, 3 + + msg = client.receive + qa.assertEqual msg.utf8, "bar" + + r = new RawData + r.resize 4 + r.setUtf8 0, "bamf" + n = client.send(r) + qa.assertEqual n, 4 + + msg = client.receive + qa.assertEqual msg.utf8, "ello" + + r = new RawData + r.resize 7 + r.setUtf8 0, "goodbye" + n = client.send(r, 4) + qa.assertEqual n, 3 +end function + +testUds = function + client = uds.connect(SOCKET, 0) + if client == null then + server = uds.createServer(SOCKET) + qa.assertEqual server != null, true + + // + print " *** Server awaits at " + SOCKET + print " ***" + print " *** Please, launch in parallel another instance of `testUds.ms`..." + print " ***" + // + + _testServer server + + // + print " *** OK" + // + + else + + // + print " *** Client connected" + // + + _testClient client + + // + print " *** OK" + // + + end if +end function + +if refEquals(locals, globals) then testUds From a31c5ffd9a08096cf8b58d73d3dc6812c0a704b9 Mon Sep 17 00:00:00 2001 From: Marc Gurevitx Date: Tue, 11 Jun 2024 20:53:02 +0300 Subject: [PATCH 4/6] Negative timeouts for indefinite uds blocking --- MiniScript-cpp/README-UDS.md | 149 +++++++++++++++++++++++++ MiniScript-cpp/src/ShellIntrinsics.cpp | 4 + 2 files changed, 153 insertions(+) create mode 100644 MiniScript-cpp/README-UDS.md diff --git a/MiniScript-cpp/README-UDS.md b/MiniScript-cpp/README-UDS.md new file mode 100644 index 0000000..f51af27 --- /dev/null +++ b/MiniScript-cpp/README-UDS.md @@ -0,0 +1,149 @@ +# Unix domain sockets + +[Unix domain sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) (or inter-process communication sockets) is a mean to exchange data between two MiniScript programs running on the same machine or between a MiniScript program and some other program that also supports unix domain sockets (reverse proxies, databases etc). + +The MiniScript shell (interpreter) provides an intrinsic `uds` module with functions and classes that help writing both client and server side of UDS communication. + +Steps to write a client: + +* Call `uds.connect()` -- it returns a connection object (`uds.Connection`). +* Call connection's `send()` and `receive()` methods to exchange data with the server. + +Steps to write a server: + +* Call `uds.createServer()` -- it returns a server object (`uds.Server`). +* Call server's `accept()` method -- it returns a connection object when a new client connects (`uds.Connection`). +* Call connection's `send()` and `receive()` methods to exchange data with the client. + +Both `connect()` and `createServer()` accept a path to a socket-file used as the connection point. The server will create it if it doesn't exist. + +Methods `uds.connect()`, `connection.receive()` and `server.accept()` have an optional `timeout` parameter (in seconds). They return as soon as the result is ready or as the timeout expires (in which case they return `null`). If the timeout is zero, they return immediately in a non-blocking fashion. If the timeout is negative, these methods will block till the result is ready. The default value is 30 seconds. + + +## Example + +Simple echo server: + +```c +srv = uds.createServer("echo.sock") +while true + c = srv.accept(-1) + msg = c.receive + print "received " + msg.utf8 + c.send msg + c.close +end while +``` + +Client: + +```c +c = uds.connect("echo.sock") +c.send "foo" +msg = c.receive +print msg.utf8 // prints: foo +c.close +``` + + +## API - Creating client and server + +#### uds.connect() + +`uds.connect(sockPath, timeout=30) -> uds.Connection or null` + +Creates a connection to a server on the client side. (In C++ it calls *socket()* and *connect()*.) + +The `sockPath` parameter is a path to a socket file (connection point). + +The `timeout` parameter tells how many seconds to wait for the server to accept us (`0` -- return immediately, `-1` -- wait indefinitely). + +If successful, a connection object is returned (an instance of `uds.Connection`). + +Otherwise, the result is `null`. + + +#### uds.createServer() + +`uds.createServer(sockPath, backlog=20) -> uds.Server or null` + +Creates a server object. (In C++ it calls *socket()*, *bind()* and *listen()*.) + +The `sockPath` parameter is a path to a socket file (connection point). + +If the socket file doesn't exist, this function creates it. + +If the socket file exists, this function deletes it and creates a new one. + +The `backlog` parameter (the maximal number of queued connections) will be passed to C++'s *listen()*. + +If successful, a server object is returned (an instance of `uds.Server`). + +Otherwise, the result is `null`. + + +## API - Connection + +#### connection.isOpen() + +`connection.isOpen() -> true or false` + +Returns whether the underlying socket is still open. + +#### connection.close() + +`connection.isOpen() -> true or false` + +Closes the underlying socket. (In C++ it calls *close()* in a unix or *closesocket()* in Windows.) + +#### connection.send() + +`connection.send(rawData, offset=0) -> nBytes` + +Sends a message over a socket. (In C++ it calls *send()*.) + +The `rawData` parameter can either be a `RawData` object or a string. + +The optional `offset` parameter tells from which byte to start. + +It returns the number of successfully sent bytes or `-1` (basically, the result of C++'s *send()*). + +#### connection.receive() + +`connection.receive(bytes=-1, timeout=30) -> RawData or null` + +Receives bytes from a socket. (In C++ it calls *recv()*.) + +The `bytes` parameter is the amount of bytes to read from a socket. If `-1`, it returns all available bytes. + +The `timeout` parameter tells how many seconds to wait for the bytes to arrive (`0` -- return immediately, `-1` -- wait indefinitely). + +If there are bytes, it returns a `RawData` object. + +Otherwise, the result is `null`. + + +## API - Server + +#### server.isOpen() + +`server.isOpen() -> true or false` + +Returns whether the underlying socket is still open. + +#### server.accept() + +`server.accept(timeout=30) -> Connection` + +Accepts a request for an incoming connection. (In C++ it calls *accept()*.) + +The `timeout` parameter tells how many seconds to wait for a connection request (`0` -- return immediately, `-1` -- wait indefinitely). + +If successful, a connection object is returned (an instance of `uds.Connection`). + +Otherwise, the result is `null`. + + +## Test + +To test `uds` launch two virtual terminals and run in each of them `miniscript testUds.ms`. diff --git a/MiniScript-cpp/src/ShellIntrinsics.cpp b/MiniScript-cpp/src/ShellIntrinsics.cpp index f73bf8a..a81b907 100644 --- a/MiniScript-cpp/src/ShellIntrinsics.cpp +++ b/MiniScript-cpp/src/ShellIntrinsics.cpp @@ -71,6 +71,7 @@ #include #include // for sockaddr_un #endif +#include #define UDS_DEFAULT_BACKLOG (20) extern "C" { @@ -1440,6 +1441,7 @@ static IntrinsicResult intrinsic_udsConnect(Context *context, IntrinsicResult pa if (!storage->isOpen()) return IntrinsicResult::Null; // Calculate the final time and end the current invokation. double timeout = context->GetVar("timeout").DoubleValue(); + if (timeout < 0) timeout = std::numeric_limits::infinity(); double finalTime = context->vm->RunTime() + timeout; ValueDict data; data.SetValue("wrapper", Value::NewHandle(storage)); @@ -1494,6 +1496,7 @@ static IntrinsicResult intrinsic_udsConnectionReceive(Context *context, Intrinsi if (!storage->isOpen()) RuntimeException("bad UDS connection, closed socket").raise(); // Calculate the final time and end the current invokation. double timeout = context->GetVar("timeout").DoubleValue(); + if (timeout < 0) timeout = std::numeric_limits::infinity(); double finalTime = context->vm->RunTime() + timeout; // Initialize a RawData result. We do not need the whole RawData object rn, a simple handle is enough. Value dataWrapper = Value::NewHandle(new RawDataHandleStorage()); @@ -1616,6 +1619,7 @@ static IntrinsicResult intrinsic_udsServerAccept(Context *context, IntrinsicResu if (!storage->isOpen()) RuntimeException("bad UDS server, closed socket").raise(); // Calculate the final time and end the current invokation. double timeout = context->GetVar("timeout").DoubleValue(); + if (timeout < 0) timeout = std::numeric_limits::infinity(); double finalTime = context->vm->RunTime() + timeout; ValueDict data; data.SetValue("wrapper", serverWrapper); From f62368340d9a06996398f4394be6a3739a934f0c Mon Sep 17 00:00:00 2001 From: Marc Gurevitx Date: Wed, 12 Jun 2024 10:00:32 +0300 Subject: [PATCH 5/6] Fix README-UDS problems --- MiniScript-cpp/README-UDS.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/MiniScript-cpp/README-UDS.md b/MiniScript-cpp/README-UDS.md index f51af27..2d2e84d 100644 --- a/MiniScript-cpp/README-UDS.md +++ b/MiniScript-cpp/README-UDS.md @@ -48,7 +48,7 @@ c.close ## API - Creating client and server -#### uds.connect() +#### Function uds.connect() `uds.connect(sockPath, timeout=30) -> uds.Connection or null` @@ -63,7 +63,7 @@ If successful, a connection object is returned (an instance of `uds.Connection`) Otherwise, the result is `null`. -#### uds.createServer() +#### Function uds.createServer() `uds.createServer(sockPath, backlog=20) -> uds.Server or null` @@ -84,19 +84,19 @@ Otherwise, the result is `null`. ## API - Connection -#### connection.isOpen() +#### Function connection.isOpen() `connection.isOpen() -> true or false` Returns whether the underlying socket is still open. -#### connection.close() +#### Function connection.close() -`connection.isOpen() -> true or false` +`connection.close()` -Closes the underlying socket. (In C++ it calls *close()* in a unix or *closesocket()* in Windows.) +Closes the underlying socket. (In C++ it calls *close()* in unixes or *closesocket()* in Windows.) -#### connection.send() +#### Function connection.send() `connection.send(rawData, offset=0) -> nBytes` @@ -104,11 +104,11 @@ Sends a message over a socket. (In C++ it calls *send()*.) The `rawData` parameter can either be a `RawData` object or a string. -The optional `offset` parameter tells from which byte to start. +The optional `offset` parameter tells from which byte in `rawData` to start. It returns the number of successfully sent bytes or `-1` (basically, the result of C++'s *send()*). -#### connection.receive() +#### Function connection.receive() `connection.receive(bytes=-1, timeout=30) -> RawData or null` @@ -125,13 +125,13 @@ Otherwise, the result is `null`. ## API - Server -#### server.isOpen() +#### Function server.isOpen() `server.isOpen() -> true or false` Returns whether the underlying socket is still open. -#### server.accept() +#### Function server.accept() `server.accept(timeout=30) -> Connection` From 5993dac071ea890a33ee8f0fc8f7a0cde2120fdd Mon Sep 17 00:00:00 2001 From: Marc Gurevitx Date: Thu, 1 Aug 2024 15:25:17 +0300 Subject: [PATCH 6/6] Close connection on EPIPE --- MiniScript-cpp/src/ShellIntrinsics.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MiniScript-cpp/src/ShellIntrinsics.cpp b/MiniScript-cpp/src/ShellIntrinsics.cpp index c230b0c..cb26322 100644 --- a/MiniScript-cpp/src/ShellIntrinsics.cpp +++ b/MiniScript-cpp/src/ShellIntrinsics.cpp @@ -221,7 +221,9 @@ class UdsConnectionHandleStorage : public UdsBaseHandleStorage { #ifdef MSG_NOSIGNAL flags |= MSG_NOSIGNAL; #endif - return send(sockfd, buf, nBytes, flags); + ssize_t nSent = send(sockfd, buf, nBytes, flags); + if (nSent < 0 && errno == EPIPE) cleanup(); + return nSent; } };