mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-08-11 14:16:41 +03:00
183 lines
7.9 KiB
C
183 lines
7.9 KiB
C
|
|
/* 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_ABS; act.code = 0; act.value = 100; /* demo: abs axis X = 100 */
|
||
|
|
memcpy(in.inln, &act, sizeof act);
|
||
|
|
printf(" DOWN CMD_INPUT ABS axis=0 val=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;
|
||
|
|
}
|