diff --git a/changelogs/unreleased/gh-8927-socketpair.md b/changelogs/unreleased/gh-8927-socketpair.md new file mode 100644 index 0000000000000000000000000000000000000000..66f09d53b81989646ce7f8fae7671531bca066e9 --- /dev/null +++ b/changelogs/unreleased/gh-8927-socketpair.md @@ -0,0 +1,4 @@ +## feature/lua/socket + +* Introduced new socket functions `socket.socketpair`, `socket.from_fd`, and + `socket:detach` (gh-8927). diff --git a/src/lua/socket.lua b/src/lua/socket.lua index 5aac3f662fb258e33e8a82908660acd788c84845..28581480497bde2d590a782e3f2de014239e099b 100644 --- a/src/lua/socket.lua +++ b/src/lua/socket.lua @@ -32,6 +32,7 @@ ffi.cdef[[ ssize_t read(int fd, void *buf, size_t count); int listen(int fd, int backlog); int socket(int domain, int type, int protocol); + int socketpair(int domain, int type, int protocol, int sv[2]); int coio_close(int s); int shutdown(int s, int how); ssize_t send(int sockfd, const void *buf, size_t len, int flags); @@ -933,7 +934,7 @@ local function socket_sendto(self, host, port, octets, flags) return tonumber(res) end -local function socket_new(domain, stype, proto) +local function check_socket_args(domain, stype, proto) local idomain = get_ivalue(internal.DOMAIN, domain) if idomain == nil then boxerrno(boxerrno.EINVAL) @@ -951,6 +952,14 @@ local function socket_new(domain, stype, proto) return nil end + return idomain, itype, iproto +end + +local function socket_new(domain, stype, proto) + local idomain, itype, iproto = check_socket_args(domain, stype, proto) + if idomain == nil then + return nil + end local fd = ffi.C.socket(idomain, itype, iproto) if fd >= 0 then local socket = make_socket(fd, itype) @@ -962,6 +971,25 @@ local function socket_new(domain, stype, proto) end end +local function socket_socketpair(domain, stype, proto) + local idomain, itype, iproto = check_socket_args(domain, stype, proto) + if idomain == nil then + return nil + end + local sv = ffi.new('int[2]') + if ffi.C.socketpair(idomain, itype, iproto, sv) ~= 0 then + return nil + end + local s1 = make_socket(sv[0], itype) + local s2 = make_socket(sv[1], itype) + if not s1:nonblock(true) or not s2:nonblock(true) then + s1:close() + s2:close() + return nil + end + return s1, s2 +end + local function socket_from_fd(fd) if type(fd) ~= 'number' then error('fd must be a number') @@ -1622,6 +1650,7 @@ end return setmetatable({ from_fd = socket_from_fd; + socketpair = socket_socketpair; getaddrinfo = getaddrinfo, tcp_connect = tcp_connect, tcp_server = tcp_server, diff --git a/test/app-luatest/socket_test.lua b/test/app-luatest/socket_test.lua index 92374ff8d4b6b8292ae8da00c6db8baab04f9057..db099b415bed313478b6a4fede3275a3c1f7cf0a 100644 --- a/test/app-luatest/socket_test.lua +++ b/test/app-luatest/socket_test.lua @@ -1,3 +1,4 @@ +local errno = require('errno') local socket = require('socket') local t = require('luatest') @@ -58,3 +59,26 @@ g.test_detach = function() t.assert_is_not(s2:name(), nil) t.assert(s2:close()) end + +g.test_socketpair = function() + t.assert_is(socket.socketpair(), nil) + t.assert_equals(errno(), errno.EINVAL) + t.assert_is(socket.socketpair('foo'), nil) + t.assert_equals(errno(), errno.EINVAL) + t.assert_is(socket.socketpair('AF_UNIX', 'bar'), nil) + t.assert_equals(errno(), errno.EINVAL) + t.assert_is(socket.socketpair('AF_UNIX', 'SOCK_STREAM', 'baz'), nil) + t.assert_equals(errno(), errno.EPROTOTYPE) + t.assert_is(socket.socketpair('AF_INET', 'SOCK_STREAM', 0), nil) + t.assert_equals(errno(), errno.EOPNOTSUPP) + + local s1, s2 = socket.socketpair('AF_UNIX', 'SOCK_STREAM', 0) + t.assert(s1) + t.assert(s2) + t.assert(s1:nonblock()) + t.assert(s2:nonblock()) + t.assert_equals(s1:send('foo'), 3) + t.assert_equals(s2:recv(), 'foo') + t.assert(s1:close()) + t.assert(s2:close()) +end