|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | +// |
| 4 | +// boj-health cartridge — ADR-0006 five-symbol Zig FFI implementation. |
| 5 | +// |
| 6 | +// Reference implementation: no external services, no env vars required. |
| 7 | +// Demonstrates the full Idris2 ABI → Zig FFI → boj-invoke → Elixir chain. |
| 8 | +// |
| 9 | +// Tools: |
| 10 | +// boj_health_status — JSON health blob: ok, version, uptime_ms |
| 11 | +// boj_health_ping — always {pong: true} |
| 12 | +// boj_health_version — version string only |
| 13 | +// |
| 14 | +// Runtime note: boj-invoke targets x86_64-linux-gnu (glibc) so it uses |
| 15 | +// DlDynLib (real dlopen). This .so can therefore safely use link_libc = true |
| 16 | +// (glibc) without a musl/glibc clash. The std_options override prevents |
| 17 | +// this .so from overwriting boj-invoke's SIGSEGV handler at dlopen time. |
| 18 | + |
| 19 | +const std = @import("std"); |
| 20 | +const shim = @import("cartridge_shim"); |
| 21 | + |
| 22 | +// Use the C clock_gettime directly — straightforward, no Zig TLS involved. |
| 23 | +const c = @cImport({ |
| 24 | + @cInclude("time.h"); |
| 25 | +}); |
| 26 | + |
| 27 | +// Suppress Zig's debug segfault signal handler so this .so does not |
| 28 | +// overwrite boj-invoke's handler when dlopened into the host Zig binary. |
| 29 | +pub const std_options: std.Options = .{ |
| 30 | + .enable_segfault_handler = false, |
| 31 | +}; |
| 32 | + |
| 33 | +var init_time_ms: i64 = 0; |
| 34 | +var init_done: bool = false; |
| 35 | + |
| 36 | +// ─── Five-symbol ADR-0006 ABI ──────────────────────────────────────────────── |
| 37 | + |
| 38 | +export fn boj_cartridge_name() callconv(.c) [*:0]const u8 { |
| 39 | + return "boj-health"; |
| 40 | +} |
| 41 | + |
| 42 | +export fn boj_cartridge_version() callconv(.c) [*:0]const u8 { |
| 43 | + return "0.1.0"; |
| 44 | +} |
| 45 | + |
| 46 | +export fn boj_cartridge_init() callconv(.c) c_int { |
| 47 | + var ts: c.struct_timespec = undefined; |
| 48 | + _ = c.clock_gettime(c.CLOCK_MONOTONIC, &ts); |
| 49 | + init_time_ms = ts.tv_sec * 1000 + @divTrunc(ts.tv_nsec, 1_000_000); |
| 50 | + init_done = true; |
| 51 | + return 0; |
| 52 | +} |
| 53 | + |
| 54 | +export fn boj_cartridge_deinit() callconv(.c) void { |
| 55 | + init_done = false; |
| 56 | +} |
| 57 | + |
| 58 | +export fn boj_cartridge_invoke( |
| 59 | + tool_name: [*c]const u8, |
| 60 | + json_args: [*c]const u8, |
| 61 | + out_buf: [*c]u8, |
| 62 | + in_out_len: [*c]usize, |
| 63 | +) callconv(.c) i32 { |
| 64 | + _ = json_args; |
| 65 | + if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS; |
| 66 | + |
| 67 | + if (shim.toolIs(tool_name, "boj_health_ping")) { |
| 68 | + return shim.writeResult(out_buf, in_out_len, "{\"pong\":true}"); |
| 69 | + } |
| 70 | + |
| 71 | + if (shim.toolIs(tool_name, "boj_health_version")) { |
| 72 | + return shim.writeResult(out_buf, in_out_len, "{\"version\":\"0.1.0\"}"); |
| 73 | + } |
| 74 | + |
| 75 | + if (shim.toolIs(tool_name, "boj_health_status")) { |
| 76 | + var ts: c.struct_timespec = undefined; |
| 77 | + _ = c.clock_gettime(c.CLOCK_MONOTONIC, &ts); |
| 78 | + const now_ms: i64 = ts.tv_sec * 1000 + @divTrunc(ts.tv_nsec, 1_000_000); |
| 79 | + const uptime: i64 = if (init_done) now_ms - init_time_ms else 0; |
| 80 | + |
| 81 | + var buf: [256]u8 = undefined; |
| 82 | + const body = std.fmt.bufPrint(&buf, |
| 83 | + "{{\"ok\":true,\"version\":\"0.1.0\",\"uptime_ms\":{d},\"cartridge\":\"boj-health\"}}", |
| 84 | + .{uptime}, |
| 85 | + ) catch return shim.RC_RUNTIME_ERROR; |
| 86 | + return shim.writeResult(out_buf, in_out_len, body); |
| 87 | + } |
| 88 | + |
| 89 | + return shim.RC_UNKNOWN_TOOL; |
| 90 | +} |
0 commit comments