From 76dd3d28ea88a42c6569c53f2710698c4279c402 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 8 Sep 2026 11:55:11 +0200 Subject: [PATCH] Clarify physical_reach_size semantics and non-contiguous parity holes left by fix --- cmdline/check.c | 9 +++++++++ cmdline/parity.c | 20 +++++++++++--------- cmdline/parity.h | 19 +++++++++++++------ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/cmdline/check.c b/cmdline/check.c index 4a9d8ae1..bd98c622 100644 --- a/cmdline/check.c +++ b/cmdline/check.c @@ -3078,6 +3078,15 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b */ partial = blockstart != 0 || blockcount != 0; + /* + * Fix is best-effort on each stripe. If a stripe cannot be recovered, it + * is left unfixed and processing continues with the following stripes. + * Consequently, repaired parity is not guaranteed to be contiguous: an + * unrecoverable stripe may remain as a hole between successfully repaired + * stripes. This is intentional, as stopping would prevent recovery of all + * subsequent recoverable stripes. + */ + blockmax = parity_allocated_size(state); size = blockmax * (data_off_t)state->block_size; diff --git a/cmdline/parity.c b/cmdline/parity.c index 1691bf09..b509e837 100644 --- a/cmdline/parity.c +++ b/cmdline/parity.c @@ -135,11 +135,11 @@ void parity_physical_reach_size(struct snapraid_parity_handle* handle, data_off_ /* * Compute the physical reach of the logical parity layout across splits. * - * physical_reach_size represents physical file presence, not parity validity. - * Parity is not necessarily contiguous or valid within this range: blocks - * may be invalid, stale, unwritten, or modified. Correctness is determined - * per-block by the content state and validated independently via data hashes - * and recomputation. + * physical_reach_size represents physical file presence, not parity validity + * or contiguity. Invalid, stale, unwritten, or sparse stripes may exist below + * this boundary, for example when fix cannot recover a stripe but continues + * with later ones. Such holes are allowed and need not be represented in the + * content state. * * For split parity, splits concatenate linearly. If an earlier split is * physically shorter than its logical size, logical offsets beyond that @@ -218,8 +218,9 @@ int parity_create(struct snapraid_parity_handle* handle, const struct snapraid_p * physical_reach_size does NOT imply that parity below this offset is valid. * Parity may be invalid, stale, unwritten, or sparse in any region below * this boundary. It only records the physical EOF boundary for I/O and - * truncation. Parity validity is tracked independently per block through - * the content state, data hashes, and recomputation. + * truncation. The physical contents below this boundary are not necessarily + * described by the content state. Recovery correctness is validated + * independently using data hashes and recomputation. * * While a handle is open, physical_reach_size can intentionally differ from EOF. * In particular, preallocation may extend EOF without extending physical_reach_size. @@ -810,8 +811,9 @@ int parity_open(struct snapraid_parity_handle* handle, const struct snapraid_par * physical_reach_size does NOT imply that parity below this offset is valid. * Parity may be invalid, stale, unwritten, or sparse in any region below * this boundary. It only records the physical EOF boundary for I/O and - * truncation. Parity validity is tracked independently per block through - * the content state, data hashes, and recomputation. + * truncation. The physical contents below this boundary are not necessarily + * described by the content state. Recovery correctness is validated + * independently using data hashes and recomputation. * * While a handle is open, physical_reach_size can intentionally differ from EOF. * In particular, preallocation may extend EOF without extending physical_reach_size. diff --git a/cmdline/parity.h b/cmdline/parity.h index 4f80e917..e2851f86 100644 --- a/cmdline/parity.h +++ b/cmdline/parity.h @@ -30,10 +30,15 @@ struct snapraid_split_handle { * * This is the physical truncation and read limit of the split. * - * physical_reach_size does NOT indicate that data below this offset is valid parity. - * Parity may be invalid, stale, unwritten, or sparse at any point below this - * boundary. Correctness is tracked independently per block in the content state - * and verified through data hashes and recomputation. + * physical_reach_size does NOT indicate that data below this offset is + * valid or contiguous parity. Parity may be invalid, stale, unwritten, + * or sparse at any point below this boundary. In particular, fix may + * leave an unrecoverable stripe unfixed and continue repairing later + * stripes, intentionally leaving holes inside the physical reach. + * + * Such holes are allowed and are not necessarily represented in the + * content state. Recovery correctness is validated independently using + * the stored data hashes or the available recovery validation. * * Data beyond physical_reach_size is disposable/preallocated and discarded by * parity_truncate(). A completed write advances physical_reach_size to the end of @@ -118,8 +123,10 @@ void parity_size(struct snapraid_parity_handle* handle, data_off_t* out_size); * If the physical_reach_size of an earlier split is smaller than its logical size, * later splits cannot extend the logical offset past that missing region. * - * Parity within this range is not necessarily valid or synchronized; - * per-block validity is tracked independently in the content state. + * Parity within this range is not necessarily valid, synchronized, or contiguous. + * In particular, fix may leave an unrecoverable stripe unfixed and continue with + * later stripes, so holes inside this range are allowed and are not necessarily + * represented in the content state. * * For example, with two 100 GiB splits, if the first has physical_reach_size 90 GiB * and the second has physical_reach_size 100 GiB, the result is 90 GiB, not 190 GiB.