feat(ha-config-bootstrap): implement ha_restore_term() for ha-term.bin read path (task 6.2)

This commit is contained in:
Rongsong Shen 2026-05-28 13:21:36 +00:00
parent 722d7824c4
commit 7c27ff1954
3 changed files with 91 additions and 1 deletions

View file

@ -720,6 +720,78 @@ void ha_persist_term(ha_state_t *ha, const char *workdir)
rename(tmp, path);
}
/*
* ha_restore_term: read {workdir}/ha-term.bin at startup and restore
* ha->term and ha->voted_for_* into ha_state_t.
*
* On missing file (ENOENT): no-op — term stays 0. Req 9.3.
* On CRC/magic mismatch: log WARN, zero ha->term and ha->voted_for_*. Req 9.2.
* On valid file: atomically restore ha->term; copy voted_for fields. Req 9.2.
*
* Returns 0 on success (including gracefully-handled file-not-found and CRC bad),
* -errno on unexpected I/O error.
* If workdir is NULL, function is a no-op (safe for unit tests without a workdir).
*/
int ha_restore_term(ha_state_t *ha, const char *workdir)
{
if (!workdir)
return 0;
char path[1024];
snprintf(path, sizeof(path), "%s/ha-term.bin", workdir);
int fd = open(path, O_RDONLY);
if (fd < 0) {
if (errno == ENOENT)
return 0; /* first boot — no file yet, term stays 0 (Req 9.3) */
return -errno;
}
ha_term_file_t f;
memset(&f, 0, sizeof(f));
ssize_t n = read(fd, &f, sizeof(f));
close(fd);
if (n != (ssize_t)sizeof(f)) {
WARN("HA: ha-term.bin short read (%zd/%zu) — ignoring, starting at term 0",
n, sizeof(f));
atomic_store_explicit(&ha->term, 0ULL, memory_order_release);
ha->voted_for_term = 0;
memset(ha->voted_for_name, 0, sizeof(ha->voted_for_name));
return 0;
}
/* Verify magic */
if (f.magic != HA_TERM_MAGIC) {
WARN("HA: ha-term.bin bad magic (0x%016" PRIx64 ") — ignoring, starting at term 0",
f.magic);
atomic_store_explicit(&ha->term, 0ULL, memory_order_release);
ha->voted_for_term = 0;
memset(ha->voted_for_name, 0, sizeof(ha->voted_for_name));
return 0;
}
/* Verify CRC32 */
size_t crc_len = offsetof(ha_term_file_t, crc32);
uint32_t computed = (uint32_t)crc32(0L, (const Bytef *)&f, (uInt)crc_len);
if (computed != f.crc32) {
WARN("HA: ha-term.bin CRC mismatch (expected 0x%08x got 0x%08x) — ignoring, starting at term 0",
computed, f.crc32);
atomic_store_explicit(&ha->term, 0ULL, memory_order_release);
ha->voted_for_term = 0;
memset(ha->voted_for_name, 0, sizeof(ha->voted_for_name));
return 0;
}
/* Valid file — restore state (Req 9.2) */
atomic_store_explicit(&ha->term, f.term, memory_order_release);
ha->voted_for_term = f.voted_for_term;
strncpy(ha->voted_for_name, f.voted_for_name, sizeof(ha->voted_for_name) - 1);
ha->voted_for_name[sizeof(ha->voted_for_name) - 1] = '\0';
return 0;
}
/* ── Vote Handler (Req 2.2–2.6, 9.1) ───────────────────────────────────────
*
* handle_request_vote: process an incoming OP_requestVote RPC.

View file

@ -294,6 +294,24 @@ int ha_dispatch_gate(ha_state_t *ha, int opcode);
*/
void ha_persist_term(ha_state_t *ha, const char *workdir);
/*
* ha_restore_term: read {workdir}/ha-term.bin at startup and restore
* ha->term and ha->voted_for_* into ha_state_t.
*
* On missing file (ENOENT): no-op — term stays 0, voted_for_name stays empty.
* Returns 0 (safe first-boot path). Req 9.3.
* On CRC/magic mismatch: log WARN, zero ha->term and ha->voted_for_* for
* safety and return 0. Req 9.2.
* On valid file: atomically restore ha->term; copy voted_for_term
* and voted_for_name. Returns 0. Req 9.2.
* On unexpected I/O error: return -errno.
*
* Must be called after ha_init() and before any election or vote processing.
* If workdir is NULL, the function is a no-op (safe for unit tests).
* Requirements: 9.1, 9.2, 9.3.
*/
int ha_restore_term(ha_state_t *ha, const char *workdir);
/*
* handle_request_vote: process an incoming OP_requestVote RPC.
* Applies term/voted_for/freshness gate; persists state via ha_persist_term().