From 8c9a91c93bccb70628024ff3749dd59782204bdc Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 14 Aug 2026 11:07:15 +0200 Subject: [PATCH] Improve error message when content files are damaged or truncated --- cmdline/state.c | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/cmdline/state.c b/cmdline/state.c index 6436c729..e27b04fc 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -2133,8 +2133,15 @@ static int fs_is_block_unsynced(struct snapraid_state* state, block_off_t pos) } /** - * Flush the file checking the final CRC. + * Flush the file checking the final CRC and abort on error. * We exploit the fact that the CRC is always stored in the last 4 bytes. + * + * Notice that on corruption, decoding error, or CRC mismatch we intentionally + * exit with a fatal error rather than automatically falling back to another + * content file. Automatic fallback could silently mask disk or memory corruption + * or inadvertently load a stale state. We require the user to explicitly rename + * or delete the corrupted file so SnapRAID will fall back to the surviving copy + * on the next run. */ static void decoding_error(const char* path, STREAM* f) { @@ -2145,7 +2152,9 @@ static void decoding_error(const char* path, STREAM* f) if (seof(f)) { /* LCOV_EXCL_START */ log_fatal(ECONTENT, "Unexpected end of content file '%s' at offset %" PRIi64 "\n", path, stell(f)); - log_fatal(ECONTENT, "This content file is truncated. Please use an alternate copy.\n"); + log_fatal(ECONTENT, "The content file '%s' is truncated!\n", path); + log_fatal(ECONTENT, "To recover, rename or delete it and rerun the command.\n"); + log_fatal(ECONTENT, "SnapRAID will automatically fall back to the next healthy copy.\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -2177,7 +2186,9 @@ static void decoding_error(const char* path, STREAM* f) if (crc_computed != crc_stored) { log_fatal(ECONTENT, "CRC mismatch in '%s'\n", path); - log_fatal(ECONTENT, "This content file is damaged! Please use an alternate copy.\n"); + log_fatal(ECONTENT, "The content file '%s' is damaged or corrupted (CRC mismatch)!\n", path); + log_fatal(ECONTENT, "To recover, rename or delete it and rerun the command.\n"); + log_fatal(ECONTENT, "SnapRAID will automatically fall back to the next healthy copy.\n"); exit(EXIT_FAILURE); } else { log_fatal(ECONTENT, "The CRC of the file is correct!\n"); @@ -3398,7 +3409,9 @@ static void state_read_content(struct snapraid_state* state, const char* path, S /* LCOV_EXCL_START */ /* here don't call decoding_error() because it's too late to get the crc */ log_fatal(errno, "Error reading the CRC in '%s' at offset %" PRIi64 "\n", path, stell(f)); - log_fatal(errno, "This content file is damaged! Use an alternate copy.\n"); + log_fatal(errno, "The content file '%s' is damaged or corrupted!\n", path); + log_fatal(errno, "To recover, rename or delete it and rerun the command.\n"); + log_fatal(errno, "SnapRAID will automatically fall back to the next healthy copy.\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -3407,7 +3420,9 @@ static void state_read_content(struct snapraid_state* state, const char* path, S /* LCOV_EXCL_START */ /* here don't call decoding_error() because it's too late to get the crc */ log_fatal(ECONTENT, "CRC mismatch in '%s'\n", path); - log_fatal(ECONTENT, "The content file is damaged! Please use an alternate copy.\n"); + log_fatal(ECONTENT, "The content file '%s' is damaged or corrupted (CRC mismatch)!\n", path); + log_fatal(ECONTENT, "To recover, rename or delete it and rerun the command.\n"); + log_fatal(ECONTENT, "SnapRAID will automatically fall back to the next healthy copy.\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -3440,7 +3455,9 @@ static void state_read_content(struct snapraid_state* state, const char* path, S if (!crc_checked) { /* LCOV_EXCL_START */ log_fatal(ECONTENT, "Reached the end of '%s' without finding the expected CRC\n", path); - log_fatal(ECONTENT, "This content file is truncated or damaged! Use an alternate copy.\n"); + log_fatal(ECONTENT, "The content file '%s' is truncated or damaged!\n", path); + log_fatal(ECONTENT, "To recover, rename or delete it and rerun the command.\n"); + log_fatal(ECONTENT, "SnapRAID will automatically fall back to the next healthy copy.\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -4548,7 +4565,15 @@ void state_read(struct snapraid_state* state) int ret; int c; - /* iterate over all the available content files and load the first one present */ + /* + * Iterate over all the available content files and load the first one present. + * + * Only missing files (ENOENT) trigger automatic failover to the next replica. + * If a file exists but is corrupted or damaged, reading will fail-fast with + * exit(EXIT_FAILURE) to alert the user to possible hardware or filesystem corruption. + * Once the user renames or removes the broken copy, the next run will fall back + * to the healthy replica and automatically re-create the missing one. + */ f = 0; node = tommy_list_head(&state->contentlist); while (node) {