diff --git a/cmdline/sync.c b/cmdline/sync.c index c1f92bc3..3d6d06c5 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -930,7 +930,7 @@ static void sync_parity_writer(struct snapraid_worker* worker, struct snapraid_t task->state = TASK_STATE_DONE; } -static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) +static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax, int* sync_durable) { struct snapraid_io io; struct snapraid_plan plan; @@ -961,6 +961,8 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari unsigned waiting_mac; bit_vect_t* block_enabled; + *sync_durable = 0; + /* get the present time */ now = time(0); @@ -1728,6 +1730,9 @@ end: goto bail; /* LCOV_EXCL_STOP */ } + + *sync_durable = 1; + if (state->opt.kill_after_sync) { log_fatal(EUSER, "WARNING! Killing due --test-kill-after-sync option.\n"); exit(EXIT_SUCCESS); @@ -1897,6 +1902,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t unsigned l; int skip_sync = 0; int parity_shrink = 0; + int sync_durable = 0; msg_progress("Initializing...\n"); @@ -2129,7 +2135,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t log_fatal(EUSER, "WARNING! Killing due --test-kill-before-sync option.\n"); exit(EXIT_SUCCESS); } else if (blockstart < blockmax) { - ret = state_sync_process(state, parity_handle, blockstart, blockmax); + ret = state_sync_process(state, parity_handle, blockstart, blockmax, &sync_durable); if (ret == -1) { /* LCOV_EXCL_START */ ++process_error; @@ -2138,6 +2144,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t } } else { msg_status("Nothing to sync.\n"); + sync_durable = 1; } } @@ -2149,27 +2156,53 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t log_fatal_errno(errno, lev_config_name(l)); ++process_error; + sync_durable = 0; /* continue, as we are already exiting */ /* LCOV_EXCL_STOP */ } } /* - * On close errors, abort without committing or persisting state, - * keeping the previous content file intact for recovery on the next run. + * Only a clean full sync makes all deallocation records obsolete. + * + * When process_error == 0, parity has been fully updated without the deleted + * files, so state_commit() clears the dealloclist. + * + * If any error occurred, state_commit() is skipped so the dealloclist remains + * intact. Calling state_write() below will then persist both the accumulated + * deallocation records and any error markings to the content file, keeping + * recovery data available across consecutive interrupted or failed runs. */ + if (process_error == 0) { + msg_status("Everything OK\n"); + + state_commit(state); + } + + /* + * Persist progress, error markings, and pending deallocations only after + * all parity handles are closed AND sync durability has been confirmed. + * + * If an asynchronous parity write, state_barrier(), or parity_close() failed, + * parity on disk is uncertain or incomplete. In that case, we must not publish + * in-memory BLK/EMPTY transitions to the content file, keeping the previous + * content file intact for recovery on the next run. + * + * Similarly, if sync was skipped before parity modification (e.g. on prehash + * failure), state_snapshot_pending() was bypassed (leaving SNAPSHOT_SCAN + * unpromoted), and legacy parity splits or newly added levels may still have + * split sizes set to PARITY_SIZE_INVALID (only resolved in the parity handles). + * Requiring sync durability ensures that snapshots, split sizes, and physical + * parity are completely consistent and serializable before invoking state_write(). + */ + if (sync_durable) { + if (state->need_write || state->opt.force_content_write) + state_write(state); + } + if (process_error != 0) return -1; - msg_status("Everything OK\n"); - - /* only a clean full sync makes all deallocation records obsolete */ - state_commit(state); - - /* persist progress and error markings only after all parity handles are closed */ - if (state->need_write || state->opt.force_content_write) - state_write(state); - return 0; }