Files
vatrog-vm-signaling/src/cli.c
T

183 lines
7.9 KiB
C
Raw Normal View History

/* cli.c — vmsig spine demonstrator (no real VM).
*
* Brings up the context + epoll core, attaches an in-proc control and a set of stub
* adapters (input/vmhost/memctx) on a single endpoint (VM 0). Proves the bidirectional seam:
* UP: SEAM_UP, VM_LIFECYCLE (vmhost stub tick), MEMCTX (kcr3+locator + RO-fd);
* DOWN: CMD_ACQUIRE+CMD_INPUT -> input adapter -> ACT_ACK (correlation);
* CMD_VM QUERY -> vmhost -> VM_LIFECYCLE (addressed reply).
* The address-space context arrives via MULTICAST: control receives kcr3 and a
* pre-opened O_RDONLY fd of the RAM region (control does NOT see ram_path; it mmaps
* the fd itself, write -> EACCES). (vgpu frame perception now lives in an out-of-repo
* S-lib that consumes this MEMCTX seam — not in signaling.)
* Shutdown: on SIGINT or automatically, once all paths are proven. */
#include "vmsig.h"
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
static vmsig_core* g_core;
static void on_sigint(int s) { (void)s; if (g_core) vmsig_core_stop(g_core); }
typedef struct {
vmsig_core* core;
void* ctl;
int total, lifecycles, acks, seams, memctx;
uint64_t last_kcr3;
uint32_t last_epoch;
int sent_first; /* sent acquire+input+vm on the first lifecycle tick */
} demo;
static const char* kind_name(vmsig_kind k) {
switch (k) {
case VMSIG_EV_SEAM_UP: return "SEAM_UP";
case VMSIG_EV_SEAM_DOWN: return "SEAM_DOWN";
case VMSIG_EV_VM_LIFECYCLE: return "VM_LIFECYCLE";
case VMSIG_EV_ACT_ACK: return "ACT_ACK";
case VMSIG_EV_MEMCTX: return "MEMCTX";
default: return "?";
}
}
/* Core -> control: address-space context + pre-opened O_RDONLY fd of the RAM region.
* Demonstrate RO: mmap(PROT_READ) ok, mmap(PROT_WRITE) -> EACCES. The fd is borrowed
* (closed by the core after the call) — here we mmap and immediately unmap. */
static int on_memctx(void* user, const vmsig_event* ev, int fd) {
demo* d = user;
const vmsig_memctx* m = (const vmsig_memctx*)ev->inln;
d->memctx++;
d->last_kcr3 = m->kcr3; d->last_epoch = m->epoch;
uint32_t nseg = 0;
const vmsig_memseg* segs = vmsig_memctx_segs(ev, &nseg);
printf(" UP MEMCTX ep=%u kcr3=%#llx low=%#llx epoch=%u nseg=%u rdonly=%d\n",
(unsigned)ev->endpoint, (unsigned long long)m->kcr3,
(unsigned long long)m->low, (unsigned)m->epoch, (unsigned)nseg,
(m->flags & VMSIG_MEMCTX_RDONLY) ? 1 : 0);
if (fd >= 0 && m->low) {
void* ro = mmap(NULL, (size_t)m->low, PROT_READ, MAP_SHARED, fd, 0);
if (ro != MAP_FAILED) {
void* rw = mmap(NULL, (size_t)m->low, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf(" RO-fd: mmap(PROT_READ) ok, mmap(PROT_WRITE) %s\n",
rw == MAP_FAILED ? "EACCES (RO enforced)" : "UNEXPECTEDLY succeeded!");
if (rw != MAP_FAILED) munmap(rw, (size_t)m->low);
munmap(ro, (size_t)m->low);
}
}
(void)segs;
return 0;
}
static int on_event(void* user, const vmsig_event* ev) {
demo* d = user;
d->total++;
switch (ev->kind) {
case VMSIG_EV_SEAM_UP: d->seams++; break;
case VMSIG_EV_ACT_ACK: d->acks++; break;
default: break;
}
if (ev->kind == VMSIG_EV_VM_LIFECYCLE) {
d->lifecycles++;
vmsig_vm_state vs; memcpy(&vs, ev->inln, sizeof vs);
printf(" UP VM_LIFECYCLE ep=%u state=%u%s\n",
(unsigned)ev->endpoint, (unsigned)vs.state, ev->origin ? " (reply)" : "");
} else if (ev->kind != VMSIG_EV_MEMCTX) { /* MEMCTX is printed in on_memctx */
printf(" UP %-12s src=%u ep=%u seq=%u prio=%u\n",
kind_name(ev->kind), (unsigned)ev->source, (unsigned)ev->endpoint,
(unsigned)ev->seq, (unsigned)ev->prio);
}
/* On the first lifecycle tick: acquire the INPUT lease, send input, and query VM status. */
if (ev->kind == VMSIG_EV_VM_LIFECYCLE && !ev->origin && !d->sent_first) {
d->sent_first = 1;
/* Input is a destructive class: first acquire the exclusive INPUT lease. */
vmsig_event acq;
memset(&acq, 0, sizeof acq);
acq.kind = VMSIG_EV_CMD_ACQUIRE; acq.source = VMSIG_SRC_INPUT; acq.dir = VMSIG_DIR_DOWN;
acq.prio = VMSIG_PRIO_HIGH; acq.endpoint = 0;
((vmsig_lease_req*)acq.inln)->cls = VMSIG_LEASE_INPUT;
printf(" DOWN CMD_ACQUIRE INPUT@ep0\n");
vmsig_inproc_send(d->ctl, &acq);
vmsig_event in;
memset(&in, 0, sizeof in);
in.kind = VMSIG_EV_CMD_INPUT; in.source = VMSIG_SRC_INPUT; in.dir = VMSIG_DIR_DOWN;
in.prio = VMSIG_PRIO_HIGH; in.endpoint = 0; in.corr = 0xC0FFEEu;
in.payload.flags = VMSIG_PL_INLINE;
vmsig_input act; memset(&act, 0, sizeof act); /* neutral public input contract */
act.kind = VMSIG_INPUT_MOVE_ABS; act.x = 100; act.y = 100; /* demo: abs pointer (100,100) */
memcpy(in.inln, &act, sizeof act);
printf(" DOWN CMD_INPUT MOVE_ABS x=100 y=100 corr=0x%X\n", (unsigned)in.corr);
vmsig_inproc_send(d->ctl, &in);
vmsig_event vm;
memset(&vm, 0, sizeof vm);
vm.kind = VMSIG_EV_CMD_VM; vm.source = VMSIG_SRC_VMHOST; vm.dir = VMSIG_DIR_DOWN;
vm.prio = VMSIG_PRIO_NORMAL; vm.endpoint = 0; vm.corr = 0x5Au;
vmsig_vm_cmd vc = { VMSIG_VMOP_QUERY };
memcpy(vm.inln, &vc, sizeof vc);
printf(" DOWN CMD_VM QUERY\n");
vmsig_inproc_send(d->ctl, &vm);
}
/* All paths proven — stop (for automated verification). */
if (d->memctx >= 1 && d->acks >= 1 && d->lifecycles >= 2) vmsig_core_stop(d->core);
return 0;
}
int main(void) {
vmsig_ctx* ctx = vmsig_ctx_new();
if (!ctx) { fprintf(stderr, "ctx_new failed\n"); return 1; }
vmsig_core* core = vmsig_core_new(ctx);
if (!core) { fprintf(stderr, "core_new failed\n"); vmsig_ctx_free(ctx); return 1; }
g_core = core;
signal(SIGINT, on_sigint);
demo d;
memset(&d, 0, sizeof d);
d.core = core;
vmsig_inproc_cfg ccfg;
memset(&ccfg, 0, sizeof ccfg);
ccfg.on_event = on_event;
ccfg.on_memctx = on_memctx;
ccfg.user = &d;
ccfg.sub.source_mask = 0; /* all sources */
ccfg.sub.prio_min = VMSIG_PRIO_BULK;
ccfg.sub.endpoint_mask = 0; /* all VMs */
void* ctl = vmsig_inproc_control_new(&ccfg);
if (!ctl) { fprintf(stderr, "control_new failed\n"); vmsig_core_free(core); vmsig_ctx_free(ctx); return 1; }
d.ctl = ctl;
/* Trusted in-proc control: full grant on VM 0 (the policy is set by the embedding
* program; for an out-of-process poller the grant would be issued upon authentication). */
vmsig_grant grant;
memset(&grant, 0, sizeof grant);
grant.principal = 1;
grant.endpoint_mask = 1u << 0;
grant.source_mask = 0xFFFFFFFFu;
grant.cap_mask = VMSIG_CAP_OBSERVE | VMSIG_CAP_INPUT | VMSIG_CAP_LIFECYCLE |
VMSIG_CAP_MEMCTX | VMSIG_CAP_POWER | VMSIG_CAP_VM;
vmsig_core_add_control(core, vmsig_inproc_control_ops(), ctl, &grant);
/* Single endpoint (VM 0), stub adapters (cfg = NULL). */
if (vmsig_core_add_adapter(core, vmsig_input_ops(), NULL, 0) < 0 ||
vmsig_core_add_adapter(core, vmsig_vmhost_ops(), NULL, 0) < 0 || /* stub QEMU plane */
vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) < 0) { /* stub AS context */
fprintf(stderr, "add_adapter failed\n");
vmsig_core_free(core); vmsig_ctx_free(ctx); return 1;
}
printf("vmsig_cli: loop started (Ctrl-C to stop)\n");
int rc = vmsig_core_run(core);
printf("vmsig_cli: loop finished rc=%d (events=%d seams=%d lifecycles=%d acks=%d memctx=%d kcr3=%#llx epoch=%u)\n",
rc, d.total, d.seams, d.lifecycles, d.acks, d.memctx,
(unsigned long long)d.last_kcr3, (unsigned)d.last_epoch);
vmsig_core_free(core);
vmsig_ctx_free(ctx);
return rc;
}