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