/* 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 #include #include #include #include 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; }