Build configcompare_paths(), an offline sibling to snapdiff/walcompare that recomputes each member's active-config sha256 fresh from its own on-disk workdir/name/conf bytes and reports identical vs. divergent (Req 4.2). Wire it as a cmd_configcompare CLI subcommand in test.c, matching its siblings, after review caught it shipped as an unreachable library function in round 1. Extend snapverify to match the current on-disk checkpoint format: bump SNAP_FORMAT_VERSION to 2, replace the stale raw queue-frame decode with the capnp length-prefixed walk, add host-frame and config-bytes-section structural walks, and decode (not just presence-check) the header's cfgref tag/sha. Without this fix snapverify rejected every checkpoint written by the current codebase.
74 lines
4.1 KiB
C
74 lines
4.1 KiB
C
#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 <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
/* 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 `<path>/conf/current` and `<path>/conf/<versioned-file>`
|
|
* 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);
|