/* startup_validation.c — Startup pre-flight validation functions. * * Implements validate_vcpus(), validate_startup_config(), * validate_peer_count(), validate_cert_files(), and * validate_heartbeat_config() for use in main.c and the controller * unit test suite. * * Requirements: 4.1 (peer count), 4.2 (cert pre-flight), * 4.3 (vcpus), 4.4 (vcpus key deprecation warn), * 2.4 (heartbeat timing validation) */ #define _GNU_SOURCE #include #include #include #include #include /* ── Req 4.3: --vcpus validation ─────────────────────────────────────── */ /* * validate_vcpus: returns 0 if vcpus >= 1, -EINVAL otherwise. * Called from main.c after argp_parse() sets arguments.vcpus. */ int validate_vcpus(int vcpus) { if (vcpus <= 0) { return -EINVAL; } return 0; } /* ── Req 4.4: vcpus key in config file — warn only ───────────────────── */ /* * validate_startup_config: scan config_path for a "vcpus" key. * If found, write a deprecation warning into warn_buf (must contain "vcpus"). * Always returns 0 — the presence of a vcpus key is not a fatal error. * warn_buf is left empty (first byte = '\0') when no vcpus key is found. * * The scan is line-based: a line contains the vcpus key if it starts with * optional whitespace followed by the literal string "vcpus". */ int validate_startup_config(const char *config_path, char *warn_buf, size_t warn_len) { if (warn_buf && warn_len > 0) { warn_buf[0] = '\0'; } if (!config_path) { return 0; } FILE *fp = fopen(config_path, "r"); if (!fp) { return 0; /* can't open — not a fatal error at this stage */ } char line[1024]; while (fgets(line, sizeof(line), fp)) { /* skip leading whitespace */ const char *p = line; while (*p == ' ' || *p == '\t') { p++; } if (strncmp(p, "vcpus", 5) == 0) { /* Found a vcpus key — write deprecation warning */ if (warn_buf && warn_len > 0) { snprintf(warn_buf, warn_len, "config file contains deprecated 'vcpus' key; " "use --vcpus CLI parameter instead"); } break; } } fclose(fp); return 0; } /* ── Req 4.1: even-N peer count rejection ─────────────────────────────── */ /* * validate_peer_count: returns 0 for valid odd counts (1, 3, 5, ...), * -EINVAL for even counts > 1. * n_peers == 1 is single-node mode — always valid. * Even counts > 1 cannot form a safe majority partition. */ int validate_peer_count(int n_peers) { if (n_peers > 1 && (n_peers % 2) == 0) { return -EINVAL; } return 0; } /* ── Req 4.2: TLS cert pre-flight ─────────────────────────────────────── */ /* * validate_cert_files: check that cert_path and key_path are readable. * On the first missing/unreadable file, sets err_buf to a message * containing the full file path and returns -ENOENT. * Returns 0 if both files are accessible. */ int validate_cert_files(const char *cert_path, const char *key_path, char *err_buf, size_t err_len) { if (access(cert_path, R_OK) != 0) { if (err_buf && err_len > 0) { snprintf(err_buf, err_len, "TLS certificate not found or unreadable: %s", cert_path); } return -ENOENT; } if (access(key_path, R_OK) != 0) { if (err_buf && err_len > 0) { snprintf(err_buf, err_len, "TLS key not found or unreadable: %s", key_path); } return -ENOENT; } return 0; } /* ── Req 2.4: heartbeat timing validation ─────────────────────────────── */ /* * validate_heartbeat_config: enforce heartbeat_timeout_ms >= 2 * heartbeat_interval_ms. * * A timeout smaller than two intervals means the standby could declare the * primary dead before a single missed heartbeat has elapsed — that is an * operator misconfiguration that must be caught at startup. * * Returns 0 when the relationship holds (valid config). * Returns -EINVAL when heartbeat_timeout_ms < 2 * heartbeat_interval_ms; * writes a human-readable error message into err_buf. */ int validate_heartbeat_config(uint32_t heartbeat_timeout_ms, uint32_t heartbeat_interval_ms, char *err_buf, size_t err_len) { if (err_buf && err_len > 0) { err_buf[0] = '\0'; } if (heartbeat_timeout_ms < 2 * heartbeat_interval_ms) { if (err_buf && err_len > 0) { snprintf(err_buf, err_len, "heartbeat_timeout_ms (%u) must be >= 2 * heartbeat_interval_ms (%u); " "current ratio %.2f is below the minimum of 2.0 — " "set heartbeat_timeout_ms >= %u", heartbeat_timeout_ms, heartbeat_interval_ms, heartbeat_interval_ms > 0 ? (double)heartbeat_timeout_ms / (double)heartbeat_interval_ms : 0.0, 2 * heartbeat_interval_ms); } return -EINVAL; } return 0; }