// bare-abstract-socket — Linux abstract-namespace AF_UNIX sockets for bare.
//
// This module owns ONLY the socket setup: it creates abstract-namespace sockets
// (leading-NUL sun_path, which SELinux permits no-root where pathname sockets do
// not) and hands raw fds back to JS. All stream I/O is done by upstream, stock
// `bare-pipe` via `new Pipe(fd)` — we do not touch or fork it.
//
// connect(name) -> a connected client fd (int)
// listen(name, backlog, ctx, onc) -> a server handle; onc(fd) per accepted conn
// close(handle) -> stop polling + close the listening fd
//
// Server accept is integrated with the event loop via uv_poll on the listening
// fd (readable -> accept() the backlog). Abstract addressing is plain POSIX
// sockaddr_un, so none of libuv's name-length handling is involved.
#include <assert.h>
#include <bare.h>
#include <errno.h>
#include <fcntl.h>
#include <js.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <uv.h>
typedef struct {
uv_poll_t poll;
int fd;
js_env_t *env;
js_ref_t *ctx;
js_ref_t *on_connection;
bool closing;
js_deferred_teardown_t *teardown;
} bare_abstract_server_t;
// Fill an abstract-namespace sockaddr_un. Returns the address length to pass to
// bind()/connect(), or -1 if the name is too long. The abstract name is a
// leading NUL byte followed by `name_len` bytes (NOT NUL-terminated); the
// address length is what distinguishes the bytes, so we must pass it exactly.
static int
bare_abstract__fill_addr(struct sockaddr_un *addr, const char *name, size_t name_len) {
if (name_len + 1 > sizeof(addr->sun_path)) return -1;
memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX;
addr->sun_path[0] = '\0';
memcpy(addr->sun_path + 1, name, name_len);
return (int) (offsetof(struct sockaddr_un, sun_path) + 1 + name_len);
}
static int
bare_abstract__set_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
// uv_poll callback: the listening fd is readable -> drain the accept backlog,
// emitting each connected fd to JS. bare-pipe (via new Pipe(fd)) then does I/O.
static void
bare_abstract__on_poll(uv_poll_t *handle, int status, int events) {
int err;
bare_abstract_server_t *server = (bare_abstract_server_t *) handle->data;
if (server->closing) return;
js_env_t *env = server->env;
js_handle_scope_t *scope;
err = js_open_handle_scope(env, &scope);
assert(err == 0);
js_value_t *ctx;
err = js_get_reference_value(env, server->ctx, &ctx);
assert(err == 0);
js_value_t *on_connection;
err = js_get_reference_value(env, server->on_connection, &on_connection);
assert(err == 0);
if (status < 0) {
js_value_t *argv[2];
err = js_create_string_utf8(env, (utf8_t *) uv_err_name(status), -1, &argv[0]);
assert(err == 0);
err = js_get_null(env, &argv[1]);
assert(err == 0);
js_call_function(env, ctx, on_connection, 2, argv, NULL);
err = js_close_handle_scope(env, scope);
assert(err == 0);
return;
}
// Drain everything currently pending so we never miss a connection between
// poll wakeups.
while (!server->closing) {
int fd = accept(server->fd, NULL, NULL);
if (fd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
if (errno == EINTR || errno == ECONNABORTED) continue;
break; // other errors: stop draining this wakeup
}
bare_abstract__set_nonblock(fd);
js_value_t *argv[2];
err = js_get_null(env, &argv[0]);
assert(err == 0);
err = js_create_uint32(env, (uint32_t) fd, &argv[1]);
assert(err == 0);
js_call_function(env, ctx, on_connection, 2, argv, NULL);
}
err = js_close_handle_scope(env, scope);
assert(err == 0);
}
static void
bare_abstract__on_close(uv_handle_t *handle) {
int err;
bare_abstract_server_t *server = (bare_abstract_server_t *) handle->data;
js_env_t *env = server->env;
js_deferred_teardown_t *teardown = server->teardown;
err = js_delete_reference(env, server->on_connection);
assert(err == 0);
err = js_delete_reference(env, server->ctx);
assert(err == 0);
err = js_finish_deferred_teardown_callback(teardown);
assert(err == 0);
}
static void
bare_abstract__teardown(js_deferred_teardown_t *handle, void *data) {
bare_abstract_server_t *server = (bare_abstract_server_t *) data;
if (server->closing) return;
server->closing = true;
if (server->fd >= 0) {
close(server->fd);
server->fd = -1;
}
uv_close((uv_handle_t *) &server->poll, bare_abstract__on_close);
}
static js_value_t *
bare_abstract_connect(js_env_t *env, js_callback_info_t *info) {
int err;
size_t argc = 1;
js_value_t *argv[1];
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
assert(err == 0);
assert(argc == 1);
char name[sizeof(((struct sockaddr_un *) 0)->sun_path)];
size_t name_len;
err = js_get_value_string_utf8(env, argv[0], (utf8_t *) name, sizeof(name), &name_len);
assert(err == 0);
struct sockaddr_un addr;
int addrlen = bare_abstract__fill_addr(&addr, name, name_len);
if (addrlen < 0) {
err = js_throw_error(env, "ENAMETOOLONG", "abstract socket name too long");
assert(err == 0);
return NULL;
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
err = js_throw_error(env, uv_err_name(uv_translate_sys_error(errno)), strerror(errno));
assert(err == 0);
return NULL;
}
// AF_UNIX connect completes (or is refused) immediately for a listening peer,
// so a blocking connect never stalls the loop; switch to non-blocking after,
// as uv_pipe_open (bare-pipe) expects.
if (connect(fd, (struct sockaddr *) &addr, addrlen) < 0) {
int e = errno;
close(fd);
err = js_throw_error(env, uv_err_name(uv_translate_sys_error(e)), strerror(e));
assert(err == 0);
return NULL;
}
bare_abstract__set_nonblock(fd);
js_value_t *result;
err = js_create_uint32(env, (uint32_t) fd, &result);
assert(err == 0);
return result;
}
static js_value_t *
bare_abstract_listen(js_env_t *env, js_callback_info_t *info) {
int err;
size_t argc = 4;
js_value_t *argv[4];
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
assert(err == 0);
assert(argc == 4);
char name[sizeof(((struct sockaddr_un *) 0)->sun_path)];
size_t name_len;
err = js_get_value_string_utf8(env, argv[0], (utf8_t *) name, sizeof(name), &name_len);
assert(err == 0);
uint32_t backlog;
err = js_get_value_uint32(env, argv[1], &backlog);
assert(err == 0);
struct sockaddr_un addr;
int addrlen = bare_abstract__fill_addr(&addr, name, name_len);
if (addrlen < 0) {
err = js_throw_error(env, "ENAMETOOLONG", "abstract socket name too long");
assert(err == 0);
return NULL;
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
err = js_throw_error(env, uv_err_name(uv_translate_sys_error(errno)), strerror(errno));
assert(err == 0);
return NULL;
}
if (bind(fd, (struct sockaddr *) &addr, addrlen) < 0) {
int e = errno;
close(fd);
err = js_throw_error(env, uv_err_name(uv_translate_sys_error(e)), strerror(e));
assert(err == 0);
return NULL;
}
if (listen(fd, (int) backlog) < 0) {
int e = errno;
close(fd);
err = js_throw_error(env, uv_err_name(uv_translate_sys_error(e)), strerror(e));
assert(err == 0);
return NULL;
}
bare_abstract__set_nonblock(fd);
js_value_t *handle;
bare_abstract_server_t *server;
err = js_create_arraybuffer(env, sizeof(bare_abstract_server_t), (void **) &server, &handle);
assert(err == 0);
uv_loop_t *loop;
err = js_get_env_loop(env, &loop);
assert(err == 0);
server->fd = fd;
server->env = env;
server->closing = false;
err = uv_poll_init(loop, &server->poll, fd);
if (err < 0) {
close(fd);
err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
assert(err == 0);
return NULL;
}
server->poll.data = server;
err = js_create_reference(env, argv[2], 1, &server->ctx);
assert(err == 0);
err = js_create_reference(env, argv[3], 1, &server->on_connection);
assert(err == 0);
err = js_add_deferred_teardown_callback(env, bare_abstract__teardown, (void *) server, &server->teardown);
assert(err == 0);
err = uv_poll_start(&server->poll, UV_READABLE, bare_abstract__on_poll);
assert(err == 0);
return handle;
}
static js_value_t *
bare_abstract_close(js_env_t *env, js_callback_info_t *info) {
int err;
size_t argc = 1;
js_value_t *argv[1];
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
assert(err == 0);
assert(argc == 1);
bare_abstract_server_t *server;
err = js_get_arraybuffer_info(env, argv[0], (void **) &server, NULL);
assert(err == 0);
if (server->closing) return NULL;
server->closing = true;
if (server->fd >= 0) {
close(server->fd);
server->fd = -1;
}
uv_close((uv_handle_t *) &server->poll, bare_abstract__on_close);
return NULL;
}
static js_value_t *
bare_abstract_exports(js_env_t *env, js_value_t *exports) {
int err;
#define V(name, fn) \
{ \
js_value_t *val; \
err = js_create_function(env, name, -1, fn, NULL, &val); \
assert(err == 0); \
err = js_set_named_property(env, exports, name, val); \
assert(err == 0); \
}
V("connect", bare_abstract_connect)
V("listen", bare_abstract_listen)
V("close", bare_abstract_close)
#undef V
return exports;
}
BARE_MODULE(bare_abstract_socket, bare_abstract_exports)