vmsig: management daemon, runtime endpoint lifecycle, roster, discovery, in-tree drivers, packaging

- core: runtime attach/detach of a per-endpoint adapter trio (runtime-safe add_adapter + vmsig_core_detach_endpoint, deferred reap)
- roster: VMSIG_EV_ROSTER + CAP_ROSTER, retained per-endpoint and replayed to late subscribers
- discovery: inotify trigger dir, vmid/endpoint slot allocator, host probe; vmsigd daemon with config + per-uid admission
- input driver and vgpu perception built in-tree; vgpu perception as a separate library
- memctx: own the supplied ro_fd (closed at detach)
- deb packaging: install rules, systemd unit, tmpfiles, default config
This commit is contained in:
2026-06-22 17:25:06 +03:00
parent 0d387a4249
commit 9bde398b6c
55 changed files with 4703 additions and 61 deletions
+4 -4
View File
@@ -1,10 +1,10 @@
#ifndef VMSIG_INPUT_H
#define VMSIG_INPUT_H
/* Private config of the input adapter (vmctl). cfg==NULL => stub mode. Armed mode
* (VMSIG_WITH_VMCTL) opens vmctl_open() and actuates for real. Injection is ALWAYS
* uinput (orphaned host uinput + external QEMU input-linux). qmp_path is kept for the
* SERVICE path (power/lifecycle via vmctl QMP), not for input injection. */
/* Private config of the input adapter (vmctl, in-tree at src/si/input/). cfg==NULL or
* stub!=0 => stub mode (ack without actuation). stub==0 opens vmctl_open() and actuates for
* real. Injection is ALWAYS uinput (orphaned host uinput + external QEMU input-linux);
* qmp_path is kept for the SERVICE path (power/lifecycle via vmctl QMP), not for injection. */
typedef struct {
int stub;
const char* qmp_path; /* for power/lifecycle (vmctl QMP); NOT input injection */
+5 -17
View File
@@ -3,19 +3,17 @@
* Mechanism (recommended): vmctl is a blocking QMP round-trip; we run it on a
* worker thread, completion ack via a completion-eventfd. The uinput path is a
* local instantaneous write; when armed it would be done inline (see comment in submit).
* Real actuation is under VMSIG_WITH_VMCTL; otherwise the stub acks (spine without a VM). */
* Real actuation when cfg.stub==0 (vmctl opened); otherwise the stub acks (spine without a VM).
* vmctl is the in-tree input driver (src/si/input/, absorbed); cfg.stub gates opening it. */
#include "vmsig_adapter.h"
#include "adapter_util.h"
#include "input.h"
#include "vmctl.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/epoll.h>
#ifdef VMSIG_WITH_VMCTL
#include "vmctl.h"
#endif
/* POD request/result of the worker. */
typedef struct {
int cmd; /* 0 = input event, 1 = lifecycle */
@@ -40,9 +38,7 @@ struct vmsig_adapter {
vmsig_emit emit;
vmsig_worker* worker;
const char* qmp_path; /* borrowed from cfg (valid through attach); SERVICE power/lifecycle */
#ifdef VMSIG_WITH_VMCTL
vmctl_t* vmctl;
#endif
vmctl_t* vmctl; /* NULL in stub mode (cfg.stub) — no actuator opened */
};
static int input_job(void* user, const void* reqp, void* resp) {
@@ -53,7 +49,6 @@ static int input_job(void* user, const void* reqp, void* resp) {
rs->corr = rq->corr;
rs->origin = rq->origin;
rs->noack = rq->noack;
#ifdef VMSIG_WITH_VMCTL
if (a->vmctl) {
int r = -1;
if (rq->cmd == 0) {
@@ -87,9 +82,8 @@ static int input_job(void* user, const void* reqp, void* resp) {
rs->ok = (r == 0);
return r;
}
#endif
(void)a;
rs->ok = 1; /* stub: ack without actuation */
rs->ok = 1; /* stub: ack without actuation (vmctl not opened) */
return 0;
}
@@ -109,7 +103,6 @@ static int in_attach(vmsig_adapter* a, const vmsig_emit* emit, vmsig_fd_reg* reg
a->worker = vmsig_worker_new(input_job, a, 1, 64); /* QMP is a serial channel, cap 64 */
if (!a->worker) return -1;
#ifdef VMSIG_WITH_VMCTL
if (!a->stub) {
/* armed: open the actuator. Injection is ALWAYS uinput (orphaned host uinput + external
* QEMU input-linux). PTR_BOTH gives both pointer forms a device (A=abs tablet, B=rel
@@ -125,7 +118,6 @@ static int in_attach(vmsig_adapter* a, const vmsig_emit* emit, vmsig_fd_reg* reg
a->vmctl = vmctl_open(&vcfg);
if (!a->vmctl) { vmsig_worker_free(a->worker); a->worker = NULL; return -1; }
}
#endif
reg[0].fd = vmsig_worker_evfd(a->worker);
reg[0].epoll_events = EPOLLIN;
@@ -166,7 +158,6 @@ static int in_submit(vmsig_adapter* a, const vmsig_event* ev) {
* actuate — nothing to hold). */
vmsig_input_held h;
memset(&h, 0, sizeof h);
#ifdef VMSIG_WITH_VMCTL
if (a->vmctl) {
const uint32_t capn = (uint32_t)(sizeof h.ent / sizeof h.ent[0]);
unsigned char bits[VMCTL_KEYS_SNAPSHOT_BYTES];
@@ -184,7 +175,6 @@ static int in_submit(vmsig_adapter* a, const vmsig_event* ev) {
else h.flags |= VMSIG_INPUT_HELD_TRUNC;
}
}
#endif
vmsig_event up;
memset(&up, 0, sizeof up);
up.kind = VMSIG_EV_INPUT_HELD; up.source = VMSIG_SRC_INPUT; up.dir = VMSIG_DIR_UP;
@@ -223,9 +213,7 @@ static int in_submit(vmsig_adapter* a, const vmsig_event* ev) {
static void in_close(vmsig_adapter* a) {
if (!a) return;
vmsig_worker_free(a->worker);
#ifdef VMSIG_WITH_VMCTL
if (a->vmctl) vmctl_close(a->vmctl);
#endif
free(a);
}
+4 -2
View File
@@ -8,8 +8,10 @@ typedef struct {
int stub; /* 1 => synthetic kcr3/RO-fd (spine without a VM) */
const char* ram_path; /* armed: path to guest RAM backing (NOT published outward) */
uint64_t low; /* below-4G split (vmie_win32_open / locator.low) */
int ro_fd; /* >=0 => infra supplied a pre-sealed RO-fd (policy); */
/* <0 => default: open(ram_path, O_RDONLY) / stub-memfd */
int ro_fd; /* >=0 => infra hands a pre-sealed RO-fd (policy); OWNERSHIP */
/* TRANSFERS to the adapter (closed in close()) — the */
/* caller dups first if it must keep its own copy. */
/* <0 => default: open(ram_path, O_RDONLY) / stub-memfd */
} vmsig_memctx_cfg;
/* Max SRC bytes per atomic gva_write (bounds the worker POD slot; mc_req header + src
+6 -2
View File
@@ -85,7 +85,7 @@ struct vmsig_adapter {
int stub;
const char* ram_path; /* armed: RAM-backing path (NOT published outward) */
uint64_t low;
int cfg_ro_fd; /* >=0 => infra-sealed RO-fd (policy); <0 => default */
int cfg_ro_fd; /* >=0 => infra-sealed RO-fd (owned by adapter, closed in mc_close); <0 => default */
vmsig_emit emit;
int registered; /* register_memctx already called */
vmsig_worker* worker; /* off-loop bootstrap + atomic writes */
@@ -398,7 +398,11 @@ static void mc_close(vmsig_adapter* a) {
if (a->win) vmie_win32_close(a->win); /* AFTER worker join: no in-flight gva_write */
#endif
if (a->stub_fd >= 0) close(a->stub_fd);
/* cfg_ro_fd belongs to the infrastructure (the open caller) — do NOT close it. */
/* ro_fd ownership transferred to the adapter at open(): close it here so a re-grant
* (detach + re-attach with a fresh infra ro_fd) does not leak the prior one. Infra
* that must keep its own copy dups before handing it in — symmetric to the holder
* side, which dups the borrowed RO-fd it receives. */
if (a->cfg_ro_fd >= 0) close(a->cfg_ro_fd);
free(a);
}