From e8726ce95aa30775f5e1cfe76d8692c97f7fceeb Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 8 Sep 2026 10:06:11 +0200 Subject: [PATCH] Add regression tests and document parity size requirements when adding a parity level --- Makefile.am | 47 +++++++++++++++++++++++++++++++++++++++++++++++ cmdline/sync.c | 5 +++++ 2 files changed, 52 insertions(+) diff --git a/Makefile.am b/Makefile.am index bca4b92e..3200ad63 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1320,6 +1320,53 @@ endif # !HAVE_MEMORY_CHECKER $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_SPLIT2) locate > /dev/null $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_SPLIT2) sync $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_SPLIT2) check + $(MSG) Test adding a parity level after an interrupted normal shrink + rm -rf bench/realloc-level + mkdir -p bench/realloc-level/disk1 + mkdir -p bench/realloc-level/disk2 + mkdir -p bench/realloc-level/disk3 + mkdir -p bench/realloc-level/disk4 + mkdir -p bench/realloc-level/disk5 + mkdir -p bench/realloc-level/disk6 + dd bs=1024 count=1 if=/dev/urandom of=bench/realloc-level/disk1/data + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL1) sync + dd bs=1024 count=1 if=/dev/urandom of=bench/realloc-level/disk1/tail + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL1) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) -d 2-parity fix + rm bench/realloc-level/disk1/tail + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL1) --test-kill-after-resize sync + cp bench/realloc-level/content bench/realloc-level/content.checkpoint + $(MSG) Accept the new level initialized at the old logical size + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) check + cp bench/realloc-level/content.checkpoint bench/realloc-level/content + $(MSG) Accept the new level initialized at the checkpoint allocation + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) -d 2-parity fix + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) check + cp bench/realloc-level/content.checkpoint bench/realloc-level/content + $(MSG) Accept the checkpoint allocation after adding files since fix + dd bs=1024 count=2 if=/dev/urandom of=bench/realloc-level/disk1/growth + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) check + $(MSG) Test fix leaving pending parity shorter than the checkpoint allocation + cp -p bench/realloc-level/disk1/data bench/realloc-level/disk2/data + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) sync + cp -p bench/realloc-level/disk1/growth bench/realloc-level/disk1/tail + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL1) --test-kill-after-sync sync + rm bench/realloc-level/disk2/data + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL1) fix + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) fix + cp bench/realloc-level/content bench/realloc-level/content.checkpoint + $(MSG) Resume after removing the pending additions + mv bench/realloc-level/disk1/tail bench/realloc-level/tail + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) check + $(MSG) Resume with the pending additions still allocated + mv bench/realloc-level/tail bench/realloc-level/disk1/tail + cp bench/realloc-level/content.checkpoint bench/realloc-level/content + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(REALLOC_LEVEL2) check $(MSG) Test interrupted normal shrink after adding a parity split rm -rf bench/realloc-split mkdir -p bench/realloc-split/disk1 diff --git a/cmdline/sync.c b/cmdline/sync.c index 51296587..95c9d17f 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -2031,6 +2031,11 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t * A full parity rebuild or reallocation from zero reconstructs truncated * parity. Tail-only reallocation still relies on the existing prefix, so a * truncation before parity_tail remains fatal. + * + * New levels need the same physical coverage of valid blocks, not an exact + * match with another level or the allocated extent. After an interrupted + * sync, fix leaves pending parity to sync and may truncate its unwritten + * tail. Sync will resize and regenerate that tail if it is still allocated. */ if (!state->opt.force_full && !(state->opt.force_realloc && state->opt.parity_tail == 0)) { /* if the parities are too small */