#pragma once /* * configcompare.h — cross-member active-configuration comparator * (config-upgrade-snapshot task 8.4, Requirement 4.2). * * Compares each cluster member's locally-held ACTIVE configuration content * hash, recomputed FRESH from that member's own on-disk configuration file * (never trusted from a cached/embedded tag), and reports whether every * member agrees. * * A new sibling tool to walcompare, NOT an extension of it: walcompare's * internal model is WAL-entry-indexed (lsn -> term, pcrc) -- an awkward fit * for a single (tag, sha256) value per member with no LSN dimension * (design.md "Regression Tooling"). Mirrors snapdiff_paths's exact * signature/reporting shape instead. * * Offline/file-based: no live RPC to any cluster member, consistent with * every other tool in this family (walcompare, snapdiff, snapverify). * * Strictly read-only: opens files O_RDONLY, mutates nothing. */ #include #include /* Bound on the number of members a single call can compare -- generous for * any real cluster size this codebase targets; a caller with more members * than this is almost certainly passing a malformed argument list. */ #define CONFIGCOMPARE_MAX_MEMBERS 32 typedef struct { int n; /* member count passed in */ int read_rc[CONFIGCOMPARE_MAX_MEMBERS]; /* 0 = resolved+hashed ok, * else -errno; entries at * or past a short-circuit * failure are unset (0) * and must be ignored -- * see all_present */ uint8_t sha256[CONFIGCOMPARE_MAX_MEMBERS][32]; /* valid only where * read_rc[i] == 0 */ int all_present; /* 1 iff every member (0..n-1) was resolved and * hashed successfully */ int all_identical; /* 1 iff all_present && every member's sha256 * equals the first member's */ int first_divergent_index; /* -1 if all_identical or !all_present; * else the first index (after the * reference, member 0) whose hash * differs from member 0's */ } configcompare_report_t; /* Compare the active-configuration content hash of `n` members. Each * `conf_paths[i]` is a member's `workdir/name` directory -- the SAME * directory persist.c's `read_active_config_bytes()` and config_repl.c's * `local_config_sha256()` both resolve `conf/current` (and the versioned * config file it points at) under, matching where this spec's snapshot-embed * step also reads from. NOT the raw `conf/current` file itself -- this * function resolves `/conf/current` and `/conf/` * internally, exactly like those two functions do. * * Prints a human-readable report to `out` (may be NULL) and fills `*rep` * (may be NULL). Returns: * 0 if every member's config was readable and all hashes are identical * 1 if every member's config was readable but at least one diverges * (see `*rep->first_divergent_index` for which) * <0 (negative errno) if `n < 2` (nothing to compare), `n` exceeds * CONFIGCOMPARE_MAX_MEMBERS, or any member's config could not be * resolved/read -- mirrors snapdiff_paths's early-return-on-open- * failure style: the first unreadable member short-circuits the scan, * and `*rep` is still filled with whatever was learned about the * members checked before the failure, so the report can say which * member and why. A member that could not be read is NOT evidence of * matching OR mismatching configuration -- it is a distinct third * outcome from "identical" and "divergent". */ int configcompare_paths(const char* const* conf_paths, int n, FILE* out, configcompare_report_t* rep);