From 2332b2e37c1490be161ad7bbe69688621dc11b47 Mon Sep 17 00:00:00 2001 From: Alexey Yerin Date: Sat, 21 Oct 2023 19:25:26 +0300 Subject: [PATCH] Add support for UNIX domain sockets --- ev/+linux/file.ha | 23 ++++++++++++----------- ev/+linux/loop.ha | 2 ++ ev/+linux/socket.ha | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/ev/+linux/file.ha b/ev/+linux/file.ha index faac28c7ac5f0506d149f6a3959f1232d536a5d7..0a4d53c8e18b2f9f34ee1bc5641c94a6cea9de52 100644 --- a/ev/+linux/file.ha +++ b/ev/+linux/file.ha @@ -10,16 +10,17 @@ NONE = 0, READV = 1 << 0, WRITEV = 1 << 1, - READABLE = 1 << 16, - WRITABLE = 2 << 16, - ACCEPT = 3 << 16, - CONNECT_TCP = 4 << 16, - SIGNAL = 5 << 16, - TIMER = 6 << 16, - SENDTO = 7 << 16, - RECVFROM = 8 << 16, - SEND = 9 << 16, - RECV = 10 << 16, + READABLE = 1 << 16, + WRITABLE = 2 << 16, + ACCEPT = 3 << 16, + CONNECT_TCP = 4 << 16, + CONNECT_UNIX = 5 << 16, + SIGNAL = 6 << 16, + TIMER = 7 << 16, + SENDTO = 8 << 16, + RECVFROM = 9 << 16, + SEND = 10 << 16, + RECV = 11 << 16, }; export type fflags = enum uint { @@ -152,7 +153,7 @@ }; switch (file.op) { case op::ACCEPT => events |= rt::EPOLLIN; - case op::CONNECT_TCP => + case op::CONNECT_TCP, op::CONNECT_UNIX => events |= rt::EPOLLOUT; case op::SIGNAL => events |= rt::EPOLLIN; diff --git a/ev/+linux/loop.ha b/ev/+linux/loop.ha index 2716907f3136a3fac8eed173ca933da3e1632e82..86c92e2bec3935d6c392c00be40daf2c79ad426d 100644 --- a/ev/+linux/loop.ha +++ b/ev/+linux/loop.ha @@ -172,6 +172,8 @@ case op::ACCEPT => accept_ready(file, ev); case op::CONNECT_TCP => connect_tcp_ready(file, ev); + case op::CONNECT_UNIX => + connect_unix_ready(file, ev); case op::SIGNAL => signal_ready(file, ev); case op::TIMER => diff --git a/ev/+linux/socket.ha b/ev/+linux/socket.ha index ef20d54138eef961e085a656b135d70013c4d72c..ef7b3f6401f27bb5817c54a9ecaaa2f7c8eb5d5a 100644 --- a/ev/+linux/socket.ha +++ b/ev/+linux/socket.ha @@ -3,6 +3,7 @@ use net; use net::ip; use net::tcp; use net::udp; +use net::unix; use rt; // Creates a socket which listens for incoming TCP connections on the given @@ -26,6 +27,16 @@ port: u16, opts: udp::listen_option... ) (*file | net::error | errors::error) = { const sock = udp::listen(addr, port, opts...)?; + return register(loop, sock)?; +}; + +// Creates a UNIX domain socket at the given path. +export fn listen_unix( + loop: *loop, + addr: unix::addr, + opts: unix::listen_option... +) (*file | net::error | errors::error) = { + const sock = unix::listen(addr, opts...)?; return register(loop, sock)?; }; @@ -92,6 +103,52 @@ file_epoll_ctl(file); return mkreq(&connect_tcp_cancel, file); }; +// Connects to a UNIX domain socket. +// +// The variadic arguments accept [[net::sockflag]] and/or no more than one user +// data pointer. If the user data pointer is provided, it will be passed to the +// callback. This allows the user to pass a state object through the connection +// process: +// +// let user: state = // ... +// ev::connect_user(&loop, &connected, addr, &user); +// +// fn connected(result: (*ev::file | net::error), user: nullable *opaque) void = { +// let user = user: *state; +// }; +// +// The user data object provided will be assigned to the [[file]] which is +// provided to the callback after the connection is established. +// +// If you don't need a user data object you can just omit it: +// +// ev::connect_unix(&loop, &connected, addr, &user); +export fn connect_unix( + loop: *loop, + cb: *connectcb, + addr: unix::addr, + opts: (net::sockflag | *opaque)... +) (req | net::error | errors::error) = { + let opt: net::sockflag = 0; + let user: nullable *opaque = null; + for (let i = 0z; i < len(opts); i += 1) { + match (opts[i]) { + case let o: net::sockflag => + opt |= o; + case let u: *opaque => + assert(user == null); + user = u; + }; + }; + const sock = unix::connect(addr, opt | net::sockflag::NONBLOCK)?; + let file = register(loop, sock)?; + file.user = user; + file.cb = cb; + file.op = op::CONNECT_UNIX; + file_epoll_ctl(file); + return mkreq(&connect_unix_cancel, file); +}; + fn connect_tcp_ready( sock: *file, ev: *rt::epoll_event, @@ -117,9 +174,41 @@ cb(sock, sock.user); }; }; +fn connect_unix_ready( + sock: *file, + ev: *rt::epoll_event, +) void = { + assert(sock.op == op::CONNECT_UNIX); + assert(ev.events & rt::EPOLLOUT != 0); + assert(sock.cb != null); + const cb = sock.cb: *connectcb; + sock.op &= ~op::CONNECT_UNIX; + file_epoll_ctl(sock); + + let errno = 0i, optsz = size(int): u32; + rt::getsockopt(sock.fd, rt::SOL_SOCKET, rt::SO_ERROR, &errno, &optsz)!; + if (errno != 0) { + cb(errors::errno(errno), sock.user); + close(sock); + } else { + // XXX: If the user puts NONBLOCK into the opts provided at + // [[connect_unix]] we could try to preserve that here + const fl = rt::fcntl(sock.fd, rt::F_GETFL, void)!; + rt::fcntl(sock.fd, rt::F_SETFL, fl & ~rt::O_NONBLOCK)!; + cb(sock, sock.user); + }; +}; + fn connect_tcp_cancel(req: *req) void = { const sock = req.user: *file; assert(sock.op == op::CONNECT_TCP); + sock.op = op::NONE; + file_epoll_ctl(sock); +}; + +fn connect_unix_cancel(req: *req) void = { + const sock = req.user: *file; + assert(sock.op == op::CONNECT_UNIX); sock.op = op::NONE; file_epoll_ctl(sock); }; -- 2.48.1