/* config.c — vmsigd config parser (see vmsigd.h). INI-ish: `key = value` globals + repeated * `[grant uid=N]` stanzas. Pure libc; no core/vmie dependency (unit-testable in any build). */ #define _GNU_SOURCE #include "vmsigd.h" #include "vmsig_control.h" /* VMSIG_CAP_* */ #include #include #include #include #include void vmsigd_config_defaults(vmsigd_config* c) { memset(c, 0, sizeof *c); snprintf(c->socket, sizeof c->socket, "%s", "/run/vmsig/vmsigd.sock"); snprintf(c->watch, sizeof c->watch, "%s", "/dev/shm/vmsig"); snprintf(c->pve_conf, sizeof c->pve_conf, "%s", "/etc/pve/qemu-server"); snprintf(c->qmp_dir, sizeof c->qmp_dir, "%s", "/var/run/qemu-server"); snprintf(c->slots, sizeof c->slots, "%s", "/dev/shm/vmsig/.slots"); } uint32_t vmsigd_caps_from_str(const char* s) { static const struct { const char* k; uint32_t bit; } map[] = { { "observe", VMSIG_CAP_OBSERVE }, { "input", VMSIG_CAP_INPUT }, { "lifecycle", VMSIG_CAP_LIFECYCLE }, { "power", VMSIG_CAP_POWER }, { "vm", VMSIG_CAP_VM }, { "memctx", VMSIG_CAP_MEMCTX }, { "memwrite", VMSIG_CAP_MEMWRITE }, { "roster", VMSIG_CAP_ROSTER }, }; uint32_t mask = 0; while (s && *s) { while (*s == ',' || *s == ' ' || *s == '\t') s++; const char* w = s; while (*s && *s != ',' && *s != ' ' && *s != '\t') s++; size_t len = (size_t)(s - w); for (size_t i = 0; i < sizeof map / sizeof map[0]; i++) if (len == strlen(map[i].k) && strncmp(w, map[i].k, len) == 0) { mask |= map[i].bit; break; } } return mask; } /* Trim leading/trailing whitespace in place; returns the trimmed start. */ static char* trim(char* s) { while (*s == ' ' || *s == '\t' || *s == '\r') s++; char* e = s + strlen(s); while (e > s && (e[-1] == ' ' || e[-1] == '\t' || e[-1] == '\r' || e[-1] == '\n')) *--e = 0; return s; } static void set_path(char* dst, size_t cap, const char* v) { snprintf(dst, cap, "%s", v); } static void parse_vmids(vmsigd_grant_rule* g, const char* v) { g->all_vms = 0; g->nvmids = 0; if (strchr(v, '*')) { g->all_vms = 1; return; } while (*v) { while (*v == ',' || *v == ' ' || *v == '\t') v++; if (*v < '0' || *v > '9') { if (*v) v++; continue; } uint32_t id = (uint32_t)strtoul(v, NULL, 10); while (*v >= '0' && *v <= '9') v++; if (id && g->nvmids < VMSIGD_MAX_VMIDS) g->vmids[g->nvmids++] = id; } } int vmsigd_config_parse_buf(vmsigd_config* c, const char* buf) { if (!c || !buf) return -1; char* copy = strdup(buf); if (!copy) return -1; vmsigd_grant_rule* cur = NULL; /* current [grant] stanza, or NULL for globals */ char* save = NULL; for (char* line = strtok_r(copy, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) { char* p = trim(line); if (!*p || *p == '#' || *p == ';') continue; if (*p == '[') { cur = NULL; /* [grant uid=N] */ char* u = strstr(p, "uid="); if (u && c->ngrants < VMSIGD_MAX_GRANTS) { cur = &c->grants[c->ngrants++]; memset(cur, 0, sizeof *cur); cur->uid = (uint32_t)strtoul(u + 4, NULL, 10); } continue; } char* eq = strchr(p, '='); if (!eq) continue; *eq = 0; char* key = trim(p); char* val = trim(eq + 1); if (cur) { if (!strcmp(key, "vmids")) parse_vmids(cur, val); else if (!strcmp(key, "caps")) cur->cap_mask = vmsigd_caps_from_str(val); else if (!strcmp(key, "arb_prio")) cur->arb_prio = (uint32_t)strtoul(val, NULL, 10); } else { if (!strcmp(key, "socket")) set_path(c->socket, sizeof c->socket, val); else if (!strcmp(key, "watch")) set_path(c->watch, sizeof c->watch, val); else if (!strcmp(key, "pve_conf")) set_path(c->pve_conf, sizeof c->pve_conf, val); else if (!strcmp(key, "qmp_dir")) set_path(c->qmp_dir, sizeof c->qmp_dir, val); else if (!strcmp(key, "slots")) set_path(c->slots, sizeof c->slots, val); } } free(copy); return 0; } int vmsigd_config_parse_file(vmsigd_config* c, const char* path) { int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) return -1; char buf[16 * 1024]; size_t got = 0; for (;;) { ssize_t n = read(fd, buf + got, sizeof buf - 1 - got); if (n < 0) { close(fd); return -1; } if (n == 0) break; got += (size_t)n; if (got >= sizeof buf - 1) break; } close(fd); buf[got] = 0; return vmsigd_config_parse_buf(c, buf); }