ha_send_snapshot_to_standby ignored the network send's return value in its chunk loop, silently truncating a transfer on a mid-stream error while still logging "complete" (bug-1386). Fixing the silent drop exposed a second, independent bug in the same pipeline: ha_handle_snapshot_chunk appended received chunks in arrival order instead of at their wire-carried chunk_offset, so out-of-order delivery across the vtask pool corrupted the reassembled file even though every chunk's bytes were individually correct (bug-1389). Fixed by checking every fallible call in the send loop and switching the receiver to pwrite-at-offset with a count-based completion tracker. Adds snapverify/snapdiff xce_test commands (offline checkpoint-file structural validator and byte-level comparator) used to diagnose bug-1389 and kept as standing diagnostic tools for future snapshot-transfer triage. g_drive_async_configpush_offload flipped back ON after both fixes, validated via 6-round chaotic + 3/3 force-snapshot soak (raft-drive-async task 3.3). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SDWVNQLKxqTVHh42cH7rV3
113 lines
3 KiB
C
113 lines
3 KiB
C
#define _GNU_SOURCE
|
|
#include "snapdiff.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#define SNAPDIFF_BUF_SIZE 65536
|
|
|
|
int snapdiff_paths(const char* path1, const char* path2, FILE* out,
|
|
snapdiff_report_t* rep)
|
|
{
|
|
if (!path1 || !path2)
|
|
return -EINVAL;
|
|
|
|
snapdiff_report_t local;
|
|
memset(&local, 0, sizeof(local));
|
|
local.diff_offset = -1;
|
|
|
|
int fd1 = open(path1, O_RDONLY);
|
|
if (fd1 < 0) {
|
|
int saved = errno;
|
|
if (out)
|
|
fprintf(out, "snapdiff: %s: %s\n", path1, strerror(saved));
|
|
return -saved;
|
|
}
|
|
int fd2 = open(path2, O_RDONLY);
|
|
if (fd2 < 0) {
|
|
int saved = errno;
|
|
close(fd1);
|
|
if (out)
|
|
fprintf(out, "snapdiff: %s: %s\n", path2, strerror(saved));
|
|
return -saved;
|
|
}
|
|
|
|
struct stat st1, st2;
|
|
if (fstat(fd1, &st1) != 0 || fstat(fd2, &st2) != 0) {
|
|
int saved = errno;
|
|
close(fd1);
|
|
close(fd2);
|
|
if (out)
|
|
fprintf(out, "snapdiff: fstat failed: %s\n", strerror(saved));
|
|
return -saved;
|
|
}
|
|
local.size1 = st1.st_size;
|
|
local.size2 = st2.st_size;
|
|
|
|
if (st1.st_size != st2.st_size) {
|
|
local.size_mismatch = 1;
|
|
close(fd1);
|
|
close(fd2);
|
|
if (rep)
|
|
*rep = local;
|
|
if (out)
|
|
fprintf(out,
|
|
"snapdiff: %s (%jd bytes) vs %s (%jd bytes): SIZE MISMATCH\n",
|
|
path1, (intmax_t)local.size1, path2, (intmax_t)local.size2);
|
|
return 1;
|
|
}
|
|
|
|
uint8_t buf1[SNAPDIFF_BUF_SIZE];
|
|
uint8_t buf2[SNAPDIFF_BUF_SIZE];
|
|
off_t off = 0;
|
|
int found_diff = 0;
|
|
|
|
for (;;) {
|
|
ssize_t n1 = read(fd1, buf1, sizeof(buf1));
|
|
ssize_t n2 = read(fd2, buf2, sizeof(buf2));
|
|
if (n1 <= 0 || n2 <= 0)
|
|
break;
|
|
ssize_t n = (n1 < n2) ? n1 : n2;
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
if (buf1[i] != buf2[i]) {
|
|
local.diff_offset = off + (off_t)i;
|
|
found_diff = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (found_diff)
|
|
break;
|
|
off += (off_t)n;
|
|
}
|
|
close(fd1);
|
|
close(fd2);
|
|
|
|
if (!found_diff) {
|
|
local.identical = 1;
|
|
if (rep)
|
|
*rep = local;
|
|
if (out)
|
|
fprintf(out, "snapdiff: %s vs %s: IDENTICAL (%jd bytes)\n", path1,
|
|
path2, (intmax_t)local.size1);
|
|
return 0;
|
|
}
|
|
|
|
local.chunk_index = local.diff_offset / SNAPDIFF_CHUNK_SIZE;
|
|
local.at_chunk_boundary = (local.diff_offset % SNAPDIFF_CHUNK_SIZE == 0);
|
|
|
|
if (rep)
|
|
*rep = local;
|
|
if (out)
|
|
fprintf(out,
|
|
"snapdiff: %s vs %s: DIFFER at offset %jd (chunk %jd%s)\n",
|
|
path1, path2, (intmax_t)local.diff_offset,
|
|
(intmax_t)local.chunk_index,
|
|
local.at_chunk_boundary
|
|
? " -- exactly at a chunk boundary, first byte of chunk"
|
|
: " -- NOT at a chunk boundary, mid-chunk");
|
|
return 1;
|
|
}
|