mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2025-12-16 19:24:40 +00:00
Add doxygen documentation for closing functions.
This commit is contained in:
541
src/close.c
541
src/close.c
@@ -15,6 +15,24 @@
|
||||
* 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/>.
|
||||
*/
|
||||
/**
|
||||
* @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 <stdio.h>
|
||||
@@ -30,197 +48,42 @@
|
||||
#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.
|
||||
* For images opened in write mode, this function performs critical finalization operations
|
||||
* including writing cached DDT tables, updating the index, writing the final image header,
|
||||
* and ensuring all data structures are properly persisted. It handles both single-level
|
||||
* and multi-level DDT structures and performs comprehensive cleanup of all allocated resources.
|
||||
* When working with a multi-level DDT (i.e. primary table with tableShift > 0), a single
|
||||
* secondary table may be cached in memory while sectors belonging to its range are written.
|
||||
* This function serializes the currently cached secondary table (if any) at the end of the
|
||||
* file, aligning the write position to the DDT block alignment, and updates the corresponding
|
||||
* 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:
|
||||
* @retval 0 Successfully closed the context and freed all resources. This is returned when:
|
||||
* - 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
|
||||
* On return the cached secondary table buffers and bookkeeping fields (cachedSecondaryDdtSmall,
|
||||
* cachedSecondaryDdtBig, cachedDdtOffset) are cleared.
|
||||
*
|
||||
* @retval -1 Context validation failed. This occurs when:
|
||||
* - The context parameter is NULL
|
||||
* - The context magic number doesn't match AARU_MAGIC (invalid context type)
|
||||
* - The errno is set to EINVAL to indicate invalid argument
|
||||
*
|
||||
* @retval AARUF_ERROR_CANNOT_WRITE_HEADER (-16) Write operations failed. This occurs when:
|
||||
* - Cannot write the initial image header at position 0 (for write mode)
|
||||
* - Cannot write cached secondary DDT header or data to the image file
|
||||
* - 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.
|
||||
* @param ctx Pointer to an initialized aaruformatContext in write mode.
|
||||
* @return AARUF_STATUS_OK on success, or AARUF_ERROR_CANNOT_WRITE_HEADER if the
|
||||
* secondary table or updated primary table cannot be flushed.
|
||||
* @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.
|
||||
* @note If no cached secondary DDT is pending (detected via tableShift and cache pointers),
|
||||
* the function is a no-op returning AARUF_STATUS_OK.
|
||||
* @internal
|
||||
*/
|
||||
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
|
||||
// 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 =
|
||||
ctx->userDataDdtHeader.tableShift > 0 &&
|
||||
(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");
|
||||
|
||||
fseek(ctx->imageStream, 0, SEEK_END);
|
||||
@@ -298,8 +161,7 @@ int aaruf_close(void *context)
|
||||
if(written_bytes == 1)
|
||||
{
|
||||
// Update primary table entry to point to new location
|
||||
uint64_t new_secondary_table_block_offset =
|
||||
end_of_file >> ctx->userDataDdtHeader.blockAlignmentShift;
|
||||
uint64_t new_secondary_table_block_offset = end_of_file >> ctx->userDataDdtHeader.blockAlignmentShift;
|
||||
|
||||
if(ctx->userDataDdtHeader.sizeType == SmallDdtSizeType)
|
||||
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++)
|
||||
{
|
||||
entry = (IndexEntry *)utarray_eltptr(ctx->indexEntries, k);
|
||||
if(entry && entry->offset == ctx->cachedDdtOffset &&
|
||||
entry->blockType == DeDuplicationTable2)
|
||||
if(entry && entry->offset == ctx->cachedDdtOffset && entry->blockType == DeDuplicationTable2)
|
||||
{
|
||||
TRACE("Found old DDT index entry at position %u, removing", k);
|
||||
utarray_erase(ctx->indexEntries, k, 1);
|
||||
@@ -381,11 +242,33 @@ int aaruf_close(void *context)
|
||||
|
||||
// Set position
|
||||
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
|
||||
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");
|
||||
|
||||
// Calculate CRC64 of the primary DDT table data first
|
||||
@@ -457,11 +340,32 @@ int aaruf_close(void *context)
|
||||
}
|
||||
else
|
||||
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
|
||||
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");
|
||||
|
||||
// Calculate CRC64 of the primary DDT table data
|
||||
@@ -536,8 +440,28 @@ int aaruf_close(void *context)
|
||||
}
|
||||
else
|
||||
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 aligned_position;
|
||||
|
||||
@@ -575,8 +499,8 @@ int aaruf_close(void *context)
|
||||
bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 ||
|
||||
ctx->checksums.hasSpamSum || ctx->checksums.hasBlake3;
|
||||
|
||||
if(has_checksums)
|
||||
{
|
||||
if(!has_checksums) return ;
|
||||
|
||||
ChecksumHeader checksum_header = {0};
|
||||
checksum_header.identifier = ChecksumBlock;
|
||||
|
||||
@@ -668,18 +592,30 @@ int aaruf_close(void *context)
|
||||
|
||||
utarray_push_back(ctx->indexEntries, &checksum_index_entry);
|
||||
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
|
||||
if(ctx->tracksHeader.entries > 0 && ctx->trackEntries != NULL)
|
||||
{
|
||||
if(ctx->tracksHeader.entries <= 0 || ctx->trackEntries == NULL) return ;
|
||||
|
||||
fseek(ctx->imageStream, 0, SEEK_END);
|
||||
long tracks_position = ftell(ctx->imageStream);
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
tracks_position = aligned_position;
|
||||
}
|
||||
@@ -706,18 +642,32 @@ int aaruf_close(void *context)
|
||||
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
|
||||
if(ctx->mode2_subheaders != NULL)
|
||||
{
|
||||
if(ctx->mode2_subheaders == NULL) return ;
|
||||
|
||||
fseek(ctx->imageStream, 0, SEEK_END);
|
||||
long mode2_subheaders_position = ftell(ctx->imageStream);
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
mode2_subheaders_position = aligned_position;
|
||||
}
|
||||
@@ -728,7 +678,7 @@ int aaruf_close(void *context)
|
||||
subheaders_block.type = CompactDiscMode2Subheader;
|
||||
subheaders_block.compression = None;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 (P–W) 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);
|
||||
long prefix_position = ftell(ctx->imageStream);
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
if(written_bytes == 1)
|
||||
{
|
||||
TRACE("Successfully wrote CD sector prefix subheaders block (%" PRIu64 " bytes)",
|
||||
prefix_block.length);
|
||||
TRACE("Successfully wrote CD sector prefix block (%" PRIu64 " bytes)", prefix_block.length);
|
||||
// Add prefix block to index
|
||||
TRACE("Adding CD sector prefix block to index");
|
||||
IndexEntry prefix_index_entry;
|
||||
@@ -796,21 +769,39 @@ int aaruf_close(void *context)
|
||||
prefix_index_entry.dataType = CdSectorPrefix;
|
||||
prefix_index_entry.offset = prefix_position;
|
||||
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
|
||||
TRACE("Writing index at the end of the file");
|
||||
fseek(ctx->imageStream, 0, SEEK_END);
|
||||
long index_position = ftell(ctx->imageStream);
|
||||
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
index_position = 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);
|
||||
TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64);
|
||||
}
|
||||
else { index_header.crc64 = 0; }
|
||||
else
|
||||
index_header.crc64 = 0;
|
||||
|
||||
// Write index header
|
||||
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;
|
||||
entry = (IndexEntry *)utarray_next(ctx->indexEntries, entry))
|
||||
{
|
||||
if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1)
|
||||
{
|
||||
entries_written++;
|
||||
@@ -860,7 +851,6 @@ int aaruf_close(void *context)
|
||||
TRACE("Failed to write index entry %zu", entries_written);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(entries_written == index_header.entries)
|
||||
{
|
||||
@@ -873,9 +863,7 @@ int aaruf_close(void *context)
|
||||
// Seek back to beginning and rewrite header
|
||||
fseek(ctx->imageStream, 0, SEEK_SET);
|
||||
if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) == 1)
|
||||
{
|
||||
TRACE("Successfully updated header with index offset");
|
||||
}
|
||||
else
|
||||
{
|
||||
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_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)
|
||||
{
|
||||
TRACE("Clearing sector hash map");
|
||||
|
||||
Reference in New Issue
Block a user