Add doxygen documentation for closing functions.

This commit is contained in:
2025-10-03 22:27:49 +01:00
parent 66b24a5e6e
commit db6cd112b2

View File

@@ -15,6 +15,24 @@
* You should have received a copy of the GNU Lesser General Public * You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>. * License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/ */
/**
* @file close.c
* @brief Implements image finalization and resource cleanup for libaaruformat.
*
* This translation unit contains the logic that flushes any remaining in-memory
* structures (deduplication tables, checksum information, track metadata, MODE 2
* subheaders, sector prefix data and the global index) to the on-disk Aaru image
* when closing a context opened for writing. It also performs orderly teardown of
* dynamically allocated resources regardless of read or write mode.
*
* Helper (static) functions perform discrete serialization steps; the public
* entry point is ::aaruf_close(). All write helpers assume that the caller has
* already validated the context magic and (for write mode) written the initial
* provisional header at offset 0. Functions return libaaruformat status codes
* (AARUF_STATUS_OK on success or an AARUF_ERROR_* constant) except for
* ::aaruf_close(), which returns 0 on success and -1 on failure while setting
* errno.
*/
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
@@ -30,197 +48,42 @@
#include "log.h" #include "log.h"
/** /**
* @brief Closes an AaruFormat image context and frees resources. * @brief Flush a cached secondary (child) DeDuplication Table (DDT) to the image.
* *
* Closes the image file, frees memory, and releases all resources associated with the context. * When working with a multi-level DDT (i.e. primary table with tableShift > 0), a single
* For images opened in write mode, this function performs critical finalization operations * secondary table may be cached in memory while sectors belonging to its range are written.
* including writing cached DDT tables, updating the index, writing the final image header, * This function serializes the currently cached secondary table (if any) at the end of the
* and ensuring all data structures are properly persisted. It handles both single-level * file, aligning the write position to the DDT block alignment, and updates the corresponding
* and multi-level DDT structures and performs comprehensive cleanup of all allocated resources. * primary table entry to point to the new block-aligned location. The primary table itself is
* then re-written in-place (only its data array portion) to persist the updated pointer. The
* index is updated by removing any previous index entry for the same secondary table offset
* and inserting a new one for the freshly written table.
* *
* @param context Pointer to the aaruformat context to close. * CRC64 is computed for the serialized table contents and stored in both crc64 and cmpCrc64
* fields of the written DdtHeader2 (no compression is applied).
* *
* @return Returns one of the following status codes: * On return the cached secondary table buffers and bookkeeping fields (cachedSecondaryDdtSmall,
* @retval 0 Successfully closed the context and freed all resources. This is returned when: * cachedSecondaryDdtBig, cachedDdtOffset) are cleared.
* - The context is valid and properly initialized
* - For write mode: All cached data is successfully written to the image file
* - DDT tables (single-level or multi-level) are successfully written and indexed
* - The image index is successfully written with proper CRC validation
* - The final image header is updated with correct index offset and written
* - All memory resources are successfully freed
* - The image stream is closed without errors
* *
* @retval -1 Context validation failed. This occurs when: * @param ctx Pointer to an initialized aaruformatContext in write mode.
* - The context parameter is NULL * @return AARUF_STATUS_OK on success, or AARUF_ERROR_CANNOT_WRITE_HEADER if the
* - The context magic number doesn't match AARU_MAGIC (invalid context type) * secondary table or updated primary table cannot be flushed.
* - The errno is set to EINVAL to indicate invalid argument * @retval AARUF_STATUS_OK Success or no cached secondary DDT needed flushing.
* * @retval AARUF_ERROR_CANNOT_WRITE_HEADER Failed writing secondary table or rewriting primary table.
* @retval AARUF_ERROR_CANNOT_WRITE_HEADER (-16) Write operations failed. This occurs when: * @note If no cached secondary DDT is pending (detected via tableShift and cache pointers),
* - Cannot write the initial image header at position 0 (for write mode) * the function is a no-op returning AARUF_STATUS_OK.
* - Cannot write cached secondary DDT header or data to the image file * @internal
* - Cannot write primary DDT header or table data to the image file
* - Cannot write single-level DDT header or table data to the image file
* - Cannot write index header to the image file
* - Cannot write all index entries to the image file
* - Cannot update the final image header with the correct index offset
* - Any file I/O operation fails during the finalization process
*
* @retval Error codes from aaruf_close_current_block() may be propagated when:
* - The current writing buffer cannot be properly closed and written
* - Block finalization fails during the close operation
* - Compression or writing of the final block encounters errors
*
* @note Write Mode Finalization Process:
* - Writes the image header at the beginning of the file
* - Closes any open writing buffer (current block being written)
* - Writes cached secondary DDT tables and updates primary DDT references
* - Writes primary DDT tables (single-level or multi-level) with CRC validation
* - Writes the complete index with all block references at the end of the file
* - Updates the image header with the final index offset
*
* @note DDT Finalization:
* - For multi-level DDTs: Writes cached secondary tables and updates primary table pointers
* - For single-level DDTs: Writes the complete table directly to the designated position
* - Calculates and validates CRC64 checksums for all DDT data
* - Updates index entries to reference newly written DDT blocks
*
* @note Index Writing:
* - Creates IndexHeader3 structure with entry count and CRC validation
* - Writes all IndexEntry structures sequentially after the header
* - Aligns index position to block boundaries for optimal access
* - Updates the main image header with the final index file offset
*
* @note Memory Cleanup:
* - Frees all allocated DDT tables (userDataDdtMini, userDataDdtBig, cached secondary tables)
* - Frees sector metadata arrays (sectorPrefix, sectorSuffix, sectorSubchannel, etc.)
* - Frees media tag hash table and all associated tag data
* - Frees track entries, metadata blocks, and hardware information
* - Closes LRU caches for block headers and data
* - Unmaps memory-mapped DDT structures (Linux-specific)
*
* @note Platform-Specific Operations:
* - Linux: Unmaps memory-mapped DDT structures using munmap() if not loaded in memory
* - Other platforms: Standard memory deallocation only
*
* @note Error Handling Strategy:
* - Critical write failures return immediately with error codes
* - Memory cleanup continues even if some write operations fail
* - All allocated resources are freed regardless of write success/failure
* - File stream is always closed, even on error conditions
*
* @warning This function must be called to properly finalize write-mode images.
* Failing to call aaruf_close() on write-mode contexts will result in
* incomplete or corrupted image files.
*
* @warning After calling this function, the context pointer becomes invalid and
* should not be used. All operations on the context will result in
* undefined behavior.
*
* @warning For write-mode contexts, this function performs extensive file I/O.
* Ensure sufficient disk space and proper file permissions before calling.
*
* @warning The function sets errno to EINVAL for context validation failures
* but uses library-specific error codes for write operation failures.
*
* @note Checksum Block Writing:
* - Finalizes any active checksum calculations (MD5, SHA1, SHA256, SpamSum, BLAKE3)
* - Builds a ChecksumHeader followed by one or more ChecksumEntry records
* - Each checksum entry stores its algorithm type, length, and raw digest/value
* - The entire checksum block is aligned to the same block boundary policy as other appended blocks
* - Adds a corresponding index entry (blockType = ChecksumBlock)
* - Sets header.featureCompatible |= AARU_FEATURE_RW_BLAKE3 when a BLAKE3 digest is written
*
* @note Tracks Block Writing:
* - When track metadata (tracksHeader.entries > 0) is present, writes a TracksHeader plus all TrackEntry items
* - Aligns the block prior to writing if required by blockAlignmentShift
* - Appends an index entry (blockType = TracksBlock)
*
* @note Alignment Strategy:
* - Secondary DDT tables, checksum block, tracks block, and final index are all aligned to 2^blockAlignmentShift
* by padding forward to the next boundary when the current file position is unaligned
* - Alignment ensures predictable block addressing for deduplication and fast random access
*
* @note Deduplication Hash Map Cleanup:
* - If ctx->deduplicate is true and sectorHashMap exists, free_map() is invoked to release the hash map before
* final context deallocation
*
* @note Feature Flags:
* - BLAKE3 presence triggers setting the AARU_FEATURE_RW_BLAKE3 bit in the image header's featureCompatible mask
*
* @note Index CRC (Clarification):
* - A CRC64 is computed over the contiguous array of IndexEntry structures and stored in IndexHeader3.crc64;
* zero is stored if no entries exist or CRC context allocation fails
*
* @note Error Propagation (Clarification):
* - Failures after partially writing certain optional blocks (checksum, tracks) do not prevent subsequent memory
* cleanup, but may still return AARUF_ERROR_CANNOT_WRITE_HEADER if a critical header/table write fails
*
* @note Resource Ownership (Clarification):
* - All dynamically allocated checksum buffers (e.g., SpamSum string) and cryptographic contexts are finalized
* or freed prior to context memory release
*
* @note Mode 2 Subheaders Block:
* - If mode2_subheaders is present it is serialized as a DataBlock with type CompactDiscMode2Subheader, CRC64
* protected and indexed similarly to other appended metadata blocks.
*/ */
int aaruf_close(void *context) static int32_t write_cached_secondary_ddt(aaruformatContext *ctx)
{ {
TRACE("Entering aaruf_close(%p)", context);
mediaTagEntry *media_tag = NULL;
mediaTagEntry *tmp_media_tag = NULL;
if(context == NULL)
{
FATAL("Invalid context");
errno = EINVAL;
return -1;
}
aaruformatContext *ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
errno = EINVAL;
return -1;
}
if(ctx->isWriting)
{
TRACE("File is writing");
TRACE("Seeking to start of image");
// Write the header at the beginning of the file
fseek(ctx->imageStream, 0, SEEK_SET);
TRACE("Writing header at position 0");
if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1)
{
fclose(ctx->imageStream);
ctx->imageStream = NULL;
errno = AARUF_ERROR_CANNOT_WRITE_HEADER;
return -1;
}
// Close current block first
TRACE("Closing current block if any");
if(ctx->writingBuffer != NULL)
{
int error = aaruf_close_current_block(ctx);
if(error != AARUF_STATUS_OK) return error;
}
// Write cached secondary table to file end and update primary table entry with its position // 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) // 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 = bool has_cached_secondary_ddt =
ctx->userDataDdtHeader.tableShift > 0 && ctx->userDataDdtHeader.tableShift > 0 &&
(ctx->cachedDdtOffset != 0 || ctx->cachedSecondaryDdtSmall != NULL || ctx->cachedSecondaryDdtBig != NULL); (ctx->cachedDdtOffset != 0 || ctx->cachedSecondaryDdtSmall != NULL || ctx->cachedSecondaryDdtBig != NULL);
if(has_cached_secondary_ddt) if(!has_cached_secondary_ddt) return AARUF_STATUS_OK;
{
TRACE("Writing cached secondary DDT table to file"); TRACE("Writing cached secondary DDT table to file");
fseek(ctx->imageStream, 0, SEEK_END); fseek(ctx->imageStream, 0, SEEK_END);
@@ -298,8 +161,7 @@ int aaruf_close(void *context)
if(written_bytes == 1) if(written_bytes == 1)
{ {
// Update primary table entry to point to new location // Update primary table entry to point to new location
uint64_t new_secondary_table_block_offset = uint64_t new_secondary_table_block_offset = end_of_file >> ctx->userDataDdtHeader.blockAlignmentShift;
end_of_file >> ctx->userDataDdtHeader.blockAlignmentShift;
if(ctx->userDataDdtHeader.sizeType == SmallDdtSizeType) if(ctx->userDataDdtHeader.sizeType == SmallDdtSizeType)
ctx->userDataDdtMini[ctx->cachedDdtPosition] = (uint16_t)new_secondary_table_block_offset; ctx->userDataDdtMini[ctx->cachedDdtPosition] = (uint16_t)new_secondary_table_block_offset;
@@ -319,8 +181,7 @@ int aaruf_close(void *context)
for(unsigned int k = 0; k < utarray_len(ctx->indexEntries); k++) for(unsigned int k = 0; k < utarray_len(ctx->indexEntries); k++)
{ {
entry = (IndexEntry *)utarray_eltptr(ctx->indexEntries, k); entry = (IndexEntry *)utarray_eltptr(ctx->indexEntries, k);
if(entry && entry->offset == ctx->cachedDdtOffset && if(entry && entry->offset == ctx->cachedDdtOffset && entry->blockType == DeDuplicationTable2)
entry->blockType == DeDuplicationTable2)
{ {
TRACE("Found old DDT index entry at position %u, removing", k); TRACE("Found old DDT index entry at position %u, removing", k);
utarray_erase(ctx->indexEntries, k, 1); utarray_erase(ctx->indexEntries, k, 1);
@@ -381,11 +242,33 @@ int aaruf_close(void *context)
// Set position // Set position
fseek(ctx->imageStream, 0, SEEK_END); fseek(ctx->imageStream, 0, SEEK_END);
}
return AARUF_STATUS_OK;
}
/**
* @brief Write (flush) the multi-level primary DDT table header and data back to its file offset.
*
* This function is applicable only when a multi-level DDT is in use (tableShift > 0). It updates
* the header fields (identifier, type, compression, CRC, lengths) and writes first the header and
* then the entire primary table data block at ctx->primaryDdtOffset. The function also pushes an
* IndexEntry for the primary DDT into the in-memory index array so that later write_index_block()
* will serialize it.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode.
* @return AARUF_STATUS_OK on success; AARUF_ERROR_CANNOT_WRITE_HEADER if either the header or the
* table body cannot be written. Returns AARUF_STATUS_OK immediately if no primary table
* should be written (single-level DDT or table buffers absent).
* @retval AARUF_STATUS_OK Success or nothing to do (no multi-level primary table present).
* @retval AARUF_ERROR_CANNOT_WRITE_HEADER Failed writing header or primary table data.
* @internal
*/
static int32_t write_primary_ddt(aaruformatContext *ctx)
{
// Write the cached primary DDT table back to its position in the file // Write the cached primary DDT table back to its position in the file
if(ctx->userDataDdtHeader.tableShift > 0 && (ctx->userDataDdtMini != NULL || ctx->userDataDdtBig != NULL)) if(ctx->userDataDdtHeader.tableShift <= 0 || ctx->userDataDdtMini == NULL && ctx->userDataDdtBig == NULL)
{ return AARUF_STATUS_OK;
TRACE("Writing cached primary DDT table back to file"); TRACE("Writing cached primary DDT table back to file");
// Calculate CRC64 of the primary DDT table data first // Calculate CRC64 of the primary DDT table data first
@@ -457,11 +340,32 @@ int aaruf_close(void *context)
} }
else else
TRACE("Failed to write primary DDT table to file"); TRACE("Failed to write primary DDT table to file");
}
return AARUF_STATUS_OK;
}
/**
* @brief Serialize a single-level DDT (tableShift == 0) directly after its header.
*
* For single-level DDT configurations the entire table of sector references is contiguous.
* This routine computes a CRC64 for the table, populates all header metadata, writes the header
* at ctx->primaryDdtOffset followed immediately by the table, and registers the block in the
* index.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode with tableShift == 0.
* @return AARUF_STATUS_OK on success; AARUF_ERROR_CANNOT_WRITE_HEADER if serialization fails.
* Returns AARUF_STATUS_OK without action when the context represents a multi-level DDT
* or the table buffers are NULL.
* @retval AARUF_STATUS_OK Success or nothing to do (not single-level / buffers missing).
* @retval AARUF_ERROR_CANNOT_WRITE_HEADER Failed writing header or table data.
* @internal
*/
static int32_t write_single_level_ddt(aaruformatContext *ctx)
{
// Write the single level DDT table block aligned just after the header // Write the single level DDT table block aligned just after the header
if(ctx->userDataDdtHeader.tableShift == 0 && (ctx->userDataDdtMini != NULL || ctx->userDataDdtBig != NULL)) if(ctx->userDataDdtHeader.tableShift != 0 || ctx->userDataDdtMini == NULL && ctx->userDataDdtBig == NULL)
{ return AARUF_STATUS_OK;
TRACE("Writing single-level DDT table to file"); TRACE("Writing single-level DDT table to file");
// Calculate CRC64 of the primary DDT table data // Calculate CRC64 of the primary DDT table data
@@ -536,8 +440,28 @@ int aaruf_close(void *context)
} }
else else
TRACE("Failed to write single-level DDT table data to file"); TRACE("Failed to write single-level DDT table data to file");
}
return AARUF_STATUS_OK;
}
/**
* @brief Finalize any active checksum calculations and append a checksum block.
*
* This routine completes pending hash contexts (MD5, SHA-1, SHA-256, SpamSum, BLAKE3), marks the
* presence flags in ctx->checksums, and if at least one checksum exists writes a ChecksumBlock at
* the end of the image (block-aligned). Individual ChecksumEntry records are serialized for each
* available algorithm and the block is indexed. Feature flags (e.g. AARU_FEATURE_RW_BLAKE3) are
* updated if required.
*
* Memory ownership: for SpamSum a buffer is allocated here if a digest was being computed and is
* subsequently written without being freed inside this function (it will be freed during close).
* The BLAKE3 context is freed after finalization.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode.
* @internal
*/
static void write_checksum_block(aaruformatContext *ctx)
{
uint64_t alignment_mask; uint64_t alignment_mask;
uint64_t aligned_position; uint64_t aligned_position;
@@ -575,8 +499,8 @@ int aaruf_close(void *context)
bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 || bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 ||
ctx->checksums.hasSpamSum || ctx->checksums.hasBlake3; ctx->checksums.hasSpamSum || ctx->checksums.hasBlake3;
if(has_checksums) if(!has_checksums) return ;
{
ChecksumHeader checksum_header = {0}; ChecksumHeader checksum_header = {0};
checksum_header.identifier = ChecksumBlock; checksum_header.identifier = ChecksumBlock;
@@ -668,18 +592,30 @@ int aaruf_close(void *context)
utarray_push_back(ctx->indexEntries, &checksum_index_entry); utarray_push_back(ctx->indexEntries, &checksum_index_entry);
TRACE("Added checksum block index entry at offset %" PRIu64, checksum_position); TRACE("Added checksum block index entry at offset %" PRIu64, checksum_position);
} }
/**
* @brief Serialize the tracks metadata block and add it to the index.
*
* Writes a TracksHeader followed by the array of TrackEntry structures if any track entries are
* present (tracksHeader.entries > 0 and trackEntries not NULL). The block is aligned to the DDT
* block boundary and an IndexEntry is appended.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode.
* @internal
*/
static void write_tracks_block(aaruformatContext *ctx)
{
// Write tracks block // Write tracks block
if(ctx->tracksHeader.entries > 0 && ctx->trackEntries != NULL) if(ctx->tracksHeader.entries <= 0 || ctx->trackEntries == NULL) return ;
{
fseek(ctx->imageStream, 0, SEEK_END); fseek(ctx->imageStream, 0, SEEK_END);
long tracks_position = ftell(ctx->imageStream); long tracks_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed // Align index position to block boundary if needed
alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1; uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1;
if(tracks_position & alignment_mask) if(tracks_position & alignment_mask)
{ {
aligned_position = tracks_position + alignment_mask & ~alignment_mask; uint64_t aligned_position = tracks_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET); fseek(ctx->imageStream, aligned_position, SEEK_SET);
tracks_position = aligned_position; tracks_position = aligned_position;
} }
@@ -706,18 +642,32 @@ int aaruf_close(void *context)
TRACE("Added tracks block index entry at offset %" PRIu64, tracks_position); TRACE("Added tracks block index entry at offset %" PRIu64, tracks_position);
} }
} }
} }
/**
* @brief Serialize a MODE 2 (XA) subheaders data block.
*
* When Compact Disc Mode 2 form sectors are present, optional 8-byte subheaders (one per logical
* sector including negative / overflow ranges) are stored in an in-memory buffer. This function
* writes that buffer as a DataBlock of type CompactDiscMode2Subheader with CRC64 (no compression)
* and adds an IndexEntry.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode; ctx->mode2_subheaders must
* point to a buffer sized for the described sector span.
* @internal
*/
static void write_mode2_subheaders_block(aaruformatContext *ctx)
{
// Write MODE 2 subheader data block // Write MODE 2 subheader data block
if(ctx->mode2_subheaders != NULL) if(ctx->mode2_subheaders == NULL) return ;
{
fseek(ctx->imageStream, 0, SEEK_END); fseek(ctx->imageStream, 0, SEEK_END);
long mode2_subheaders_position = ftell(ctx->imageStream); long mode2_subheaders_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed // Align index position to block boundary if needed
alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1; uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1;
if(mode2_subheaders_position & alignment_mask) if(mode2_subheaders_position & alignment_mask)
{ {
aligned_position = mode2_subheaders_position + alignment_mask & ~alignment_mask; uint64_t aligned_position = mode2_subheaders_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET); fseek(ctx->imageStream, aligned_position, SEEK_SET);
mode2_subheaders_position = aligned_position; mode2_subheaders_position = aligned_position;
} }
@@ -728,7 +678,7 @@ int aaruf_close(void *context)
subheaders_block.type = CompactDiscMode2Subheader; subheaders_block.type = CompactDiscMode2Subheader;
subheaders_block.compression = None; subheaders_block.compression = None;
subheaders_block.length = subheaders_block.length =
(uint32_t)(ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors + ctx->userDataDdtHeader.overflow); (uint32_t)(ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors + ctx->userDataDdtHeader.overflow) * 8;
subheaders_block.cmpLength = subheaders_block.length; subheaders_block.cmpLength = subheaders_block.length;
// Calculate CRC64 // Calculate CRC64
@@ -753,17 +703,41 @@ int aaruf_close(void *context)
TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, mode2_subheaders_position); TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, mode2_subheaders_position);
} }
} }
} }
/**
* @brief Serialize the optional CD sector prefix block.
*
* The sector prefix corresponds to the leading bytes of a raw CD sector (synchronization pattern,
* address / header in MSF format and the mode byte) that precede the user data and any ECC/ECCP
* fields. It is unrelated to subchannel (PW) data, which is handled separately. If prefix data
* was collected (ctx->sector_prefix != NULL), this writes a DataBlock of type CdSectorPrefix
* containing exactly the bytes accumulated up to sector_prefix_offset. The block is CRC64
* protected, uncompressed, aligned to the DDT block boundary and indexed.
*
* Typical raw Mode 1 / Mode 2 sector layout (2352 bytes total):
* 12-byte sync pattern (00 FF FF FF FF FF FF FF FF FF FF 00)
* 3-byte address (Minute, Second, Frame in BCD)
* 1-byte mode (e.g., 0x01, 0x02)
* ... user data ...
* ... ECC / EDC ...
* The stored prefix encompasses the first 16 bytes (sync + address + mode) or whatever subset
* was gathered by the writer.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode.
* @internal
*/
static void write_sector_prefix(aaruformatContext *ctx)
{
if(ctx->sector_prefix == NULL) return;
if(ctx->sector_prefix != NULL)
{
fseek(ctx->imageStream, 0, SEEK_END); fseek(ctx->imageStream, 0, SEEK_END);
long prefix_position = ftell(ctx->imageStream); long prefix_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed // Align index position to block boundary if needed
alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1; uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1;
if(prefix_position & alignment_mask) if(prefix_position & alignment_mask)
{ {
aligned_position = prefix_position + alignment_mask & ~alignment_mask; uint64_t aligned_position = prefix_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET); fseek(ctx->imageStream, aligned_position, SEEK_SET);
prefix_position = aligned_position; prefix_position = aligned_position;
} }
@@ -787,8 +761,7 @@ int aaruf_close(void *context)
size_t written_bytes = fwrite(ctx->sector_prefix, prefix_block.length, 1, ctx->imageStream); size_t written_bytes = fwrite(ctx->sector_prefix, prefix_block.length, 1, ctx->imageStream);
if(written_bytes == 1) if(written_bytes == 1)
{ {
TRACE("Successfully wrote CD sector prefix subheaders block (%" PRIu64 " bytes)", TRACE("Successfully wrote CD sector prefix block (%" PRIu64 " bytes)", prefix_block.length);
prefix_block.length);
// Add prefix block to index // Add prefix block to index
TRACE("Adding CD sector prefix block to index"); TRACE("Adding CD sector prefix block to index");
IndexEntry prefix_index_entry; IndexEntry prefix_index_entry;
@@ -796,21 +769,39 @@ int aaruf_close(void *context)
prefix_index_entry.dataType = CdSectorPrefix; prefix_index_entry.dataType = CdSectorPrefix;
prefix_index_entry.offset = prefix_position; prefix_index_entry.offset = prefix_position;
utarray_push_back(ctx->indexEntries, &prefix_index_entry); utarray_push_back(ctx->indexEntries, &prefix_index_entry);
TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, prefix_position); TRACE("Added CD sector prefix block index entry at offset %" PRIu64, prefix_position);
}
} }
} }
}
/**
* @brief Serialize the accumulated index entries at the end of the image and back-patch the header.
*
* All previously written structural blocks push their IndexEntry into ctx->indexEntries. This
* function collects them, writes an IndexHeader3 followed by each IndexEntry, computes CRC64 over
* the entries, and then updates the main AaruHeaderV2 (at offset 0) with the index offset. The
* index itself is aligned to the DDT block boundary. No previous index chaining is currently
* implemented (index_header.previous = 0).
*
* @param ctx Pointer to an initialized aaruformatContext in write mode.
* @return AARUF_STATUS_OK on success; AARUF_ERROR_CANNOT_WRITE_HEADER if the index header, any
* entry, or the header back-patch fails.
* @retval AARUF_STATUS_OK Index written and header updated.
* @retval AARUF_ERROR_CANNOT_WRITE_HEADER Failed writing index header, entries, or updating main header.
* @internal
*/
static int32_t write_index_block(aaruformatContext *ctx)
{
// Write the complete index at the end of the file // Write the complete index at the end of the file
TRACE("Writing index at the end of the file"); TRACE("Writing index at the end of the file");
fseek(ctx->imageStream, 0, SEEK_END); fseek(ctx->imageStream, 0, SEEK_END);
long index_position = ftell(ctx->imageStream); long index_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed // Align index position to block boundary if needed
alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1; uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1;
if(index_position & alignment_mask) if(index_position & alignment_mask)
{ {
aligned_position = index_position + alignment_mask & ~alignment_mask; uint64_t aligned_position = index_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET); fseek(ctx->imageStream, aligned_position, SEEK_SET);
index_position = aligned_position; index_position = aligned_position;
TRACE("Aligned index position to %" PRIu64, aligned_position); TRACE("Aligned index position to %" PRIu64, aligned_position);
@@ -833,7 +824,8 @@ int aaruf_close(void *context)
aaruf_crc64_final(index_crc64_context, &index_header.crc64); aaruf_crc64_final(index_crc64_context, &index_header.crc64);
TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64); TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64);
} }
else { index_header.crc64 = 0; } else
index_header.crc64 = 0;
// Write index header // Write index header
if(fwrite(&index_header, sizeof(IndexHeader3), 1, ctx->imageStream) == 1) if(fwrite(&index_header, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
@@ -848,7 +840,6 @@ int aaruf_close(void *context)
for(entry = (IndexEntry *)utarray_front(ctx->indexEntries); entry != NULL; for(entry = (IndexEntry *)utarray_front(ctx->indexEntries); entry != NULL;
entry = (IndexEntry *)utarray_next(ctx->indexEntries, entry)) entry = (IndexEntry *)utarray_next(ctx->indexEntries, entry))
{
if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1) if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1)
{ {
entries_written++; entries_written++;
@@ -860,7 +851,6 @@ int aaruf_close(void *context)
TRACE("Failed to write index entry %zu", entries_written); TRACE("Failed to write index entry %zu", entries_written);
break; break;
} }
}
if(entries_written == index_header.entries) if(entries_written == index_header.entries)
{ {
@@ -873,9 +863,7 @@ int aaruf_close(void *context)
// Seek back to beginning and rewrite header // Seek back to beginning and rewrite header
fseek(ctx->imageStream, 0, SEEK_SET); fseek(ctx->imageStream, 0, SEEK_SET);
if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) == 1) if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) == 1)
{
TRACE("Successfully updated header with index offset"); TRACE("Successfully updated header with index offset");
}
else else
{ {
TRACE("Failed to update header with index offset"); TRACE("Failed to update header with index offset");
@@ -896,6 +884,121 @@ int aaruf_close(void *context)
return AARUF_ERROR_CANNOT_WRITE_HEADER; return AARUF_ERROR_CANNOT_WRITE_HEADER;
} }
return AARUF_STATUS_OK;
}
/**
* @brief Close an Aaru image context, flushing pending data structures and releasing resources.
*
* Public API entry point used to finalize an image being written or simply dispose of a context
* opened for reading. For write-mode contexts (ctx->isWriting true) the function performs the
* following ordered steps:
* 1. Rewrite the (possibly updated) main header at offset 0.
* 2. Close any open data block via aaruf_close_current_block().
* 3. Flush a cached secondary DDT (multi-level) if pending.
* 4. Flush either the primary DDT (multi-level) or the single-level DDT table.
* 5. Finalize and append checksum block(s) for all enabled algorithms.
* 6. Write auxiliary metadata blocks: tracks, MODE 2 subheaders, sector prefix.
* 7. Serialize the global index and patch header.indexOffset.
* 8. Clear deduplication hash map if used.
*
* Afterwards (or for read-mode contexts) all dynamically allocated buffers, arrays, hash tables
* and mapping structures are freed/unmapped. Media tags are removed from their hash table.
*
* Error Handling:
* - Returns -1 with errno = EINVAL if the provided pointer is NULL or not a valid context.
* - Returns -1 with errno set to AARUF_ERROR_CANNOT_WRITE_HEADER if a header write fails.
* - If any intermediate serialization helper returns an error status, that error value is
* propagated (converted to -1 with errno set accordingly by the caller if desired). In the
* current implementation aaruf_close() directly returns the negative error code for helper
* failures to preserve detail.
*
* @param context Opaque pointer returned by earlier open/create calls (must be an aaruformatContext).
* @return 0 on success; -1 or negative libaaruformat error code on failure.
* @retval 0 All pending data flushed (if writing) and resources released successfully.
* @retval -1 Invalid context pointer or initial header rewrite failure (errno = EINVAL or AARUF_ERROR_CANNOT_WRITE_HEADER).
* @retval AARUF_ERROR_CANNOT_WRITE_HEADER A later write helper (e.g., index, DDT) failed and returned this code directly.
* @retval <other negative libaaruformat code> Propagated from a write helper if future helpers add more error codes.
* @note On success the context memory itself is freed; the caller must not reuse the pointer.
*/
int aaruf_close(void *context)
{
TRACE("Entering aaruf_close(%p)", context);
mediaTagEntry *media_tag = NULL;
mediaTagEntry *tmp_media_tag = NULL;
if(context == NULL)
{
FATAL("Invalid context");
errno = EINVAL;
return -1;
}
aaruformatContext *ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
errno = EINVAL;
return -1;
}
if(ctx->isWriting)
{
TRACE("File is writing");
TRACE("Seeking to start of image");
// Write the header at the beginning of the file
fseek(ctx->imageStream, 0, SEEK_SET);
TRACE("Writing header at position 0");
if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1)
{
fclose(ctx->imageStream);
ctx->imageStream = NULL;
errno = AARUF_ERROR_CANNOT_WRITE_HEADER;
return -1;
}
// Close current block first
TRACE("Closing current block if any");
if(ctx->writingBuffer != NULL)
{
int error = aaruf_close_current_block(ctx);
if(error != AARUF_STATUS_OK) return error;
}
// Write cached secondary DDT table if any
int32_t res = write_cached_secondary_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
// Write primary DDT table (multi-level) if applicable
res = write_primary_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
// Write single-level DDT table if applicable
res = write_single_level_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
// Finalize checksums and write checksum block
write_checksum_block(ctx);
// Write tracks block
write_tracks_block(ctx);
// Write MODE 2 subheader data block
write_mode2_subheaders_block(ctx);
// Write CD sector prefix data block
write_sector_prefix(ctx);
// Write the complete index at the end of the file
res = write_index_block(ctx);
if(res != AARUF_STATUS_OK) return res;
if(ctx->deduplicate && ctx->sectorHashMap != NULL) if(ctx->deduplicate && ctx->sectorHashMap != NULL)
{ {
TRACE("Clearing sector hash map"); TRACE("Clearing sector hash map");