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
+156
View File
@@ -0,0 +1,156 @@
/* vmsigd.c — the vmsig management daemon.
*
* Owns the /dev/shm/vmsig discovery namespace and serves a unix-socket control plane over the
* signaling layer for the VMs found there. It wires nothing VM-specific: discovery hot-plugs
* each VM's adapter trio and publishes the roster; the daemon only supplies the loop, the
* discovery roots, the control socket, and a coarse per-uid admission policy.
*
* Real input/memctx actuation needs an armed library build (memctx -> vmie). A stub build
* still runs (socket/admission/discovery machinery), but memctx will not bootstrap.
*
* Usage: vmsigd [--config PATH] [--socket S] [--watch DIR] [--pve-conf DIR] [--qmp-dir DIR]
* [--slots PATH] [--foreground]
* precedence: argv > environment (VMSIGD_*) > config file > built-in defaults. */
#define _GNU_SOURCE
#include "vmsig.h"
#include "vmsig_socket.h"
#include "discovery.h"
#include "core_internal.h" /* core_add_source (in-repo daemon, intimate with the core) */
#include "vmsigd.h"
#include "vmsigd_admission.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/signalfd.h>
static vmsig_core* g_core;
static vmsigd_config g_cfg;
static char g_cfg_path[VMSIGD_PATH_MAX];
/* Audit trace: admissions/denials, lease and memctx grants — on the loop thread, to stderr
* (systemd routes stderr to the journal). */
static void on_audit(void* ud, const vmsig_audit* a) {
(void)ud;
static const char* k[] = {
"ADMIT", "REJECT", "DOWN_DENIED", "LEASE_GRANTED", "LEASE_DENIED",
"LEASE_REVOKED", "LEASE_RECLAIMED", "MEMCTX_GRANTED"
};
const char* name = (a->kind <= VMSIG_AUDIT_MEMCTX_GRANTED) ? k[a->kind] : "?";
fprintf(stderr, "vmsigd: audit %-14s principal=%u ep=%u cmd=%u detail=%u\n",
name, a->principal, a->endpoint, a->cmd, a->detail);
}
/* Signals arrive as fd readiness (signalfd) on the loop thread — no async-handler hazards.
* TERM/INT => graceful stop; HUP => reload ONLY the admission table from the config file
* (paths/socket/adapters are untouched; already-connected grants are not retroactively
* changed — a peer reconnects to pick up a changed entitlement). */
static void on_signal(void* user, uint32_t events) {
(void)events;
int sfd = *(int*)user;
struct signalfd_siginfo si;
while (read(sfd, &si, sizeof si) == (ssize_t)sizeof si) {
if (si.ssi_signo == SIGINT || si.ssi_signo == SIGTERM) {
vmsig_core_stop(g_core);
} else if (si.ssi_signo == SIGHUP) {
vmsigd_config fresh;
vmsigd_config_defaults(&fresh);
if (g_cfg_path[0] && vmsigd_config_parse_file(&fresh, g_cfg_path) == 0) {
memcpy(g_cfg.grants, fresh.grants, sizeof g_cfg.grants);
g_cfg.ngrants = fresh.ngrants; /* swap admission table only */
fprintf(stderr, "vmsigd: reloaded %d grant rule(s)\n", g_cfg.ngrants);
}
}
}
}
static const char* arg_val(int argc, char** argv, int* i) {
char* a = argv[*i];
char* eq = strchr(a, '=');
if (eq) return eq + 1;
if (*i + 1 < argc) { (*i)++; return argv[*i]; }
return "";
}
static void apply_env(vmsigd_config* c) {
const char* v;
if ((v = getenv("VMSIGD_SOCKET"))) snprintf(c->socket, sizeof c->socket, "%s", v);
if ((v = getenv("VMSIGD_WATCH"))) snprintf(c->watch, sizeof c->watch, "%s", v);
if ((v = getenv("VMSIGD_PVE_CONF"))) snprintf(c->pve_conf, sizeof c->pve_conf, "%s", v);
if ((v = getenv("VMSIGD_QMP_DIR"))) snprintf(c->qmp_dir, sizeof c->qmp_dir, "%s", v);
if ((v = getenv("VMSIGD_SLOTS"))) snprintf(c->slots, sizeof c->slots, "%s", v);
}
int main(int argc, char** argv) {
/* config path: argv --config > env > default. */
const char* cfg_path = getenv("VMSIGD_CONFIG");
if (!cfg_path) cfg_path = "/etc/vmsig/vmsigd.conf";
for (int i = 1; i < argc; i++)
if (!strncmp(argv[i], "--config", 8)) { cfg_path = arg_val(argc, argv, &i); }
vmsigd_config_defaults(&g_cfg);
vmsigd_config_parse_file(&g_cfg, cfg_path); /* missing file => defaults (not fatal) */
snprintf(g_cfg_path, sizeof g_cfg_path, "%s", cfg_path);
apply_env(&g_cfg);
for (int i = 1; i < argc; i++) {
char* a = argv[i];
if (!strncmp(a, "--config", 8)) { (void)arg_val(argc, argv, &i); }
else if (!strncmp(a, "--socket", 8)) snprintf(g_cfg.socket, sizeof g_cfg.socket, "%s", arg_val(argc, argv, &i));
else if (!strncmp(a, "--watch", 7)) snprintf(g_cfg.watch, sizeof g_cfg.watch, "%s", arg_val(argc, argv, &i));
else if (!strncmp(a, "--pve-conf", 10)) snprintf(g_cfg.pve_conf, sizeof g_cfg.pve_conf, "%s", arg_val(argc, argv, &i));
else if (!strncmp(a, "--qmp-dir", 9)) snprintf(g_cfg.qmp_dir, sizeof g_cfg.qmp_dir, "%s", arg_val(argc, argv, &i));
else if (!strncmp(a, "--slots", 7)) snprintf(g_cfg.slots, sizeof g_cfg.slots, "%s", arg_val(argc, argv, &i));
else if (!strcmp(a, "--foreground")) { /* default; systemd Type=simple */ }
else if (!strcmp(a, "-h") || !strcmp(a, "--help")) {
fprintf(stderr, "usage: %s [--config P][--socket S][--watch D][--pve-conf D]"
"[--qmp-dir D][--slots P][--foreground]\n", argv[0]);
return 0;
}
}
/* Signals via signalfd, serviced on the loop thread. SIGPIPE ignored (dead-peer writes). */
signal(SIGPIPE, SIG_IGN);
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGHUP);
sigprocmask(SIG_BLOCK, &mask, NULL);
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (sfd < 0) { perror("vmsigd: signalfd"); return 1; }
vmsig_ctx* ctx = vmsig_ctx_new();
if (!ctx) { fprintf(stderr, "vmsigd: ctx_new failed\n"); close(sfd); return 1; }
g_core = vmsig_core_new(ctx);
if (!g_core) { fprintf(stderr, "vmsigd: core_new failed\n"); vmsig_ctx_free(ctx); close(sfd); return 1; }
vmsig_core_set_audit(g_core, on_audit, NULL);
if (core_add_source(g_core, sfd, on_signal, &sfd, NULL) != 0) {
fprintf(stderr, "vmsigd: signal source registration failed\n");
vmsig_core_free(g_core); vmsig_ctx_free(ctx); close(sfd); return 1;
}
vmsig_discovery* disc = vmsig_discovery_new(
g_core, g_cfg.watch, g_cfg.pve_conf, g_cfg.qmp_dir,
g_cfg.slots[0] ? g_cfg.slots : NULL, NULL, NULL);
if (!disc) {
fprintf(stderr, "vmsigd: discovery_new(%s) failed\n", g_cfg.watch);
vmsig_core_free(g_core); vmsig_ctx_free(ctx); close(sfd); return 1;
}
vmsigd_admission adm = { &g_cfg, disc };
if (vmsig_socket_attach(g_core, g_cfg.socket, vmsigd_policy, &adm) != 0) {
fprintf(stderr, "vmsigd: socket_attach(%s) failed\n", g_cfg.socket);
vmsig_core_free(g_core); vmsig_ctx_free(ctx); close(sfd); return 1;
}
fprintf(stderr, "vmsigd: serving %s (watch=%s pve=%s qmp=%s) %d grant rule(s)\n",
g_cfg.socket, g_cfg.watch, g_cfg.pve_conf, g_cfg.qmp_dir, g_cfg.ngrants);
int rc = vmsig_core_run(g_core);
fprintf(stderr, "vmsigd: loop exit rc=%d\n", rc);
vmsig_core_free(g_core); /* reaps discovery (source on_free) + closes the socket listener */
vmsig_ctx_free(ctx);
close(sfd);
return rc;
}