diff --git a/build.zig b/build.zig index f43835d..a6fc05a 100644 --- a/build.zig +++ b/build.zig @@ -59,6 +59,7 @@ pub fn build(b: *std.Build) void { "src/c/metronome.cpp", "src/c/signal.cpp", "src/c/timeout.cpp", + "src/c/unx.cpp", "src/c/trace.cpp", "src/cbor.cpp", "src/executor_asio.cpp", @@ -122,6 +123,7 @@ pub fn build(b: *std.Build) void { "test/spawn_exit.cpp", "test/tests.cpp", "test/timeout_test.cpp", + "test/unx_c_api.cpp", }, .flags = &cppflags }); tests.linkLibrary(lib); tests.linkLibrary(asio_dep.artifact("asio")); diff --git a/include/thespian/c/unx.h b/include/thespian/c/unx.h new file mode 100644 index 0000000..9448ccf --- /dev/null +++ b/include/thespian/c/unx.h @@ -0,0 +1,38 @@ +#pragma once + +// NOLINTBEGIN(modernize-use-trailing-return-type) +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// UNIX-domain socket utilities (acceptors/connectors) + +// mode indicates whether the path is a filesystem entry or an abstract socket. +typedef enum thespian_unx_mode { + THESPIAN_UNX_MODE_FILE = 0, + THESPIAN_UNX_MODE_ABSTRACT = 1, +} thespian_unx_mode; + +struct thespian_unx_acceptor_handle; +struct thespian_unx_acceptor_handle * +thespian_unx_acceptor_create(const char *tag); +int thespian_unx_acceptor_listen(struct thespian_unx_acceptor_handle *, + const char *path, thespian_unx_mode mode); +int thespian_unx_acceptor_close(struct thespian_unx_acceptor_handle *); +void thespian_unx_acceptor_destroy(struct thespian_unx_acceptor_handle *); + +struct thespian_unx_connector_handle; +struct thespian_unx_connector_handle * +thespian_unx_connector_create(const char *tag); +int thespian_unx_connector_connect(struct thespian_unx_connector_handle *, + const char *path, thespian_unx_mode mode); +int thespian_unx_connector_cancel(struct thespian_unx_connector_handle *); +void thespian_unx_connector_destroy(struct thespian_unx_connector_handle *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type) diff --git a/include/thespian/unx.hpp b/include/thespian/unx.hpp index be18b49..e323abd 100644 --- a/include/thespian/unx.hpp +++ b/include/thespian/unx.hpp @@ -23,6 +23,11 @@ struct acceptor { acceptor_ref ref; }; +// C++ helpers used by the C binding layer +void acceptor_listen(acceptor_impl *h, std::string_view path, mode m); +void acceptor_close(acceptor_impl *h); +void destroy_acceptor(acceptor_impl *h); + struct connector_impl; using connector_dtor = void (*)(connector_impl *); using connector_ref = std::unique_ptr; @@ -39,4 +44,9 @@ struct connector { connector_ref ref; }; +// C++ helpers for C API +void connector_connect(connector_impl *h, std::string_view path, mode m); +void connector_cancel(connector_impl *h); +void destroy_connector(connector_impl *h); + } // namespace thespian::unx diff --git a/src/c/unx.cpp b/src/c/unx.cpp new file mode 100644 index 0000000..e4ae3b1 --- /dev/null +++ b/src/c/unx.cpp @@ -0,0 +1,134 @@ +#include +#include +#include + +using thespian::unx::acceptor_impl; +using thespian::unx::connector_impl; +using thespian::unx::mode; + +extern "C" { + +namespace { +static auto to_cpp_mode(thespian_unx_mode m) -> mode { + return m == THESPIAN_UNX_MODE_ABSTRACT ? mode::abstract : mode::file; +} +} // namespace + +auto thespian_unx_acceptor_create(const char *tag) + -> struct thespian_unx_acceptor_handle * { + try { + auto *h = thespian::unx::acceptor::create(tag).ref.release(); + return reinterpret_cast(h); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_unx_acceptor_create error"); + return nullptr; + } +} + +auto thespian_unx_acceptor_listen(struct thespian_unx_acceptor_handle *handle, + const char *path, thespian_unx_mode m) + -> int { + try { + thespian::unx::acceptor_listen( + reinterpret_cast(handle), // NOLINT + path, to_cpp_mode(m)); + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_unx_acceptor_listen error"); + return -1; + } +} + +auto thespian_unx_acceptor_close(struct thespian_unx_acceptor_handle *handle) + -> int { + try { + thespian::unx::acceptor_close( + reinterpret_cast(handle)); // NOLINT + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_unx_acceptor_close error"); + return -1; + } +} + +void thespian_unx_acceptor_destroy( + struct thespian_unx_acceptor_handle *handle) { + try { + thespian::unx::destroy_acceptor( + reinterpret_cast(handle)); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + } catch (...) { + thespian_set_last_error("unknown thespian_unx_acceptor_destroy error"); + } +} + +auto thespian_unx_connector_create(const char *tag) + -> struct thespian_unx_connector_handle * { + try { + auto *h = thespian::unx::connector::create(tag).ref.release(); + return reinterpret_cast( // NOLINT + h); + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_unx_connector_create error"); + return nullptr; + } +} + +auto thespian_unx_connector_connect( + struct thespian_unx_connector_handle *handle, const char *path, + thespian_unx_mode m) -> int { + try { + thespian::unx::connector_connect( + reinterpret_cast(handle), // NOLINT + path, to_cpp_mode(m)); + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_unx_connector_connect error"); + return -1; + } +} + +auto thespian_unx_connector_cancel(struct thespian_unx_connector_handle *handle) + -> int { + try { + thespian::unx::connector_cancel( + reinterpret_cast(handle)); // NOLINT + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_unx_connector_cancel error"); + return -1; + } +} + +void thespian_unx_connector_destroy( + struct thespian_unx_connector_handle *handle) { + try { + thespian::unx::destroy_connector( + reinterpret_cast(handle)); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + } catch (...) { + thespian_set_last_error("unknown thespian_unx_connector_destroy error"); + } +} + +} // extern "C" diff --git a/src/instance.cpp b/src/instance.cpp index f0596d6..00ae6bd 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -1396,6 +1396,22 @@ void connector::connect(string_view path, mode m) { ref->connect(path, m); } void connector::cancel() { ref->cancel(); } +// C API helpers - must be defined where acceptor_impl/connector_impl are +// complete + +void acceptor_listen(acceptor_impl *h, std::string_view path, mode m) { + h->listen(path, m); +} +void acceptor_close(acceptor_impl *h) { h->close(); } + +void connector_connect(connector_impl *h, std::string_view path, mode m) { + h->connect(path, m); +} +void connector_cancel(connector_impl *h) { h->cancel(); } + +void destroy_acceptor(acceptor_impl *h) { delete h; } +void destroy_connector(connector_impl *h) { delete h; } + } // namespace unx namespace endpoint { diff --git a/src/thespian.zig b/src/thespian.zig index 70d4760..7a6e641 100644 --- a/src/thespian.zig +++ b/src/thespian.zig @@ -6,6 +6,7 @@ const c = @cImport({ @cInclude("thespian/c/metronome.h"); @cInclude("thespian/c/timeout.h"); @cInclude("thespian/c/signal.h"); + @cInclude("thespian/c/unx.h"); }); const c_posix = if (builtin.os.tag != .windows) @cImport({ @cInclude("thespian/backtrace.h"); @@ -741,6 +742,61 @@ pub const metronome = struct { } }; +pub const unx_mode = enum(u8) { + file = 0, + abstract = 1, +}; + +pub const unx_acceptor = struct { + handle: *c.struct_thespian_unx_acceptor_handle, + + const Self = @This(); + + pub fn init(tag_: []const u8) !Self { + return .{ .handle = c.thespian_unx_acceptor_create(tag_) orelse return log_last_error(error.ThespianUnxAcceptorInitFailed) }; + } + + pub fn listen(self: *const Self, path: []const u8, mode: unx_mode) !void { + const mval: u8 = @intCast(mode); + const ret = c.thespian_unx_acceptor_listen(self.handle, path, mval); + if (ret < 0) return error.ThespianUnxAcceptorListenFailed; + } + + pub fn close(self: *const Self) !void { + const ret = c.thespian_unx_acceptor_close(self.handle); + if (ret < 0) return error.ThespianUnxAcceptorCloseFailed; + } + + pub fn deinit(self: *const Self) void { + c.thespian_unx_acceptor_destroy(self.handle); + } +}; + +pub const unx_connector = struct { + handle: *c.struct_thespian_unx_connector_handle, + + const Self = @This(); + + pub fn init(tag_: []const u8) !Self { + return .{ .handle = c.thespian_unx_connector_create(tag_) orelse return log_last_error(error.ThespianUnxConnectorInitFailed) }; + } + + pub fn connect(self: *const Self, path: []const u8, mode: unx_mode) !void { + const mval: u8 = @intCast(mode); + const ret = c.thespian_unx_connector_connect(self.handle, path, mval); + if (ret < 0) return error.ThespianUnxConnectorConnectFailed; + } + + pub fn cancel(self: *const Self) !void { + const ret = c.thespian_unx_connector_cancel(self.handle); + if (ret < 0) return error.ThespianUnxConnectorCancelFailed; + } + + pub fn deinit(self: *const Self) void { + c.thespian_unx_connector_destroy(self.handle); + } +}; + pub const timeout = struct { handle: ?*c.struct_thespian_timeout_handle, diff --git a/test/tests.cpp b/test/tests.cpp index aba9ea0..0c3dffa 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -111,6 +111,7 @@ extern "C" auto runtestcase(const char *name) -> int { tests["perf_spawn"] = perf_spawn; tests["spawn_exit"] = spawn_exit; tests["timeout_test"] = timeout_test; + tests["unx_c_api"] = unx_c_api; env_t env{}; env_t log_env{}; diff --git a/test/tests.hpp b/test/tests.hpp index 4cf3e4b..791ab01 100644 --- a/test/tests.hpp +++ b/test/tests.hpp @@ -27,3 +27,4 @@ testcase perf_ring; testcase perf_spawn; testcase spawn_exit; testcase timeout_test; +testcase unx_c_api; diff --git a/test/unx_c_api.cpp b/test/unx_c_api.cpp new file mode 100644 index 0000000..049f4be --- /dev/null +++ b/test/unx_c_api.cpp @@ -0,0 +1,25 @@ +#include "tests.hpp" +#include + +using namespace thespian; + +// Very small smoke test for the new C API. We don't actually open sockets, +// just verify that the create/destroy functions work and return non-null. + +auto unx_c_api(thespian::context &ctx, bool &result, thespian::env_t env) + -> thespian::result { + (void)ctx; + (void)env; + + struct thespian_unx_acceptor_handle *a = thespian_unx_acceptor_create("tag"); + check(a != nullptr); + thespian_unx_acceptor_destroy(a); + + struct thespian_unx_connector_handle *c = + thespian_unx_connector_create("tag"); + check(c != nullptr); + thespian_unx_connector_destroy(c); + + result = true; + return ok(); +}