diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index 08256bc..9affa9a 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -66,6 +66,9 @@ typedef struct FluxCaptureMapEntry FluxCaptureMapEntry; * - If deduplicate == false, sectorHashMap may still be populated for bookkeeping but duplicates are stored * independently. * - If userDataDdtMini != NULL then userDataDdtBig == NULL (and vice versa) for a given level. + * - If no_user_data_ddt == true the image carries no user-data DDT (intentional, e.g. flux-only image): user_data_ddt, + * user_data_ddt2, cached_secondary_ddt2 and tape_ddt MUST all be NULL, and user_data_ddt_header.entries MUST be 0. + * Sector read/write APIs return AARUF_ERROR_USER_DATA_NOT_PRESENT in this state. */ #ifndef MD5_DIGEST_LENGTH @@ -197,6 +200,7 @@ typedef struct aaruformat_context int ddt_version; ///< DDT version in use (1=legacy, 2=v2 hierarchical). uint8_t shift; ///< Legacy overall shift (deprecated by data_shift/table_shift). bool in_memory_ddt; ///< True if primary (and possibly secondary) DDT loaded. + bool no_user_data_ddt; ///< True if image intentionally has no user-data DDT (e.g. flux-only image). When set, user_data_ddt*/tape_ddt MUST be NULL and user_data_ddt_header.entries MUST be 0. /* Optical auxiliary buffers (NULL if not present) */ uint8_t *sector_prefix; ///< Raw per-sector prefix (e.g., sync+header) uncorrected. diff --git a/include/aaruformat/errors.h b/include/aaruformat/errors.h index cef2306..ccb6249 100644 --- a/include/aaruformat/errors.h +++ b/include/aaruformat/errors.h @@ -73,6 +73,7 @@ #define AARUF_ERROR_CANNOT_ENCRYPT_SECTOR (-34) ///< AES sector encryption failed. #define AARUF_ERROR_CANNOT_DECRYPT_SECTOR (-35) ///< AES sector decryption failed. #define AARUF_ERROR_MISSING_ENCRYPTION_KEY (-36) ///< Required encryption key not present in media tags. +#define AARUF_ERROR_USER_DATA_NOT_PRESENT (-37) ///< Image has no user-data DDT (e.g. flux-only image); sector data is unavailable. /** @} */ /** \name Non-fatal sector status codes (non-negative) @@ -156,6 +157,8 @@ static inline const char *aaruformat_error_string(int code) return "Cannot decrypt sector"; case AARUF_ERROR_MISSING_ENCRYPTION_KEY: return "Missing encryption key"; + case AARUF_ERROR_USER_DATA_NOT_PRESENT: + return "User data not present (flux-only image)"; /* Status */ case AARUF_STATUS_OK: diff --git a/src/close_write.c b/src/close_write.c index c5df5f4..c326f2a 100644 --- a/src/close_write.c +++ b/src/close_write.c @@ -69,6 +69,9 @@ */ static int32_t write_cached_secondary_ddt(aaruformat_context *ctx) { + // Flux-only images carry no DDT. + if(ctx->no_user_data_ddt) return AARUF_STATUS_OK; + // Write cached secondary table to file end and update primary table entry with its position // Check if we have a cached table that needs to be written (either it has an offset or exists in memory) bool has_cached_secondary_ddt = @@ -296,6 +299,9 @@ static int32_t write_cached_secondary_ddt(aaruformat_context *ctx) */ static int32_t write_primary_ddt(aaruformat_context *ctx) { + // Flux-only images carry no DDT. + if(ctx->no_user_data_ddt) return AARUF_STATUS_OK; + // Write the cached primary DDT table back to its position in the file if(ctx->user_data_ddt_header.tableShift <= 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK; @@ -398,6 +404,9 @@ static int32_t write_primary_ddt(aaruformat_context *ctx) */ static int32_t write_single_level_ddt(aaruformat_context *ctx) { + // Flux-only images carry no DDT. + if(ctx->no_user_data_ddt) return AARUF_STATUS_OK; + // Write the single level DDT table block aligned just after the header if(ctx->user_data_ddt_header.tableShift != 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK; @@ -661,6 +670,9 @@ static int32_t write_tape_ddt(aaruformat_context *ctx) { if(!ctx->is_tape) return AARUF_STATUS_INVALID_CONTEXT; + // Flux-only images carry no DDT (defensive; tape images aren't flux-only today). + if(ctx->no_user_data_ddt) return AARUF_STATUS_OK; + // Traverse the tape DDT uthash and find the biggest key uint64_t max_key = 0; TapeDdtHashEntry *entry, *tmp; diff --git a/src/create.c b/src/create.c index 080023f..9675187 100644 --- a/src/create.c +++ b/src/create.c @@ -417,7 +417,28 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me ctx->library_major_version = LIBAARUFORMAT_MAJOR_VERSION; ctx->library_minor_version = LIBAARUFORMAT_MINOR_VERSION; - if(!is_tape) + /* Flux-only images carry no sector/DDT data: the caller signals this by passing zero + * for all sector counts. In that case skip DDT initialization entirely and lay out the + * image like a tape (header followed by aligned blocks). Sector read/write APIs return + * AARUF_ERROR_USER_DATA_NOT_PRESENT for such contexts. */ + const bool flux_only = !is_tape && user_sectors == 0 && negative_sectors == 0 && overflow_sectors == 0; + + if(flux_only) + { + TRACE("Initializing flux-only image (no DDT)"); + ctx->no_user_data_ddt = true; + + /* Record alignment in both the container header and the (otherwise unused) DDT header + * so existing helpers that consult either get a consistent value. */ + ctx->user_data_ddt_header.blockAlignmentShift = parsed_options.block_alignment; + ctx->header.blockAlignmentShift = parsed_options.block_alignment; + ctx->user_data_ddt_header.dataShift = parsed_options.data_shift; + + const uint64_t alignment_mask = (1ULL << parsed_options.block_alignment) - 1; + ctx->next_block_position = sizeof(AaruHeaderV2); + ctx->next_block_position = ctx->next_block_position + alignment_mask & ~alignment_mask; + } + else if(!is_tape) { // Initialize DDT2 TRACE("Initializing DDT2"); ctx->in_memory_ddt = true; @@ -541,7 +562,8 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me ctx->use_zstd = parsed_options.zstd; ctx->zstd_level = parsed_options.zstd_level; ctx->num_threads = parsed_options.num_threads; - if(ctx->deduplicate) + /* Flux-only images have no sector data to deduplicate against. */ + if(ctx->deduplicate && !ctx->no_user_data_ddt) ctx->sector_hash_map = create_map(ctx->user_data_ddt_header.blocks * 25 / 100); // 25% of total sectors ctx->rewinded = false; @@ -581,10 +603,12 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me ctx->is_writing = true; ctx->finalize_write = aaruf_finalize_write; - // Initialize dirty flags - all true by default for new images - ctx->dirty_secondary_ddt = true; - ctx->dirty_primary_ddt = true; - ctx->dirty_single_level_ddt = true; + // Initialize dirty flags - all true by default for new images. + // Flux-only images (no DDT) leave the DDT dirty flags cleared so finalize never + // attempts to serialize a non-existent table. + ctx->dirty_secondary_ddt = !ctx->no_user_data_ddt; + ctx->dirty_primary_ddt = !ctx->no_user_data_ddt; + ctx->dirty_single_level_ddt = !ctx->no_user_data_ddt; ctx->dirty_checksum_block = true; ctx->dirty_tracks_block = true; ctx->dirty_mode2_subheaders_block = true; diff --git a/src/open.c b/src/open.c index 4f6e24b..2554f4e 100644 --- a/src/open.c +++ b/src/open.c @@ -543,6 +543,7 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m } bool found_user_data_ddt = false; + bool has_flux_block = false; ctx->image_info.ImageSize = 0; for(i = 0; i < utarray_len(index_entries); i++) { @@ -663,6 +664,7 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m // Store the FluxDataBlock offset for lazy loading // Don't read flux entries during open - load them on-demand when actually needed // This avoids unnecessary I/O if flux data is never accessed + has_flux_block = true; break; default: TRACE("Unhandled block type %4.4s with data type %d is indexed to be at %" PRIu64 "", @@ -675,12 +677,19 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m if(!found_user_data_ddt) { - FATAL("Could not find user data deduplication table, aborting..."); - aaruf_close(ctx); - aaruf_set_open_error(AARUF_ERROR_CANNOT_READ_INDEX); + if(!has_flux_block) + { + FATAL("Could not find user data deduplication table or flux data, aborting..."); + aaruf_close(ctx); + aaruf_set_open_error(AARUF_ERROR_CANNOT_READ_INDEX); - TRACE("Exiting aaruf_open() = NULL"); - return NULL; + TRACE("Exiting aaruf_open() = NULL"); + return NULL; + } + + TRACE("No user data DDT found but flux data present, opening as flux-only image"); + ctx->no_user_data_ddt = true; + /* image_info.Sectors stays 0; biggestSectorSize stays 0; sector APIs will reject. */ } if(ctx->header.biggestSectorSize != 0) @@ -748,9 +757,12 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m ctx->header.lastWrittenTime = get_filetime_uint64(); ctx->image_info.LastModificationTime = ctx->header.lastWrittenTime; - // Calculate aligned next block position + // Calculate aligned next block position. For flux-only images there is no user-data DDT + // header, so use the alignment shift recorded in the container header instead. + const uint8_t resume_alignment_shift = + ctx->no_user_data_ddt ? ctx->header.blockAlignmentShift : ctx->user_data_ddt_header.blockAlignmentShift; aaruf_fseek(ctx->imageStream, 0, SEEK_END); - const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1; + const uint64_t alignment_mask = (1ULL << resume_alignment_shift) - 1; ctx->next_block_position = (uint64_t)aaruf_ftell(ctx->imageStream); // Start just after the header ctx->next_block_position = ctx->next_block_position + alignment_mask & ~alignment_mask; @@ -770,7 +782,8 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m ctx->compression_enabled = parsed_options.compress; ctx->lzma_dict_size = parsed_options.dictionary; ctx->deduplicate = parsed_options.deduplicate; - if(ctx->deduplicate) + /* Flux-only images have no DDT and therefore no sector deduplication map. */ + if(ctx->deduplicate && !ctx->no_user_data_ddt) ctx->sector_hash_map = create_map(ctx->user_data_ddt_header.blocks * 25 / 100); // 25% of total sectors // Cannot checksum a resumed file diff --git a/src/read.c b/src/read.c index 7ddbef7..976bef5 100644 --- a/src/read.c +++ b/src/read.c @@ -396,6 +396,14 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se return AARUF_ERROR_NOT_AARUFORMAT; } + // Flux-only images carry no DDT; sector reads cannot be served. + if(ctx->no_user_data_ddt) + { + *sector_status = SectorStatusNotDumped; + TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_USER_DATA_NOT_PRESENT (flux-only image)"); + return AARUF_ERROR_USER_DATA_NOT_PRESENT; + } + if(negative && sector_address > ctx->user_data_ddt_header.negative) { FATAL("Sector address out of bounds"); @@ -1472,6 +1480,14 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector_long(void *context, const uint64 return AARUF_ERROR_NOT_AARUFORMAT; } + // Flux-only images carry no DDT; sector reads cannot be served. + if(ctx->no_user_data_ddt) + { + if(sector_status != NULL) *sector_status = SectorStatusNotDumped; + TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_USER_DATA_NOT_PRESENT (flux-only image)"); + return AARUF_ERROR_USER_DATA_NOT_PRESENT; + } + if(negative && sector_address > ctx->user_data_ddt_header.negative) { FATAL("Sector address out of bounds"); @@ -2275,6 +2291,13 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector_tag(const void *context, const u return AARUF_ERROR_NOT_AARUFORMAT; } + // Flux-only images carry no DDT and therefore no per-sector tags. + if(ctx->no_user_data_ddt) + { + TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_USER_DATA_NOT_PRESENT (flux-only image)"); + return AARUF_ERROR_USER_DATA_NOT_PRESENT; + } + if(negative && sector_address > ctx->user_data_ddt_header.negative) { FATAL("Sector address out of bounds"); diff --git a/src/verify.c b/src/verify.c index fec2168..bd5f550 100644 --- a/src/verify.c +++ b/src/verify.c @@ -226,13 +226,18 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context) } uint64_t crc_length; - const unsigned int entry_count = utarray_len(index_entries); + const unsigned int entry_count = utarray_len(index_entries); + bool has_ddt_block = false; + bool has_flux_block = false; for(unsigned int i = 0; i < entry_count; i++) { IndexEntry *entry = utarray_eltptr(index_entries, i); TRACE("Checking block with type %4.4s at position %" PRIu64, (char *)&entry->blockType, entry->offset); + if(entry->blockType == DeDuplicationTable || entry->blockType == DeDuplicationTable2) has_ddt_block = true; + if(entry->blockType == FluxDataBlock) has_flux_block = true; + if(aaruf_fseek(ctx->imageStream, (aaru_off_t)entry->offset, SEEK_SET) != 0) { FATAL("Could not seek to block at offset %" PRIu64, entry->offset); @@ -468,6 +473,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context) } } + /* An image must contain either a user-data DDT or a flux data block (flux-only images are + * legitimate). If neither is present the index is structurally invalid. */ + if(!has_ddt_block && !has_flux_block) + { + FATAL("Image contains neither a user-data DDT nor a flux data block"); + status = AARUF_ERROR_CANNOT_READ_INDEX; + goto cleanup; + } + status = AARUF_STATUS_OK; cleanup: diff --git a/src/write.c b/src/write.c index 257048f..2227636 100644 --- a/src/write.c +++ b/src/write.c @@ -134,6 +134,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_ return AARUF_READ_ONLY; } + // Flux-only images carry no DDT and no sector storage. + if(ctx->no_user_data_ddt) + { + FATAL("Cannot write sector to flux-only image (no user-data DDT)"); + + TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_USER_DATA_NOT_PRESENT"); + return AARUF_ERROR_USER_DATA_NOT_PRESENT; + } + if(negative && sector_address > ctx->user_data_ddt_header.negative) { FATAL("Sector address out of bounds"); @@ -655,6 +664,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector_long(void *context, uint64_t se return AARUF_READ_ONLY; } + // Flux-only images carry no DDT and no sector storage. + if(ctx->no_user_data_ddt) + { + FATAL("Cannot write long sector to flux-only image (no user-data DDT)"); + + TRACE("Exiting aaruf_write_sector_long() = AARUF_ERROR_USER_DATA_NOT_PRESENT"); + return AARUF_ERROR_USER_DATA_NOT_PRESENT; + } + if(negative && sector_address > ctx->user_data_ddt_header.negative) { FATAL("Sector address out of bounds"); @@ -2305,6 +2323,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector_tag(void *context, const uint64 return AARUF_READ_ONLY; } + // Flux-only images carry no DDT and no per-sector tag storage. + if(ctx->no_user_data_ddt) + { + FATAL("Cannot write sector tag to flux-only image (no user-data DDT)"); + + TRACE("Exiting aaruf_write_sector_tag() = AARUF_ERROR_USER_DATA_NOT_PRESENT"); + return AARUF_ERROR_USER_DATA_NOT_PRESENT; + } + if(negative && sector_address > ctx->user_data_ddt_header.negative) { FATAL("Sector address out of bounds"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 79f58e4..d2f6471 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -104,6 +104,7 @@ add_executable(tests_run mode2_errored.cpp reed_solomon.cpp erasure_coding.cpp + flux_only_image.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../src/lib/aes128.c ${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_crypto.c ${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_encryption_map.c diff --git a/tests/flux_only_image.cpp b/tests/flux_only_image.cpp new file mode 100644 index 0000000..7a7003b --- /dev/null +++ b/tests/flux_only_image.cpp @@ -0,0 +1,176 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "../include/aaruformat.h" +#include "gtest/gtest.h" + +namespace +{ +/// Fill a buffer with a deterministic, easy-to-validate pattern. +void fill_pattern(uint8_t *buf, size_t length, uint32_t seed) +{ + for(size_t i = 0; i < length; ++i) buf[i] = static_cast((seed + i * 7u) & 0xFF); +} +} // namespace + +class FluxOnlyImageFixture : public testing::Test +{ +public: + FluxOnlyImageFixture() = default; + +protected: + void SetUp() override {} + void TearDown() override {} +}; + +/// Create a flux-only image (all sector counts zero), write a flux capture, close, reopen, verify +/// and read back the flux capture. Sector reads must be rejected with AARUF_ERROR_USER_DATA_NOT_PRESENT. +TEST_F(FluxOnlyImageFixture, create_open_read_verify_flux_only) +{ + constexpr const char *kPath = "test_flux_only.aif"; + + // Flux-only intent: user_sectors=0, negative=0, overflow=0, is_tape=false. + void *context = aaruf_create(kPath, /*media_type=*/1, /*sector_size=*/512, /*user_sectors=*/0, /*negative=*/0, + /*overflow=*/0, "compress=false", + reinterpret_cast("gtest"), 5, 0, 0, /*is_tape=*/false); + ASSERT_NE(context, nullptr) << "Failed to create flux-only image"; + + constexpr uint32_t kDataLen = 4096; + constexpr uint32_t kIndexLen = 64; + uint8_t data[kDataLen]; + uint8_t index[kIndexLen]; + fill_pattern(data, kDataLen, 0xA5u); + fill_pattern(index, kIndexLen, 0x5Au); + + int32_t flux_write = aaruf_write_flux_capture(context, + /*head=*/0, /*track=*/0, /*subtrack=*/0, + /*capture_index=*/0, + /*data_resolution=*/25000000ULL, + /*index_resolution=*/25000000ULL, data, kDataLen, index, kIndexLen); + ASSERT_EQ(flux_write, AARUF_STATUS_OK) << "Failed to write flux capture"; + + // Sector writes against a flux-only image must be rejected, not silently accepted. + uint8_t sector[512]; + fill_pattern(sector, sizeof(sector), 0u); + const int32_t sector_write = + aaruf_write_sector(context, /*sector_address=*/0, /*negative=*/false, sector, SectorStatusDumped, 512); + EXPECT_EQ(sector_write, AARUF_ERROR_USER_DATA_NOT_PRESENT) + << "Sector write to flux-only image should be rejected"; + + int32_t close_result = aaruf_close(context); + ASSERT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close flux-only image"; + + context = aaruf_open(kPath, /*resume=*/false, /*options=*/nullptr); + ASSERT_NE(context, nullptr) << "Failed to reopen flux-only image"; + + EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK) + << "Flux-only image should pass verification"; + + uint32_t read_index_len = kIndexLen; + uint32_t read_data_len = kDataLen; + uint8_t read_index[kIndexLen]; + uint8_t read_data[kDataLen]; + int32_t flux_read = aaruf_read_flux_capture(context, /*head=*/0, /*track=*/0, /*subtrack=*/0, + /*capture_index=*/0, read_index, &read_index_len, read_data, + &read_data_len); + ASSERT_EQ(flux_read, AARUF_STATUS_OK) << "Failed to read back flux capture"; + EXPECT_EQ(read_data_len, kDataLen) << "Unexpected flux data length"; + EXPECT_EQ(read_index_len, kIndexLen) << "Unexpected flux index length"; + EXPECT_EQ(memcmp(read_data, data, kDataLen), 0) << "Flux data round-trip mismatch"; + EXPECT_EQ(memcmp(read_index, index, kIndexLen), 0) << "Flux index round-trip mismatch"; + + // Sector reads against a flux-only image must surface the dedicated error code. + uint32_t sector_length = sizeof(sector); + uint8_t sector_status = 0; + int32_t sector_read = + aaruf_read_sector(context, /*sector_address=*/0, /*negative=*/false, sector, §or_length, §or_status); + EXPECT_EQ(sector_read, AARUF_ERROR_USER_DATA_NOT_PRESENT) + << "Sector read on flux-only image must return AARUF_ERROR_USER_DATA_NOT_PRESENT"; + EXPECT_EQ(sector_status, SectorStatusNotDumped) + << "Sector status should report not-dumped for flux-only images"; + + sector_length = sizeof(sector); + sector_status = 0; + int32_t sector_long_read = aaruf_read_sector_long(context, /*sector_address=*/0, /*negative=*/false, sector, + §or_length, §or_status); + EXPECT_EQ(sector_long_read, AARUF_ERROR_USER_DATA_NOT_PRESENT) + << "Long sector read on flux-only image must return AARUF_ERROR_USER_DATA_NOT_PRESENT"; + + uint32_t tag_length = sizeof(sector); + int32_t tag_read = + aaruf_read_sector_tag(context, /*sector_address=*/0, /*negative=*/false, sector, &tag_length, + kSectorTagCdSubchannel); + EXPECT_EQ(tag_read, AARUF_ERROR_USER_DATA_NOT_PRESENT) + << "Sector tag read on flux-only image must return AARUF_ERROR_USER_DATA_NOT_PRESENT"; + + close_result = aaruf_close(context); + EXPECT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close reopened flux-only image"; + + unlink(kPath); +} + +/// A standard image (with sectors) that has no flux data should still respond to flux reads with +/// AARUF_ERROR_FLUX_DATA_NOT_FOUND. Regression check for the "DDT-present, flux-absent" path. +TEST_F(FluxOnlyImageFixture, regular_image_flux_read_returns_not_found) +{ + constexpr const char *kPath = "test_regular_no_flux.aif"; + constexpr size_t kTotalSectors = 16; + + void *context = aaruf_create(kPath, /*media_type=*/1, /*sector_size=*/512, kTotalSectors, /*negative=*/0, + /*overflow=*/0, "compress=false", + reinterpret_cast("gtest"), 5, 0, 0, /*is_tape=*/false); + ASSERT_NE(context, nullptr) << "Failed to create regular image"; + + uint8_t sector[512]; + fill_pattern(sector, sizeof(sector), 0x42u); + for(size_t i = 0; i < kTotalSectors; ++i) + { + const int32_t write_result = aaruf_write_sector(context, i, false, sector, SectorStatusDumped, 512); + ASSERT_EQ(write_result, AARUF_STATUS_OK) << "Failed to write sector " << i; + } + + int32_t close_result = aaruf_close(context); + ASSERT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close regular image"; + + context = aaruf_open(kPath, false, nullptr); + ASSERT_NE(context, nullptr) << "Failed to reopen regular image"; + + EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK) << "Regular image should pass verification"; + + uint8_t read_data[16]; + uint8_t read_index[16]; + uint32_t read_data_len = sizeof(read_data); + uint32_t read_index_len = sizeof(read_index); + + int32_t flux_read = aaruf_read_flux_capture(context, 0, 0, 0, 0, read_index, &read_index_len, read_data, + &read_data_len); + EXPECT_EQ(flux_read, AARUF_ERROR_FLUX_DATA_NOT_FOUND) + << "Regular image without flux should return AARUF_ERROR_FLUX_DATA_NOT_FOUND"; + + close_result = aaruf_close(context); + EXPECT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close reopened regular image"; + + unlink(kPath); +}