diff --git a/src/open.c b/src/open.c index 7a4f31a..4f6e24b 100644 --- a/src/open.c +++ b/src/open.c @@ -302,12 +302,58 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m if(ctx->header.identifier != DIC_MAGIC && ctx->header.identifier != AARU_MAGIC) { - FATAL("Incorrect identifier for AaruFormat file: %8.8s", (char *)&ctx->header.identifier); - cleanup_open_failure(ctx); - aaruf_set_open_error(AARUF_ERROR_NOT_AARUFORMAT); + /* Primary header corrupt — try recovery footer backup header */ + TRACE("Primary header invalid, trying recovery footer backup header"); - TRACE("Exiting aaruf_open() = NULL"); - return NULL; + aaruf_fseek(ctx->imageStream, 0, SEEK_END); + int64_t file_size = aaruf_ftell(ctx->imageStream); + + if(file_size >= (int64_t)sizeof(AaruRecoveryFooter)) + { + aaruf_fseek(ctx->imageStream, + (aaru_off_t)(file_size - (int64_t)sizeof(AaruRecoveryFooter)), SEEK_SET); + AaruRecoveryFooter recovery_footer; + if(fread(&recovery_footer, sizeof(AaruRecoveryFooter), 1, ctx->imageStream) == 1 && + recovery_footer.footerMagic == AARU_RECOVERY_FOOTER_MAGIC) + { + /* Verify the backup header's CRC matches what was recorded */ + uint64_t backup_crc = aaruf_crc64_data( + (const uint8_t *)&recovery_footer.backupHeader, sizeof(AaruHeaderV2)); + if(backup_crc == recovery_footer.headerCrc64 && + (recovery_footer.backupHeader.identifier == DIC_MAGIC || + recovery_footer.backupHeader.identifier == AARU_MAGIC)) + { + TRACE("Recovery footer backup header valid, using it"); + memcpy(&ctx->header, &recovery_footer.backupHeader, sizeof(AaruHeaderV2)); + /* Seek past the header for the next read */ + aaruf_fseek(ctx->imageStream, (aaru_off_t)sizeof(AaruHeaderV2), SEEK_SET); + } + else + { + FATAL("Recovery footer backup header also invalid"); + cleanup_open_failure(ctx); + aaruf_set_open_error(AARUF_ERROR_NOT_AARUFORMAT); + TRACE("Exiting aaruf_open() = NULL"); + return NULL; + } + } + else + { + FATAL("No recovery footer found, cannot recover from corrupt header"); + cleanup_open_failure(ctx); + aaruf_set_open_error(AARUF_ERROR_NOT_AARUFORMAT); + TRACE("Exiting aaruf_open() = NULL"); + return NULL; + } + } + else + { + FATAL("Incorrect identifier for AaruFormat file: %8.8s", (char *)&ctx->header.identifier); + cleanup_open_failure(ctx); + aaruf_set_open_error(AARUF_ERROR_NOT_AARUFORMAT); + TRACE("Exiting aaruf_open() = NULL"); + return NULL; + } } if(ctx->header.imageMajorVersion < AARUF_VERSION_V2 && resume_mode) diff --git a/tests/erasure_coding.cpp b/tests/erasure_coding.cpp index 5fc1f44..0cc00c2 100644 --- a/tests/erasure_coding.cpp +++ b/tests/erasure_coding.cpp @@ -355,3 +355,23 @@ TEST_F(ErasureCodingTest, RecoverWithXOR_M1) ASSERT_TRUE(success) << "XOR recovery failed"; EXPECT_EQ(read_crc, golden_crc); } + +/** + * @test Corrupt the primary header (first 128 bytes), verify backup header from recovery footer is used. + */ +TEST_F(ErasureCodingTest, BackupHeaderRecovery) +{ + uint64_t golden_crc = 0; + ASSERT_TRUE(create_ec_image("data/mf2hd.aif", "ec_test_output.aif", 4, 2, &golden_crc)); + + ASSERT_TRUE(copy_file("ec_test_output.aif", "ec_test_corrupt.aif")); + + /* Corrupt the first 64 bytes of the file (part of AaruHeaderV2, including the magic) */ + corrupt_file("ec_test_corrupt.aif", 0, 64); + + /* Open should succeed using the backup header from the recovery footer */ + bool success = false; + uint64_t read_crc = compute_image_crc("ec_test_corrupt.aif", &success); + ASSERT_TRUE(success) << "Backup header recovery failed — could not open/read corrupted image"; + EXPECT_EQ(read_crc, golden_crc) << "CRC mismatch after backup header recovery"; +}