Loading...
Searching...
No Matches
close.c
Go to the documentation of this file.
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef __linux__
#include <sys/mman.h>
#endif
#include <aaruformat.h>
#include "internal.h"
#include "log.h"
static int32_t write_cached_secondary_ddt(aaruformat_context *ctx)
{
// 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->user_data_ddt_header.tableShift > 0 && (ctx->cached_ddt_offset != 0 || ctx->cached_secondary_ddt2 != NULL);
if(!has_cached_secondary_ddt) return AARUF_STATUS_OK;
TRACE("Writing cached secondary DDT table to file");
fseek(ctx->imageStream, 0, SEEK_END);
long end_of_file = ftell(ctx->imageStream);
// Align the position according to block alignment shift
uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(end_of_file & alignment_mask)
{
// Calculate the next aligned position
uint64_t aligned_position = end_of_file + alignment_mask & ~alignment_mask;
// Seek to the aligned position and pad with zeros if necessary
fseek(ctx->imageStream, aligned_position, SEEK_SET);
end_of_file = aligned_position;
TRACE("Aligned DDT write position from %ld to %" PRIu64 " (alignment shift: %d)",
ftell(ctx->imageStream) - (aligned_position - end_of_file), aligned_position,
ctx->user_data_ddt_header.blockAlignmentShift);
}
// Prepare DDT header for the cached table
DdtHeader2 ddt_header = {0};
ddt_header.identifier = DeDuplicationTableSecondary;
ddt_header.type = UserData;
ddt_header.compression = ctx->compression_enabled ? Lzma : None;
ddt_header.levels = ctx->user_data_ddt_header.levels;
ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
ddt_header.negative = ctx->user_data_ddt_header.negative;
ddt_header.overflow = ctx->user_data_ddt_header.overflow;
ddt_header.blockAlignmentShift = ctx->user_data_ddt_header.blockAlignmentShift;
ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
ddt_header.tableShift = 0; // Secondary tables are single level
uint64_t items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
ddt_header.blocks = items_per_ddt_entry;
ddt_header.entries = items_per_ddt_entry;
ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry;
// Calculate data size
ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
// Calculate CRC64 of the data
crc64_ctx *crc64_context = aaruf_crc64_init();
if(crc64_context != NULL)
{
aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
uint64_t crc64;
aaruf_crc64_final(crc64_context, &crc64);
ddt_header.crc64 = crc64;
}
// Free CRC64 context
aaruf_crc64_free(crc64_context);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(ddt_header.compression == None)
{
buffer = (uint8_t *)ctx->cached_secondary_ddt2;
ddt_header.cmpCrc64 = ddt_header.crc64;
}
else
{
buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for secondary DDT v2 compression");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
size_t dst_size = (size_t)ddt_header.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size,
(uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length, lzma_properties, &props_size,
9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
ddt_header.cmpLength = (uint32_t)dst_size;
if(ddt_header.cmpLength >= ddt_header.length)
{
ddt_header.compression = None;
free(buffer);
buffer = (uint8_t *)ctx->cached_secondary_ddt2;
}
}
if(ddt_header.compression == None)
{
ddt_header.cmpLength = ddt_header.length;
ddt_header.cmpCrc64 = ddt_header.crc64;
}
else
ddt_header.cmpCrc64 = aaruf_crc64_data(buffer, ddt_header.cmpLength);
if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
{
if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
if(fwrite(buffer, ddt_header.cmpLength, 1, ctx->imageStream) == 1)
{
// Update primary table entry to point to new location
const uint64_t new_secondary_table_block_offset =
end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
ctx->user_data_ddt2[ctx->cached_ddt_position] = (uint64_t)new_secondary_table_block_offset;
// Update index: remove old entry for cached DDT and add new one
TRACE("Updating index for cached secondary DDT");
// Remove old index entry for the cached DDT
if(ctx->cached_ddt_offset != 0)
{
TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
const IndexEntry *entry = NULL;
// Find and remove the old index entry
for(unsigned int k = 0; k < utarray_len(ctx->index_entries); k++)
{
entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->offset == ctx->cached_ddt_offset &&
(entry->blockType == DeDuplicationTableSecondary ||
entry->blockType == DeDuplicationTableSAlpha))
{
TRACE("Found old DDT index entry at position %u, removing", k);
utarray_erase(ctx->index_entries, k, 1);
break;
}
}
}
// Add new index entry for the newly written secondary DDT
IndexEntry new_ddt_entry;
new_ddt_entry.blockType = DeDuplicationTableSecondary;
new_ddt_entry.dataType = UserData;
new_ddt_entry.offset = end_of_file;
utarray_push_back(ctx->index_entries, &new_ddt_entry);
ctx->dirty_index_block = true;
TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
// Write the updated primary table back to its original position in the file
long saved_pos = ftell(ctx->imageStream);
fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
size_t primary_written_bytes = 0;
primary_written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
if(primary_written_bytes != 1)
{
TRACE("Could not flush primary DDT table to file.");
return AARUF_ERROR_CANNOT_WRITE_HEADER;
}
fseek(ctx->imageStream, saved_pos, SEEK_SET);
}
else
TRACE("Failed to write cached secondary DDT data");
}
else
TRACE("Failed to write cached secondary DDT header");
// Free the cached table
free(ctx->cached_secondary_ddt2);
ctx->cached_secondary_ddt2 = NULL;
ctx->cached_ddt_offset = 0;
// Set position
fseek(ctx->imageStream, 0, SEEK_END);
if(ddt_header.compression == Lzma) free(buffer);
return AARUF_STATUS_OK;
}
static int32_t write_primary_ddt(aaruformat_context *ctx)
{
// 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;
TRACE("Writing cached primary DDT table back to file");
// Calculate CRC64 of the primary DDT table data first
crc64_ctx *crc64_context = aaruf_crc64_init();
if(crc64_context != NULL)
{
size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
aaruf_crc64_update(crc64_context, (uint8_t *)ctx->user_data_ddt2, primary_table_size);
uint64_t crc64;
aaruf_crc64_final(crc64_context, &crc64);
// Properly populate all header fields for multi-level DDT primary table
ctx->user_data_ddt_header.identifier = DeDuplicationTable2;
ctx->user_data_ddt_header.type = UserData;
ctx->user_data_ddt_header.compression = None;
// levels, tableLevel, previousLevelOffset, negative, overflow, blockAlignmentShift,
// dataShift, tableShift, sizeType, entries, blocks, start are already set during creation
ctx->user_data_ddt_header.crc64 = crc64;
ctx->user_data_ddt_header.cmpCrc64 = crc64;
ctx->user_data_ddt_header.length = primary_table_size;
ctx->user_data_ddt_header.cmpLength = primary_table_size;
TRACE("Calculated CRC64 for primary DDT: 0x%16lX", crc64);
}
// Free CRC64 context
aaruf_crc64_free(crc64_context);
// First write the DDT header
fseek(ctx->imageStream, ctx->primary_ddt_offset, SEEK_SET);
size_t headerWritten = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
if(headerWritten != 1)
{
TRACE("Failed to write primary DDT header to file");
return AARUF_ERROR_CANNOT_WRITE_HEADER;
}
// Then write the table data (position is already after the header)
size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
// Write the primary table data
size_t written_bytes = 0;
written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote primary DDT header and table to file (%" PRIu64 " entries, %zu bytes)",
ctx->user_data_ddt_header.entries, primary_table_size);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing primary DDT index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == UserData)
{
TRACE("Found existing primary DDT index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add primary DDT to index
TRACE("Adding primary DDT to index");
IndexEntry primary_ddt_entry;
primary_ddt_entry.blockType = DeDuplicationTable2;
primary_ddt_entry.dataType = UserData;
primary_ddt_entry.offset = ctx->primary_ddt_offset;
utarray_push_back(ctx->index_entries, &primary_ddt_entry);
ctx->dirty_index_block = true;
TRACE("Added primary DDT index entry at offset %" PRIu64, ctx->primary_ddt_offset);
}
else
TRACE("Failed to write primary DDT table to file");
return AARUF_STATUS_OK;
}
static int32_t write_single_level_ddt(aaruformat_context *ctx)
{
// 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;
TRACE("Writing single-level DDT table to file");
// Calculate CRC64 of the primary DDT table data
const size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
// Properly populate all header fields
ctx->user_data_ddt_header.identifier = DeDuplicationTable2;
ctx->user_data_ddt_header.type = UserData;
ctx->user_data_ddt_header.compression = ctx->compression_enabled ? Lzma : None;
ctx->user_data_ddt_header.levels = 1; // Single level
ctx->user_data_ddt_header.tableLevel = 0; // Top level
ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
// negative and overflow are already set during creation
// blockAlignmentShift, dataShift, tableShift, sizeType, entries, blocks, start are already set
ctx->user_data_ddt_header.length = primary_table_size;
ctx->user_data_ddt_header.cmpLength = primary_table_size;
ctx->user_data_ddt_header.crc64 = aaruf_crc64_data((uint8_t *)ctx->user_data_ddt2, primary_table_size);
TRACE("Calculated CRC64 for single-level DDT: 0x%16lX", ctx->user_data_ddt_header.crc64);
uint8_t *cmp_buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(ctx->user_data_ddt_header.compression == None)
{
cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
ctx->user_data_ddt_header.cmpCrc64 = ctx->user_data_ddt_header.crc64;
}
else
{
cmp_buffer = malloc((size_t)ctx->user_data_ddt_header.length * 2); // Allocate double size for compression
if(cmp_buffer == NULL)
{
TRACE("Failed to allocate memory for secondary DDT v2 compression");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
size_t dst_size = (size_t)ctx->user_data_ddt_header.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->user_data_ddt2,
ctx->user_data_ddt_header.length, lzma_properties, &props_size, 9, ctx->lzma_dict_size,
4, 0, 2, 273, 8);
ctx->user_data_ddt_header.cmpLength = (uint32_t)dst_size;
if(ctx->user_data_ddt_header.cmpLength >= ctx->user_data_ddt_header.length)
{
ctx->user_data_ddt_header.compression = None;
free(cmp_buffer);
cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
}
}
if(ctx->user_data_ddt_header.compression == None)
{
ctx->user_data_ddt_header.cmpLength = ctx->user_data_ddt_header.length;
ctx->user_data_ddt_header.cmpCrc64 = ctx->user_data_ddt_header.crc64;
}
else
ctx->user_data_ddt_header.cmpCrc64 =
aaruf_crc64_data(cmp_buffer, (uint32_t)ctx->user_data_ddt_header.cmpLength);
if(ctx->user_data_ddt_header.compression == Lzma) ctx->user_data_ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write the DDT header first
fseek(ctx->imageStream, 0, SEEK_END);
long ddt_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(ddt_position & alignment_mask)
{
const uint64_t aligned_position = ddt_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
ddt_position = aligned_position;
}
const size_t header_written = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
if(header_written != 1)
{
TRACE("Failed to write single-level DDT header to file");
return AARUF_ERROR_CANNOT_WRITE_HEADER;
}
// Write the primary table data
size_t written_bytes = 0;
if(ctx->user_data_ddt_header.compression == Lzma)
fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
written_bytes = fwrite(cmp_buffer, ctx->user_data_ddt_header.cmpLength, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote single-level DDT header and table to file (%" PRIu64
" entries, %zu bytes, %zu compressed bytes)",
ctx->user_data_ddt_header.entries, ctx->user_data_ddt_header.length, ctx->user_data_ddt_header.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing single-level DDT index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == UserData)
{
TRACE("Found existing single-level DDT index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add single-level DDT to index
TRACE("Adding single-level DDT to index");
IndexEntry single_ddt_entry;
single_ddt_entry.blockType = DeDuplicationTable2;
single_ddt_entry.dataType = UserData;
single_ddt_entry.offset = ddt_position;
utarray_push_back(ctx->index_entries, &single_ddt_entry);
ctx->dirty_index_block = true;
TRACE("Added single-level DDT index entry at offset %" PRIu64, ddt_position);
}
else
TRACE("Failed to write single-level DDT table data to file");
// Free compression buffer if it was allocated
if(ctx->user_data_ddt_header.compression != None && cmp_buffer != (uint8_t *)ctx->user_data_ddt2) free(cmp_buffer);
return AARUF_STATUS_OK;
}
static int32_t write_tape_ddt(aaruformat_context *ctx)
{
if(!ctx->is_tape) return AARUF_STATUS_INVALID_CONTEXT;
// Traverse the tape DDT uthash and find the biggest key
uint64_t max_key = 0;
TapeDdtHashEntry *entry, *tmp;
HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
if(entry->key > max_key) max_key = entry->key;
// Initialize context user data DDT header
ctx->user_data_ddt_header.identifier = DeDuplicationTable2;
ctx->user_data_ddt_header.type = UserData;
ctx->user_data_ddt_header.compression = ctx->compression_enabled ? Lzma : None;
ctx->user_data_ddt_header.levels = 1; // Single level
ctx->user_data_ddt_header.tableLevel = 0; // Top level
ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
ctx->user_data_ddt_header.negative = 0;
ctx->user_data_ddt_header.overflow = 0;
ctx->user_data_ddt_header.tableShift = 0; // Single level
ctx->user_data_ddt_header.entries = max_key + 1;
ctx->user_data_ddt_header.blocks = max_key + 1;
ctx->user_data_ddt_header.start = 0;
ctx->user_data_ddt_header.length = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
ctx->user_data_ddt_header.cmpLength = ctx->user_data_ddt_header.length;
// Initialize memory for user data DDT
ctx->user_data_ddt2 = calloc(ctx->user_data_ddt_header.entries, sizeof(uint64_t));
if(ctx->user_data_ddt2 == NULL)
{
TRACE("Failed to allocate memory for tape DDT table");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
// Populate user data DDT from tape DDT uthash
HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
if(entry->key < ctx->user_data_ddt_header.blocks) ctx->user_data_ddt2[entry->key] = entry->value;
// Do not repeat code
return write_single_level_ddt(ctx);
}
static void write_checksum_block(aaruformat_context *ctx)
{
uint64_t alignment_mask;
uint64_t aligned_position;
// Finalize pending checksums
if(ctx->calculating_md5)
{
ctx->checksums.hasMd5 = true;
aaruf_md5_final(&ctx->md5_context, ctx->checksums.md5);
}
if(ctx->calculating_sha1)
{
ctx->checksums.hasSha1 = true;
aaruf_sha1_final(&ctx->sha1_context, ctx->checksums.sha1);
}
if(ctx->calculating_sha256)
{
ctx->checksums.hasSha256 = true;
aaruf_sha256_final(&ctx->sha256_context, ctx->checksums.sha256);
}
if(ctx->calculating_spamsum)
{
ctx->checksums.hasSpamSum = true;
ctx->checksums.spamsum = calloc(1, FUZZY_MAX_RESULT);
aaruf_spamsum_final(ctx->spamsum_context, ctx->checksums.spamsum);
aaruf_spamsum_free(ctx->spamsum_context);
}
if(ctx->calculating_blake3)
{
ctx->checksums.hasBlake3 = true;
blake3_hasher_finalize(ctx->blake3_context, ctx->checksums.blake3, BLAKE3_OUT_LEN);
free(ctx->blake3_context);
}
// Write the checksums block
bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 ||
ctx->checksums.hasSpamSum || ctx->checksums.hasBlake3;
if(!has_checksums) return;
ChecksumHeader checksum_header = {0};
checksum_header.identifier = ChecksumBlock;
fseek(ctx->imageStream, 0, SEEK_END);
long checksum_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(checksum_position & alignment_mask)
{
aligned_position = checksum_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
checksum_position = aligned_position;
}
// Skip checksum_header
fseek(ctx->imageStream, sizeof(checksum_header), SEEK_CUR);
if(ctx->checksums.hasMd5)
{
TRACE("Writing MD5 checksum entry");
ChecksumEntry md5_entry = {0};
md5_entry.length = MD5_DIGEST_LENGTH;
md5_entry.type = Md5;
fwrite(&md5_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
fwrite(&ctx->checksums.md5, MD5_DIGEST_LENGTH, 1, ctx->imageStream);
checksum_header.length += sizeof(ChecksumEntry) + MD5_DIGEST_LENGTH;
checksum_header.entries++;
}
if(ctx->checksums.hasSha1)
{
TRACE("Writing SHA1 checksum entry");
ChecksumEntry sha1_entry = {0};
sha1_entry.length = SHA1_DIGEST_LENGTH;
sha1_entry.type = Sha1;
fwrite(&sha1_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
fwrite(&ctx->checksums.sha1, SHA1_DIGEST_LENGTH, 1, ctx->imageStream);
checksum_header.length += sizeof(ChecksumEntry) + SHA1_DIGEST_LENGTH;
checksum_header.entries++;
}
if(ctx->checksums.hasSha256)
{
TRACE("Writing SHA256 checksum entry");
ChecksumEntry sha256_entry = {0};
sha256_entry.length = SHA256_DIGEST_LENGTH;
sha256_entry.type = Sha256;
fwrite(&sha256_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
fwrite(&ctx->checksums.sha256, SHA256_DIGEST_LENGTH, 1, ctx->imageStream);
checksum_header.length += sizeof(ChecksumEntry) + SHA256_DIGEST_LENGTH;
checksum_header.entries++;
}
if(ctx->checksums.hasSpamSum)
{
TRACE("Writing SpamSum checksum entry");
ChecksumEntry spamsum_entry = {0};
spamsum_entry.length = strlen((const char *)ctx->checksums.spamsum);
spamsum_entry.type = SpamSum;
fwrite(&spamsum_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
fwrite(ctx->checksums.spamsum, spamsum_entry.length, 1, ctx->imageStream);
checksum_header.length += sizeof(ChecksumEntry) + spamsum_entry.length;
checksum_header.entries++;
}
if(ctx->checksums.hasBlake3)
{
TRACE("Writing BLAKE3 checksum entry");
ChecksumEntry blake3_entry = {0};
blake3_entry.length = BLAKE3_OUT_LEN;
blake3_entry.type = Blake3;
fwrite(&blake3_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
fwrite(&ctx->checksums.blake3, BLAKE3_OUT_LEN, 1, ctx->imageStream);
checksum_header.length += sizeof(ChecksumEntry) + BLAKE3_OUT_LEN;
checksum_header.entries++;
ctx->header.featureCompatible |= AARU_FEATURE_RW_BLAKE3;
}
fseek(ctx->imageStream, checksum_position, SEEK_SET);
TRACE("Writing checksum header");
fwrite(&checksum_header, sizeof(ChecksumHeader), 1, ctx->imageStream);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing checksum block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == ChecksumBlock && entry->dataType == 0)
{
TRACE("Found existing checksum block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add checksum block to index
TRACE("Adding checksum block to index");
IndexEntry checksum_index_entry;
checksum_index_entry.blockType = ChecksumBlock;
checksum_index_entry.dataType = 0;
checksum_index_entry.offset = checksum_position;
utarray_push_back(ctx->index_entries, &checksum_index_entry);
ctx->dirty_index_block = true;
TRACE("Added checksum block index entry at offset %" PRIu64, checksum_position);
}
static void write_tracks_block(aaruformat_context *ctx)
{
// Write tracks block
if(ctx->tracks_header.entries <= 0 || ctx->track_entries == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long tracks_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(tracks_position & alignment_mask)
{
uint64_t aligned_position = tracks_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
tracks_position = aligned_position;
}
TRACE("Writing tracks block at position %ld", tracks_position);
// Write header
if(fwrite(&ctx->tracks_header, sizeof(TracksHeader), 1, ctx->imageStream) == 1)
{
// Write entries
size_t written_entries =
fwrite(ctx->track_entries, sizeof(TrackEntry), ctx->tracks_header.entries, ctx->imageStream);
if(written_entries == ctx->tracks_header.entries)
{
TRACE("Successfully wrote tracks block with %u entries", ctx->tracks_header.entries);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing tracks block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == TracksBlock && entry->dataType == 0)
{
TRACE("Found existing tracks block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add tracks block to index
TRACE("Adding tracks block to index");
IndexEntry tracks_index_entry;
tracks_index_entry.blockType = TracksBlock;
tracks_index_entry.dataType = 0;
tracks_index_entry.offset = tracks_position;
utarray_push_back(ctx->index_entries, &tracks_index_entry);
ctx->dirty_index_block = true;
TRACE("Added tracks block index entry at offset %" PRIu64, tracks_position);
}
}
}
static void write_mode2_subheaders_block(aaruformat_context *ctx)
{
// Write MODE 2 subheader data block
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
uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(mode2_subheaders_position & 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;
}
TRACE("Writing MODE 2 subheaders block at position %ld", mode2_subheaders_position);
BlockHeader subheaders_block = {0};
subheaders_block.identifier = DataBlock;
subheaders_block.type = CompactDiscMode2Subheader;
subheaders_block.compression = ctx->compression_enabled ? Lzma : None;
subheaders_block.length =
(uint32_t)(ctx->user_data_ddt_header.negative + ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) *
8;
// Calculate CRC64
subheaders_block.crc64 = aaruf_crc64_data(ctx->mode2_subheaders, subheaders_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(subheaders_block.compression == None)
{
buffer = ctx->mode2_subheaders;
subheaders_block.cmpCrc64 = subheaders_block.crc64;
}
else
{
buffer = malloc((size_t)subheaders_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for MODE 2 subheaders compression");
return;
}
size_t dst_size = (size_t)subheaders_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->mode2_subheaders, subheaders_block.length, lzma_properties,
&props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
subheaders_block.cmpLength = (uint32_t)dst_size;
if(subheaders_block.cmpLength >= subheaders_block.length)
{
subheaders_block.compression = None;
free(buffer);
buffer = ctx->mode2_subheaders;
}
}
if(subheaders_block.compression == None)
{
subheaders_block.cmpLength = subheaders_block.length;
subheaders_block.cmpCrc64 = subheaders_block.crc64;
}
else
subheaders_block.cmpCrc64 = aaruf_crc64_data(buffer, subheaders_block.cmpLength);
const size_t length_to_write = subheaders_block.cmpLength;
if(subheaders_block.compression == Lzma) subheaders_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&subheaders_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(subheaders_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote MODE 2 subheaders block (%" PRIu64 " bytes)", subheaders_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing MODE 2 subheaders block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == CompactDiscMode2Subheader)
{
TRACE("Found existing MODE 2 subheaders block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add MODE 2 subheaders block to index
TRACE("Adding MODE 2 subheaders block to index");
IndexEntry mode2_subheaders_index_entry;
mode2_subheaders_index_entry.blockType = DataBlock;
mode2_subheaders_index_entry.dataType = CompactDiscMode2Subheader;
mode2_subheaders_index_entry.offset = mode2_subheaders_position;
utarray_push_back(ctx->index_entries, &mode2_subheaders_index_entry);
ctx->dirty_index_block = true;
TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, mode2_subheaders_position);
}
}
if(subheaders_block.compression == Lzma) free(buffer);
}
static void write_sector_prefix(aaruformat_context *ctx)
{
if(ctx->sector_prefix == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long prefix_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(prefix_position & alignment_mask)
{
uint64_t aligned_position = prefix_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
prefix_position = aligned_position;
}
TRACE("Writing sector prefix block at position %ld", prefix_position);
BlockHeader prefix_block = {0};
prefix_block.identifier = DataBlock;
prefix_block.type = CdSectorPrefix;
prefix_block.compression = ctx->compression_enabled ? Lzma : None;
prefix_block.length = (uint32_t)ctx->sector_prefix_offset;
// Calculate CRC64
prefix_block.crc64 = aaruf_crc64_data(ctx->sector_prefix, prefix_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(prefix_block.compression == None)
{
buffer = ctx->sector_prefix;
prefix_block.cmpCrc64 = prefix_block.crc64;
}
else
{
buffer = malloc((size_t)prefix_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for CD sector prefix compression");
return;
}
size_t dst_size = (size_t)prefix_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_prefix, prefix_block.length, lzma_properties,
&props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
prefix_block.cmpLength = (uint32_t)dst_size;
if(prefix_block.cmpLength >= prefix_block.length)
{
prefix_block.compression = None;
free(buffer);
buffer = ctx->sector_prefix;
}
}
if(prefix_block.compression == None)
{
prefix_block.cmpLength = prefix_block.length;
prefix_block.cmpCrc64 = prefix_block.crc64;
}
else
prefix_block.cmpCrc64 = aaruf_crc64_data(buffer, prefix_block.cmpLength);
const size_t length_to_write = prefix_block.cmpLength;
if(prefix_block.compression == Lzma) prefix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&prefix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(prefix_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote CD sector prefix block (%" PRIu64 " bytes)", prefix_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing CD sector prefix block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == CdSectorPrefix)
{
TRACE("Found existing CD sector prefix block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add prefix block to index
TRACE("Adding CD sector prefix block to index");
IndexEntry prefix_index_entry;
prefix_index_entry.blockType = DataBlock;
prefix_index_entry.dataType = CdSectorPrefix;
prefix_index_entry.offset = prefix_position;
utarray_push_back(ctx->index_entries, &prefix_index_entry);
ctx->dirty_index_block = true;
TRACE("Added CD sector prefix block index entry at offset %" PRIu64, prefix_position);
}
}
if(prefix_block.compression == Lzma) free(buffer);
}
static void write_sector_suffix(aaruformat_context *ctx)
{
if(ctx->sector_suffix == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long suffix_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(suffix_position & alignment_mask)
{
const uint64_t aligned_position = suffix_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
suffix_position = aligned_position;
}
TRACE("Writing sector suffix block at position %ld", suffix_position);
BlockHeader suffix_block = {0};
suffix_block.identifier = DataBlock;
suffix_block.type = CdSectorSuffix;
suffix_block.compression = ctx->compression_enabled ? Lzma : None;
suffix_block.length = (uint32_t)ctx->sector_suffix_offset;
// Calculate CRC64
suffix_block.crc64 = aaruf_crc64_data(ctx->sector_suffix, suffix_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(suffix_block.compression == None)
{
buffer = ctx->sector_suffix;
suffix_block.cmpCrc64 = suffix_block.crc64;
}
else
{
buffer = malloc((size_t)suffix_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for CD sector suffix compression");
return;
}
size_t dst_size = (size_t)suffix_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_suffix, suffix_block.length, lzma_properties,
&props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
suffix_block.cmpLength = (uint32_t)dst_size;
if(suffix_block.cmpLength >= suffix_block.length)
{
suffix_block.compression = None;
free(buffer);
buffer = ctx->sector_suffix;
}
}
if(suffix_block.compression == None)
{
suffix_block.cmpLength = suffix_block.length;
suffix_block.cmpCrc64 = suffix_block.crc64;
}
else
suffix_block.cmpCrc64 = aaruf_crc64_data(buffer, suffix_block.cmpLength);
const size_t length_to_write = suffix_block.cmpLength;
if(suffix_block.compression == Lzma) suffix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&suffix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(suffix_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote CD sector suffix block (%" PRIu64 " bytes)", suffix_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing CD sector suffix block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == CdSectorSuffix)
{
TRACE("Found existing CD sector suffix block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add suffix block to index
TRACE("Adding CD sector suffix block to index");
IndexEntry suffix_index_entry;
suffix_index_entry.blockType = DataBlock;
suffix_index_entry.dataType = CdSectorSuffix;
suffix_index_entry.offset = suffix_position;
utarray_push_back(ctx->index_entries, &suffix_index_entry);
ctx->dirty_index_block = true;
TRACE("Added CD sector suffix block index entry at offset %" PRIu64, suffix_position);
}
}
if(suffix_block.compression == Lzma) free(buffer);
}
static void write_sector_prefix_ddt(aaruformat_context *ctx)
{
if(ctx->sector_prefix_ddt2 == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long prefix_ddt_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(prefix_ddt_position & alignment_mask)
{
const uint64_t aligned_position = prefix_ddt_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
prefix_ddt_position = aligned_position;
}
TRACE("Writing sector prefix DDT v2 at position %ld", prefix_ddt_position);
DdtHeader2 ddt_header2 = {0};
ddt_header2.identifier = DeDuplicationTable2;
ddt_header2.type = CdSectorPrefix;
ddt_header2.compression = ctx->compression_enabled ? Lzma : None;
ddt_header2.levels = 1;
ddt_header2.tableLevel = 0;
ddt_header2.negative = ctx->user_data_ddt_header.negative;
ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
ddt_header2.blockAlignmentShift = ctx->user_data_ddt_header.blockAlignmentShift;
ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
ddt_header2.tableShift = 0; // Single-level DDT
ddt_header2.entries =
ctx->image_info.Sectors + ctx->user_data_ddt_header.negative + ctx->user_data_ddt_header.overflow;
ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
ddt_header2.start = 0;
ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
// Calculate CRC64
ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_prefix_ddt2, (uint32_t)ddt_header2.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(ddt_header2.compression == None)
{
buffer = (uint8_t *)ctx->sector_prefix_ddt2;
ddt_header2.cmpCrc64 = ddt_header2.crc64;
}
else
{
buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for sector prefix DDT v2 compression");
return;
}
size_t dst_size = (size_t)ddt_header2.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_prefix_ddt2, ddt_header2.length,
lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
ddt_header2.cmpLength = (uint32_t)dst_size;
if(ddt_header2.cmpLength >= ddt_header2.length)
{
ddt_header2.compression = None;
free(buffer);
buffer = (uint8_t *)ctx->sector_prefix_ddt2;
}
}
if(ddt_header2.compression == None)
{
ddt_header2.cmpLength = ddt_header2.length;
ddt_header2.cmpCrc64 = ddt_header2.crc64;
}
else
ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
const size_t length_to_write = ddt_header2.cmpLength;
if(ddt_header2.compression == Lzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
{
if(ddt_header2.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote sector prefix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing sector prefix DDT v2 index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == CdSectorPrefix)
{
TRACE("Found existing sector prefix DDT v2 index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add prefix block to index
TRACE("Adding sector prefix DDT v2 to index");
IndexEntry prefix_ddt_index_entry;
prefix_ddt_index_entry.blockType = DeDuplicationTable2;
prefix_ddt_index_entry.dataType = CdSectorPrefix;
prefix_ddt_index_entry.offset = prefix_ddt_position;
utarray_push_back(ctx->index_entries, &prefix_ddt_index_entry);
ctx->dirty_index_block = true;
TRACE("Added sector prefix DDT v2 index entry at offset %" PRIu64, prefix_ddt_position);
}
}
if(ddt_header2.compression == Lzma) free(buffer);
}
static void write_sector_suffix_ddt(aaruformat_context *ctx)
{
if(ctx->sector_suffix_ddt2 == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long suffix_ddt_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(suffix_ddt_position & alignment_mask)
{
const uint64_t aligned_position = suffix_ddt_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
suffix_ddt_position = aligned_position;
}
TRACE("Writing sector suffix DDT v2 at position %ld", suffix_ddt_position);
DdtHeader2 ddt_header2 = {0};
ddt_header2.identifier = DeDuplicationTable2;
ddt_header2.type = CdSectorSuffix;
ddt_header2.compression = ctx->compression_enabled ? Lzma : None;
ddt_header2.levels = 1;
ddt_header2.tableLevel = 0;
ddt_header2.negative = ctx->user_data_ddt_header.negative;
ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
ddt_header2.blockAlignmentShift = ctx->user_data_ddt_header.blockAlignmentShift;
ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
ddt_header2.tableShift = 0; // Single-level DDT
ddt_header2.entries =
ctx->image_info.Sectors + ctx->user_data_ddt_header.negative + ctx->user_data_ddt_header.overflow;
ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
ddt_header2.start = 0;
ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
// Calculate CRC64
ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_suffix_ddt2, (uint32_t)ddt_header2.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(ddt_header2.compression == None)
{
buffer = (uint8_t *)ctx->sector_suffix_ddt2;
ddt_header2.cmpCrc64 = ddt_header2.crc64;
}
else
{
buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for sector suffix DDT v2 compression");
return;
}
size_t dst_size = (size_t)ddt_header2.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_suffix_ddt2, ddt_header2.length,
lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
ddt_header2.cmpLength = (uint32_t)dst_size;
if(ddt_header2.cmpLength >= ddt_header2.length)
{
ddt_header2.compression = None;
free(buffer);
buffer = (uint8_t *)ctx->sector_suffix_ddt2;
}
}
if(ddt_header2.compression == None)
{
ddt_header2.cmpLength = ddt_header2.length;
ddt_header2.cmpCrc64 = ddt_header2.crc64;
}
else
ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
const size_t length_to_write = ddt_header2.cmpLength;
if(ddt_header2.compression == Lzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
{
if(ddt_header2.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote sector suffix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing sector suffix DDT v2 block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == CdSectorSuffix)
{
TRACE("Found existing sector suffix DDT v2 index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add suffix block to index
TRACE("Adding sector suffix DDT v2 to index");
IndexEntry suffix_ddt_index_entry;
suffix_ddt_index_entry.blockType = DeDuplicationTable2;
suffix_ddt_index_entry.dataType = CdSectorSuffix;
suffix_ddt_index_entry.offset = suffix_ddt_position;
utarray_push_back(ctx->index_entries, &suffix_ddt_index_entry);
ctx->dirty_index_block = true;
TRACE("Added sector suffix DDT v2 index entry at offset %" PRIu64, suffix_ddt_position);
}
}
if(ddt_header2.compression == Lzma) free(buffer);
}
static void write_sector_subchannel(aaruformat_context *ctx)
{
if(ctx->sector_subchannel == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
// Align index position to block boundary if needed
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing sector subchannel block at position %ld", block_position);
BlockHeader subchannel_block = {0};
subchannel_block.identifier = DataBlock;
subchannel_block.compression = None;
uint8_t *buffer = ctx->sector_subchannel;
bool owns_buffer = false;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(ctx->image_info.MetadataMediaType == OpticalDisc)
{
subchannel_block.type = CdSectorSubchannel;
subchannel_block.length = (uint32_t)(ctx->user_data_ddt_header.negative + ctx->image_info.Sectors +
ctx->user_data_ddt_header.overflow) *
96;
subchannel_block.cmpLength = subchannel_block.length;
if(ctx->compression_enabled)
{
uint8_t *cst_buffer = malloc(subchannel_block.length);
if(cst_buffer == NULL)
{
TRACE("Failed to allocate memory for Claunia Subchannel Transform output");
return;
}
uint8_t *dst_buffer = malloc(subchannel_block.length);
if(dst_buffer == NULL)
{
TRACE("Failed to allocate memory for LZMA output");
free(cst_buffer);
return;
}
aaruf_cst_transform(ctx->sector_subchannel, cst_buffer, subchannel_block.length);
size_t dst_size = subchannel_block.length;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(dst_buffer, &dst_size, cst_buffer, subchannel_block.length, lzma_properties,
&props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
free(cst_buffer);
if(dst_size < subchannel_block.length)
{
subchannel_block.compression = LzmaClauniaSubchannelTransform;
subchannel_block.cmpLength = (uint32_t)dst_size;
buffer = dst_buffer;
owns_buffer = true;
}
else
{
subchannel_block.compression = None;
free(dst_buffer);
subchannel_block.cmpLength = subchannel_block.length;
}
}
}
else if(ctx->image_info.MetadataMediaType == BlockMedia)
{
switch(ctx->image_info.MediaType)
{
case AppleProfile:
case AppleFileWare:
subchannel_block.type = AppleProfileTag;
subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 20;
break;
case AppleSonyDS:
case AppleSonySS:
subchannel_block.type = AppleSonyTag;
subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 12;
break;
case PriamDataTower:
subchannel_block.type = PriamDataTowerTag;
subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 24;
break;
default:
TRACE("Incorrect media type, not writing sector subchannel block");
return; // Incorrect media type
}
subchannel_block.cmpLength = subchannel_block.length;
subchannel_block.compression = Lzma;
uint8_t *dst_buffer = malloc(subchannel_block.length);
if(dst_buffer == NULL)
{
TRACE("Failed to allocate memory for LZMA output");
return;
}
size_t dst_size = subchannel_block.length;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(dst_buffer, &dst_size, ctx->sector_subchannel, subchannel_block.length,
lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
if(dst_size < subchannel_block.length)
{
subchannel_block.cmpLength = (uint32_t)dst_size;
buffer = dst_buffer;
owns_buffer = true;
}
else
{
subchannel_block.compression = None;
free(dst_buffer);
subchannel_block.cmpLength = subchannel_block.length;
}
}
else
{
TRACE("Incorrect media type, not writing sector subchannel block");
return; // Incorrect media type
}
// Calculate CRC64 for raw subchannel data and compressed payload when present
subchannel_block.crc64 = aaruf_crc64_data(ctx->sector_subchannel, subchannel_block.length);
if(subchannel_block.compression == None)
subchannel_block.cmpCrc64 = subchannel_block.crc64;
else
subchannel_block.cmpCrc64 = aaruf_crc64_data(buffer, subchannel_block.cmpLength);
const size_t length_to_write = subchannel_block.cmpLength;
if(subchannel_block.compression != None) subchannel_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&subchannel_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(subchannel_block.compression != None) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote sector subchannel block (%" PRIu64 " bytes)", subchannel_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing subchannel block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == subchannel_block.type)
{
TRACE("Found existing subchannel block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add subchannel block to index
TRACE("Adding sector subchannel block to index");
IndexEntry subchannel_index_entry;
subchannel_index_entry.blockType = DataBlock;
subchannel_index_entry.dataType = subchannel_block.type;
subchannel_index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &subchannel_index_entry);
ctx->dirty_index_block = true;
TRACE("Added sector subchannel block index entry at offset %" PRIu64, block_position);
}
}
if(owns_buffer) free(buffer);
}
void write_dvd_long_sector_blocks(aaruformat_context *ctx)
{
if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL || ctx->sector_edc == NULL)
return;
uint64_t total_sectors =
ctx->user_data_ddt_header.negative + ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow;
// Write DVD sector ID block
fseek(ctx->imageStream, 0, SEEK_END);
long id_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(id_position & alignment_mask)
{
const uint64_t aligned_position = id_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
id_position = aligned_position;
}
TRACE("Writing DVD sector ID block at position %ld", id_position);
BlockHeader id_block = {0};
id_block.identifier = DataBlock;
id_block.type = DvdSectorId;
id_block.compression = ctx->compression_enabled ? Lzma : None;
id_block.length = (uint32_t)total_sectors * 4;
// Calculate CRC64
id_block.crc64 = aaruf_crc64_data(ctx->sector_id, id_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(id_block.compression == None)
{
buffer = ctx->sector_id;
id_block.cmpCrc64 = id_block.crc64;
}
else
{
buffer = malloc((size_t)id_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for DVD sector ID compression");
return;
}
size_t dst_size = (size_t)id_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_id, id_block.length, lzma_properties, &props_size, 9,
ctx->lzma_dict_size, 4, 0, 2, 273, 8);
id_block.cmpLength = (uint32_t)dst_size;
if(id_block.cmpLength >= id_block.length)
{
id_block.compression = None;
free(buffer);
buffer = ctx->sector_id;
}
}
if(id_block.compression == None)
{
id_block.cmpLength = id_block.length;
id_block.cmpCrc64 = id_block.crc64;
}
else
id_block.cmpCrc64 = aaruf_crc64_data(buffer, id_block.cmpLength);
size_t length_to_write = id_block.cmpLength;
if(id_block.compression == Lzma) id_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&id_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(id_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector ID block (%" PRIu64 " bytes)", id_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing DVD sector ID block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorId)
{
TRACE("Found existing DVD sector ID block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add ID block to index
TRACE("Adding DVD sector ID block to index");
IndexEntry id_index_entry;
id_index_entry.blockType = DataBlock;
id_index_entry.dataType = DvdSectorId;
id_index_entry.offset = id_position;
utarray_push_back(ctx->index_entries, &id_index_entry);
ctx->dirty_index_block = true;
TRACE("Added DVD sector ID block index entry at offset %" PRIu64, id_position);
}
}
if(id_block.compression == Lzma) free(buffer);
// Write DVD sector IED block
fseek(ctx->imageStream, 0, SEEK_END);
long ied_position = ftell(ctx->imageStream);
if(ied_position & alignment_mask)
{
const uint64_t aligned_position = ied_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
ied_position = aligned_position;
}
TRACE("Writing DVD sector IED block at position %ld", ied_position);
BlockHeader ied_block = {0};
ied_block.identifier = DataBlock;
ied_block.type = DvdSectorIed;
ied_block.compression = ctx->compression_enabled ? Lzma : None;
ied_block.length = (uint32_t)total_sectors * 2;
// Calculate CRC64
ied_block.crc64 = aaruf_crc64_data(ctx->sector_ied, ied_block.length);
buffer = NULL;
if(ied_block.compression == None)
{
buffer = ctx->sector_ied;
ied_block.cmpCrc64 = ied_block.crc64;
}
else
{
buffer = malloc((size_t)ied_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for DVD sector IED compression");
return;
}
size_t dst_size = (size_t)ied_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_ied, ied_block.length, lzma_properties, &props_size, 9,
ctx->lzma_dict_size, 4, 0, 2, 273, 8);
ied_block.cmpLength = (uint32_t)dst_size;
if(ied_block.cmpLength >= ied_block.length)
{
ied_block.compression = None;
free(buffer);
buffer = ctx->sector_ied;
}
}
if(ied_block.compression == None)
{
ied_block.cmpLength = ied_block.length;
ied_block.cmpCrc64 = ied_block.crc64;
}
else
ied_block.cmpCrc64 = aaruf_crc64_data(buffer, ied_block.cmpLength);
length_to_write = ied_block.cmpLength;
if(ied_block.compression == Lzma) ied_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&ied_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(ied_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector IED block (%" PRIu64 " bytes)", ied_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing DVD sector IED block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorIed)
{
TRACE("Found existing DVD sector IED block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add IED block to index
TRACE("Adding DVD sector IED block to index");
IndexEntry ied_index_entry;
ied_index_entry.blockType = DataBlock;
ied_index_entry.dataType = DvdSectorIed;
ied_index_entry.offset = ied_position;
utarray_push_back(ctx->index_entries, &ied_index_entry);
ctx->dirty_index_block = true;
TRACE("Added DVD sector IED block index entry at offset %" PRIu64, ied_position);
}
}
if(ied_block.compression == Lzma) free(buffer);
// Write DVD sector CPR/MAI block
fseek(ctx->imageStream, 0, SEEK_END);
long cpr_mai_position = ftell(ctx->imageStream);
if(cpr_mai_position & alignment_mask)
{
const uint64_t aligned_position = cpr_mai_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
cpr_mai_position = aligned_position;
}
TRACE("Writing DVD sector CPR/MAI block at position %ld", cpr_mai_position);
BlockHeader cpr_mai_block = {0};
cpr_mai_block.identifier = DataBlock;
cpr_mai_block.type = DvdSectorCprMai;
cpr_mai_block.compression = ctx->compression_enabled ? Lzma : None;
cpr_mai_block.length = (uint32_t)total_sectors * 6;
// Calculate CRC64
cpr_mai_block.crc64 = aaruf_crc64_data(ctx->sector_cpr_mai, cpr_mai_block.length);
buffer = NULL;
if(cpr_mai_block.compression == None)
{
buffer = ctx->sector_cpr_mai;
cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
}
else
{
buffer = malloc((size_t)cpr_mai_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for DVD sector CPR/MAI compression");
return;
}
size_t dst_size = (size_t)cpr_mai_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_cpr_mai, cpr_mai_block.length, lzma_properties,
&props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
cpr_mai_block.cmpLength = (uint32_t)dst_size;
if(cpr_mai_block.cmpLength >= cpr_mai_block.length)
{
cpr_mai_block.compression = None;
free(buffer);
buffer = ctx->sector_cpr_mai;
}
}
if(cpr_mai_block.compression == None)
{
cpr_mai_block.cmpLength = cpr_mai_block.length;
cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
}
else
cpr_mai_block.cmpCrc64 = aaruf_crc64_data(buffer, cpr_mai_block.cmpLength);
length_to_write = cpr_mai_block.cmpLength;
if(cpr_mai_block.compression == Lzma) cpr_mai_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&cpr_mai_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(cpr_mai_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector CPR/MAI block (%" PRIu64 " bytes)", cpr_mai_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing DVD sector CPR/MAI block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorCprMai)
{
TRACE("Found existing DVD sector CPR/MAI block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add CPR/MAI block to index
TRACE("Adding DVD sector CPR/MAI block to index");
IndexEntry cpr_mai_index_entry;
cpr_mai_index_entry.blockType = DataBlock;
cpr_mai_index_entry.dataType = DvdSectorCprMai;
cpr_mai_index_entry.offset = cpr_mai_position;
utarray_push_back(ctx->index_entries, &cpr_mai_index_entry);
ctx->dirty_index_block = true;
TRACE("Added DVD sector CPR/MAI block index entry at offset %" PRIu64, cpr_mai_position);
}
}
if(cpr_mai_block.compression == Lzma) free(buffer);
// Write DVD sector EDC block
fseek(ctx->imageStream, 0, SEEK_END);
long edc_position = ftell(ctx->imageStream);
if(edc_position & alignment_mask)
{
const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
edc_position = aligned_position;
}
TRACE("Writing DVD sector EDC block at position %ld", edc_position);
BlockHeader edc_block = {0};
edc_block.identifier = DataBlock;
edc_block.type = DvdSectorEdc;
edc_block.compression = ctx->compression_enabled ? Lzma : None;
edc_block.length = (uint32_t)total_sectors * 4;
// Calculate CRC64
edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
buffer = NULL;
if(edc_block.compression == None)
{
buffer = ctx->sector_edc;
edc_block.cmpCrc64 = edc_block.crc64;
}
else
{
buffer = malloc((size_t)edc_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for DVD sector EDC compression");
return;
}
size_t dst_size = (size_t)edc_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_edc, edc_block.length, lzma_properties, &props_size, 9,
ctx->lzma_dict_size, 4, 0, 2, 273, 8);
edc_block.cmpLength = (uint32_t)dst_size;
if(edc_block.cmpLength >= edc_block.length)
{
edc_block.compression = None;
free(buffer);
buffer = ctx->sector_edc;
}
}
if(edc_block.compression == None)
{
edc_block.cmpLength = edc_block.length;
edc_block.cmpCrc64 = edc_block.crc64;
}
else
edc_block.cmpCrc64 = aaruf_crc64_data(buffer, edc_block.cmpLength);
length_to_write = edc_block.cmpLength;
if(edc_block.compression == Lzma) edc_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(edc_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector EDC block (%" PRIu64 " bytes)", edc_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing DVD sector EDC block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorEdc)
{
TRACE("Found existing DVD sector EDC block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add EDC block to index
TRACE("Adding DVD sector EDC block to index");
IndexEntry edc_index_entry;
edc_index_entry.blockType = DataBlock;
edc_index_entry.dataType = DvdSectorEdc;
edc_index_entry.offset = edc_position;
utarray_push_back(ctx->index_entries, &edc_index_entry);
ctx->dirty_index_block = true;
TRACE("Added DVD sector EDC block index entry at offset %" PRIu64, edc_position);
}
}
if(edc_block.compression == Lzma) free(buffer);
}
static void write_dvd_title_key_decrypted_block(aaruformat_context *ctx)
{
if(ctx->sector_decrypted_title_key == NULL) return;
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing DVD decrypted title key block at position %ld", block_position);
BlockHeader decrypted_title_key_block = {0};
decrypted_title_key_block.identifier = DataBlock;
decrypted_title_key_block.type = DvdSectorTitleKeyDecrypted;
decrypted_title_key_block.compression = ctx->compression_enabled ? Lzma : None;
decrypted_title_key_block.length =
(uint32_t)(ctx->user_data_ddt_header.negative + ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) *
5;
// Calculate CRC64
decrypted_title_key_block.crc64 =
aaruf_crc64_data(ctx->sector_decrypted_title_key, decrypted_title_key_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(decrypted_title_key_block.compression == None)
{
buffer = ctx->sector_decrypted_title_key;
decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
}
else
{
buffer = malloc((size_t)decrypted_title_key_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for DVD decrypted title key compression");
return;
}
size_t dst_size = (size_t)decrypted_title_key_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_decrypted_title_key, decrypted_title_key_block.length,
lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
decrypted_title_key_block.cmpLength = (uint32_t)dst_size;
if(decrypted_title_key_block.cmpLength >= decrypted_title_key_block.length)
{
decrypted_title_key_block.compression = None;
free(buffer);
buffer = ctx->sector_decrypted_title_key;
}
}
if(decrypted_title_key_block.compression == None)
{
decrypted_title_key_block.cmpLength = decrypted_title_key_block.length;
decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
}
else
decrypted_title_key_block.cmpCrc64 = aaruf_crc64_data(buffer, decrypted_title_key_block.cmpLength);
const size_t length_to_write = decrypted_title_key_block.cmpLength;
if(decrypted_title_key_block.compression == Lzma) decrypted_title_key_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&decrypted_title_key_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(decrypted_title_key_block.compression == Lzma)
fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
// Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD decrypted title key block (%" PRIu64 " bytes)",
decrypted_title_key_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing DVD decrypted title key block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorTitleKeyDecrypted)
{
TRACE("Found existing DVD decrypted title key block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add decrypted title key block to index
TRACE("Adding DVD decrypted title key block to index");
IndexEntry decrypted_title_key_index_entry;
decrypted_title_key_index_entry.blockType = DataBlock;
decrypted_title_key_index_entry.dataType = DvdSectorTitleKeyDecrypted;
decrypted_title_key_index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &decrypted_title_key_index_entry);
ctx->dirty_index_block = true;
TRACE("Added DVD decrypted title key block index entry at offset %" PRIu64, block_position);
}
}
if(decrypted_title_key_block.compression == Lzma) free(buffer);
}
static void write_media_tags(aaruformat_context *ctx)
{
if(ctx->mediaTags == NULL) return;
mediaTagEntry *media_tag = NULL;
mediaTagEntry *tmp_media_tag = NULL;
HASH_ITER(hh, ctx->mediaTags, media_tag, tmp_media_tag)
{
fseek(ctx->imageStream, 0, SEEK_END);
long tag_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(tag_position & alignment_mask)
{
const uint64_t aligned_position = tag_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
tag_position = aligned_position;
}
TRACE("Writing media tag block type %d at position %ld", aaruf_get_datatype_for_media_tag_type(media_tag->type),
tag_position);
BlockHeader tag_block = {0};
tag_block.identifier = DataBlock;
tag_block.type = (uint16_t)aaruf_get_datatype_for_media_tag_type(media_tag->type);
tag_block.compression = ctx->compression_enabled ? Lzma : None;
tag_block.length = media_tag->length;
// Calculate CRC64
tag_block.crc64 = aaruf_crc64_data(media_tag->data, tag_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(tag_block.compression == None)
{
buffer = media_tag->data;
tag_block.cmpCrc64 = tag_block.crc64;
}
else
{
buffer = malloc((size_t)tag_block.length * 2); // Allocate double size for compression
if(buffer == NULL)
{
TRACE("Failed to allocate memory for media tag compression");
return;
}
size_t dst_size = (size_t)tag_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, media_tag->data, tag_block.length, lzma_properties, &props_size,
9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
tag_block.cmpLength = (uint32_t)dst_size;
if(tag_block.cmpLength >= tag_block.length)
{
tag_block.compression = None;
free(buffer);
buffer = media_tag->data;
}
}
if(tag_block.compression == None)
{
tag_block.cmpLength = tag_block.length;
tag_block.cmpCrc64 = tag_block.crc64;
}
else
tag_block.cmpCrc64 = aaruf_crc64_data(buffer, tag_block.cmpLength);
const size_t length_to_write = tag_block.cmpLength;
if(tag_block.compression == Lzma) tag_block.cmpLength += LZMA_PROPERTIES_LENGTH;
// Write header
if(fwrite(&tag_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(tag_block.compression == Lzma)
fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream); // Write data
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote media tag block type %d (%" PRIu64 " bytes)", tag_block.type,
tag_block.cmpLength);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing media tag type %d block index entries", tag_block.type);
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == tag_block.type)
{
TRACE("Found existing media tag type %d block index entry at position %d, removing",
tag_block.type, k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add media tag block to index
TRACE("Adding media tag type %d block to index", tag_block.type);
IndexEntry tag_index_entry;
tag_index_entry.blockType = DataBlock;
tag_index_entry.dataType = tag_block.type;
tag_index_entry.offset = tag_position;
utarray_push_back(ctx->index_entries, &tag_index_entry);
ctx->dirty_index_block = true;
TRACE("Added media tag block type %d index entry at offset %" PRIu64, tag_block.type, tag_position);
}
}
if(tag_block.compression == Lzma) free(buffer);
}
}
static void write_tape_file_block(aaruformat_context *ctx)
{
if(ctx->tape_files == NULL) return;
// Iterate the uthash and count how many entries do we have
const tapeFileHashEntry *tape_file = NULL;
const tapeFileHashEntry *tmp_tape_file = NULL;
size_t tape_file_count = 0;
HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file) tape_file_count++;
// Create a memory buffer to copy all the file entries
const size_t buffer_size = tape_file_count * sizeof(TapeFileEntry);
TapeFileEntry *buffer = malloc(buffer_size);
if(buffer == NULL)
{
TRACE("Failed to allocate memory for tape file entries");
return;
}
memset(buffer, 0, buffer_size);
size_t index = 0;
HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file)
{
if(index >= tape_file_count) break;
memcpy(&buffer[index], &tape_file->fileEntry, sizeof(TapeFileEntry));
index++;
}
// Create the tape file block in memory
TapeFileHeader tape_file_block = {0};
tape_file_block.identifier = TapeFileBlock;
tape_file_block.length = (uint32_t)buffer_size;
tape_file_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_file_block.length);
// Write tape file block to file, block aligned
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing tape file block at position %ld", block_position);
if(fwrite(&tape_file_block, sizeof(TapeFileHeader), 1, ctx->imageStream) == 1)
{
const size_t written_bytes = fwrite(buffer, tape_file_block.length, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote tape file block (%" PRIu64 " bytes)", tape_file_block.length);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing tape file block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == TapeFileBlock && entry->dataType == 0)
{
TRACE("Found existing tape file block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add tape file block to index
TRACE("Adding tape file block to index");
IndexEntry index_entry;
index_entry.blockType = TapeFileBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added tape file block index entry at offset %" PRIu64, block_position);
}
}
free(buffer);
}
static void write_tape_partition_block(aaruformat_context *ctx)
{
if(ctx->tape_partitions == NULL) return;
// Iterate the uthash and count how many entries do we have
const TapePartitionHashEntry *tape_partition = NULL;
const TapePartitionHashEntry *tmp_tape_partition = NULL;
size_t tape_partition_count = 0;
HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition) tape_partition_count++;
// Create a memory buffer to copy all the partition entries
const size_t buffer_size = tape_partition_count * sizeof(TapePartitionEntry);
TapePartitionEntry *buffer = malloc(buffer_size);
if(buffer == NULL)
{
TRACE("Failed to allocate memory for tape partition entries");
return;
}
memset(buffer, 0, buffer_size);
size_t index = 0;
HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition)
{
if(index >= tape_partition_count) break;
memcpy(&buffer[index], &tape_partition->partitionEntry, sizeof(TapePartitionEntry));
index++;
}
// Create the tape partition block in memory
TapePartitionHeader tape_partition_block = {0};
tape_partition_block.identifier = TapePartitionBlock;
tape_partition_block.length = (uint32_t)buffer_size;
tape_partition_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_partition_block.length);
// Write tape partition block to partition, block aligned
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing tape partition block at position %ld", block_position);
if(fwrite(&tape_partition_block, sizeof(TapePartitionHeader), 1, ctx->imageStream) == 1)
{
const size_t written_bytes = fwrite(buffer, tape_partition_block.length, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote tape partition block (%" PRIu64 " bytes)", tape_partition_block.length);
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing tape partition block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == TapePartitionBlock && entry->dataType == 0)
{
TRACE("Found existing tape partition block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add tape partition block to index
TRACE("Adding tape partition block to index");
IndexEntry index_entry;
index_entry.blockType = TapePartitionBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added tape partition block index entry at offset %" PRIu64, block_position);
}
}
free(buffer);
}
static void write_geometry_block(aaruformat_context *ctx)
{
if(ctx->geometry_block.identifier != GeometryBlock) return;
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing geometry block at position %ld", block_position);
// Write header
if(fwrite(&ctx->geometry_block, sizeof(GeometryBlockHeader), 1, ctx->imageStream) == 1)
{
TRACE("Successfully wrote geometry block");
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing geometry block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == GeometryBlock && entry->dataType == 0)
{
TRACE("Found existing geometry block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add geometry block to index
TRACE("Adding geometry block to index");
IndexEntry index_entry;
index_entry.blockType = GeometryBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added geometry block index entry at offset %" PRIu64, block_position);
}
}
static void write_metadata_block(aaruformat_context *ctx)
{
if(ctx->metadata_block_header.identifier != MetadataBlock && ctx->metadata_block_header.mediaSequence == 0 &&
ctx->metadata_block_header.lastMediaSequence == 0 && ctx->creator == NULL && ctx->comments == NULL &&
ctx->media_title == NULL && ctx->media_manufacturer == NULL && ctx->media_model == NULL &&
ctx->media_serial_number == NULL && ctx->media_barcode == NULL && ctx->media_part_number == NULL &&
ctx->drive_manufacturer == NULL && ctx->drive_model == NULL && ctx->drive_serial_number == NULL &&
ctx->drive_firmware_revision == NULL)
return;
ctx->metadata_block_header.blockSize =
sizeof(MetadataBlockHeader) + ctx->metadata_block_header.creatorLength +
ctx->metadata_block_header.commentsLength + ctx->metadata_block_header.mediaTitleLength +
ctx->metadata_block_header.mediaManufacturerLength + ctx->metadata_block_header.mediaModelLength +
ctx->metadata_block_header.mediaSerialNumberLength + ctx->metadata_block_header.mediaBarcodeLength +
ctx->metadata_block_header.mediaPartNumberLength + ctx->metadata_block_header.driveManufacturerLength +
ctx->metadata_block_header.driveModelLength + ctx->metadata_block_header.driveSerialNumberLength +
ctx->metadata_block_header.driveFirmwareRevisionLength;
ctx->metadata_block_header.identifier = MetadataBlock;
int pos = sizeof(MetadataBlockHeader);
uint8_t *buffer = calloc(1, ctx->metadata_block_header.blockSize);
if(buffer == NULL) return;
if(ctx->creator != NULL && ctx->metadata_block_header.creatorLength > 0)
{
memcpy(buffer + pos, ctx->creator, ctx->metadata_block_header.creatorLength);
ctx->metadata_block_header.creatorOffset = pos;
pos += ctx->metadata_block_header.creatorLength;
}
if(ctx->comments != NULL && ctx->metadata_block_header.commentsLength > 0)
{
memcpy(buffer + pos, ctx->comments, ctx->metadata_block_header.commentsLength);
ctx->metadata_block_header.commentsOffset = pos;
pos += ctx->metadata_block_header.commentsLength;
}
if(ctx->media_title != NULL && ctx->metadata_block_header.mediaTitleLength > 0)
{
memcpy(buffer + pos, ctx->media_title, ctx->metadata_block_header.mediaTitleLength);
ctx->metadata_block_header.mediaTitleOffset = pos;
pos += ctx->metadata_block_header.mediaTitleLength;
}
if(ctx->media_manufacturer != NULL && ctx->metadata_block_header.mediaManufacturerLength > 0)
{
memcpy(buffer + pos, ctx->media_manufacturer, ctx->metadata_block_header.mediaManufacturerLength);
ctx->metadata_block_header.mediaManufacturerOffset = pos;
pos += ctx->metadata_block_header.mediaManufacturerLength;
}
if(ctx->media_model != NULL && ctx->metadata_block_header.mediaModelLength > 0)
{
memcpy(buffer + pos, ctx->media_model, ctx->metadata_block_header.mediaModelLength);
ctx->metadata_block_header.mediaModelOffset = pos;
pos += ctx->metadata_block_header.mediaModelLength;
}
if(ctx->media_serial_number != NULL && ctx->metadata_block_header.mediaSerialNumberLength > 0)
{
memcpy(buffer + pos, ctx->media_serial_number, ctx->metadata_block_header.mediaSerialNumberLength);
ctx->metadata_block_header.mediaSerialNumberOffset = pos;
pos += ctx->metadata_block_header.mediaSerialNumberLength;
}
if(ctx->media_barcode != NULL && ctx->metadata_block_header.mediaBarcodeLength > 0)
{
memcpy(buffer + pos, ctx->media_barcode, ctx->metadata_block_header.mediaBarcodeLength);
ctx->metadata_block_header.mediaBarcodeOffset = pos;
pos += ctx->metadata_block_header.mediaBarcodeLength;
}
if(ctx->media_part_number != NULL && ctx->metadata_block_header.mediaPartNumberLength > 0)
{
memcpy(buffer + pos, ctx->media_part_number, ctx->metadata_block_header.mediaPartNumberLength);
ctx->metadata_block_header.mediaPartNumberOffset = pos;
pos += ctx->metadata_block_header.mediaPartNumberLength;
}
if(ctx->drive_manufacturer != NULL && ctx->metadata_block_header.driveManufacturerLength > 0)
{
memcpy(buffer + pos, ctx->drive_manufacturer, ctx->metadata_block_header.driveManufacturerLength);
ctx->metadata_block_header.driveManufacturerOffset = pos;
pos += ctx->metadata_block_header.driveManufacturerLength;
}
if(ctx->drive_model != NULL && ctx->metadata_block_header.driveModelLength > 0)
{
memcpy(buffer + pos, ctx->drive_model, ctx->metadata_block_header.driveModelLength);
ctx->metadata_block_header.driveModelOffset = pos;
pos += ctx->metadata_block_header.driveModelLength;
}
if(ctx->drive_serial_number != NULL && ctx->metadata_block_header.driveSerialNumberLength > 0)
{
memcpy(buffer + pos, ctx->drive_serial_number, ctx->metadata_block_header.driveSerialNumberLength);
ctx->metadata_block_header.driveSerialNumberOffset = pos;
pos += ctx->metadata_block_header.driveSerialNumberLength;
}
if(ctx->drive_firmware_revision != NULL && ctx->metadata_block_header.driveFirmwareRevisionLength > 0)
{
memcpy(buffer + pos, ctx->drive_firmware_revision, ctx->metadata_block_header.driveFirmwareRevisionLength);
ctx->metadata_block_header.driveFirmwareRevisionOffset = pos;
}
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
memcpy(buffer, &ctx->metadata_block_header, sizeof(MetadataBlockHeader));
TRACE("Writing metadata block at position %ld", block_position);
if(fwrite(buffer, ctx->metadata_block_header.blockSize, 1, ctx->imageStream) == 1)
{
TRACE("Successfully wrote metadata block");
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing metadata block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == MetadataBlock && entry->dataType == 0)
{
TRACE("Found existing metadata block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add metadata block to index
TRACE("Adding metadata block to index");
IndexEntry index_entry;
index_entry.blockType = MetadataBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added metadata block index entry at offset %" PRIu64, block_position);
}
free(buffer);
}
static void write_dumphw_block(aaruformat_context *ctx)
{
if(ctx->dump_hardware_entries_with_data == NULL || ctx->dump_hardware_header.entries == 0 ||
ctx->dump_hardware_header.identifier != DumpHardwareBlock)
return;
const size_t required_length = sizeof(DumpHardwareHeader) + ctx->dump_hardware_header.length;
uint8_t *buffer = calloc(1, required_length);
if(buffer == NULL) return;
// Start to iterate and copy the data
size_t offset = sizeof(DumpHardwareHeader);
for(int i = 0; i < ctx->dump_hardware_header.entries; i++)
{
size_t entry_size = sizeof(DumpHardwareEntry) +
ctx->dump_hardware_entries_with_data[i].entry.manufacturerLength +
ctx->dump_hardware_entries_with_data[i].entry.modelLength +
ctx->dump_hardware_entries_with_data[i].entry.revisionLength +
ctx->dump_hardware_entries_with_data[i].entry.firmwareLength +
ctx->dump_hardware_entries_with_data[i].entry.serialLength +
ctx->dump_hardware_entries_with_data[i].entry.softwareNameLength +
ctx->dump_hardware_entries_with_data[i].entry.softwareVersionLength +
ctx->dump_hardware_entries_with_data[i].entry.softwareOperatingSystemLength +
ctx->dump_hardware_entries_with_data[i].entry.extents * sizeof(DumpExtent);
if(offset + entry_size > required_length)
{
FATAL("Calculated size exceeds provided buffer length");
free(buffer);
return;
}
memcpy(buffer + offset, &ctx->dump_hardware_entries_with_data[i].entry, sizeof(DumpHardwareEntry));
offset += sizeof(DumpHardwareEntry);
if(ctx->dump_hardware_entries_with_data[i].entry.manufacturerLength > 0 &&
ctx->dump_hardware_entries_with_data[i].manufacturer != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].manufacturer,
ctx->dump_hardware_entries_with_data[i].entry.manufacturerLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.manufacturerLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.modelLength > 0 &&
ctx->dump_hardware_entries_with_data[i].model != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].model,
ctx->dump_hardware_entries_with_data[i].entry.modelLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.modelLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.revisionLength > 0 &&
ctx->dump_hardware_entries_with_data[i].revision != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].revision,
ctx->dump_hardware_entries_with_data[i].entry.revisionLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.revisionLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.firmwareLength > 0 &&
ctx->dump_hardware_entries_with_data[i].firmware != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].firmware,
ctx->dump_hardware_entries_with_data[i].entry.firmwareLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.firmwareLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.serialLength > 0 &&
ctx->dump_hardware_entries_with_data[i].serial != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].serial,
ctx->dump_hardware_entries_with_data[i].entry.serialLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.serialLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.softwareNameLength > 0 &&
ctx->dump_hardware_entries_with_data[i].softwareName != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareName,
ctx->dump_hardware_entries_with_data[i].entry.softwareNameLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.softwareNameLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.softwareVersionLength > 0 &&
ctx->dump_hardware_entries_with_data[i].softwareVersion != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareVersion,
ctx->dump_hardware_entries_with_data[i].entry.softwareVersionLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.softwareVersionLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.softwareOperatingSystemLength > 0 &&
ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem,
ctx->dump_hardware_entries_with_data[i].entry.softwareOperatingSystemLength);
offset += ctx->dump_hardware_entries_with_data[i].entry.softwareOperatingSystemLength;
}
if(ctx->dump_hardware_entries_with_data[i].entry.extents > 0 &&
ctx->dump_hardware_entries_with_data[i].extents != NULL)
{
memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].extents,
ctx->dump_hardware_entries_with_data[i].entry.extents * sizeof(DumpExtent));
offset += ctx->dump_hardware_entries_with_data[i].entry.extents * sizeof(DumpExtent);
}
}
// Calculate CRC64
ctx->dump_hardware_header.crc64 =
aaruf_crc64_data(buffer + sizeof(DumpHardwareHeader), ctx->dump_hardware_header.length);
// Copy header
memcpy(buffer, &ctx->dump_hardware_header, sizeof(DumpHardwareHeader));
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing dump hardware block at position %ld", block_position);
if(fwrite(buffer, required_length, 1, ctx->imageStream) == 1)
{
TRACE("Successfully wrote dump hardware block");
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing dump hardware block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DumpHardwareBlock && entry->dataType == 0)
{
TRACE("Found existing dump hardware block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add dump hardware block to index
TRACE("Adding dump hardware block to index");
IndexEntry index_entry;
index_entry.blockType = DumpHardwareBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added dump hardware block index entry at offset %" PRIu64, block_position);
}
free(buffer);
}
static void write_cicm_block(aaruformat_context *ctx)
{
if(ctx->cicm_block == NULL || ctx->cicm_block_header.length == 0 || ctx->cicm_block_header.identifier != CicmBlock)
return;
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing CICM XML block at position %ld", block_position);
if(fwrite(&ctx->cicm_block_header, sizeof(CicmMetadataBlock), 1, ctx->imageStream) == 1 &&
fwrite(ctx->cicm_block, ctx->cicm_block_header.length, 1, ctx->imageStream) == 1)
{
TRACE("Successfully wrote CICM XML block");
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing CICM XML block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == CicmBlock && entry->dataType == 0)
{
TRACE("Found existing CICM XML block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add CICM block to index
TRACE("Adding CICM XML block to index");
IndexEntry index_entry;
index_entry.blockType = CicmBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added CICM XML block index entry at offset %" PRIu64, block_position);
}
}
static void write_aaru_json_block(aaruformat_context *ctx)
{
if(ctx->json_block == NULL || ctx->json_block_header.length == 0 ||
ctx->json_block_header.identifier != AaruMetadataJsonBlock)
return;
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(block_position & alignment_mask)
{
const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
block_position = aligned_position;
}
TRACE("Writing Aaru metadata JSON block at position %ld", block_position);
if(fwrite(&ctx->json_block_header, sizeof(AaruMetadataJsonBlockHeader), 1, ctx->imageStream) == 1 &&
fwrite(ctx->json_block, ctx->json_block_header.length, 1, ctx->imageStream) == 1)
{
TRACE("Successfully wrote Aaru metadata JSON block");
// Remove any previously existing index entries of the same type before adding
TRACE("Removing any previously existing Aaru metadata JSON block index entries");
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == AaruMetadataJsonBlock && entry->dataType == 0)
{
TRACE("Found existing Aaru metadata JSON block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
// Add Aaru metadata JSON block to index
TRACE("Adding Aaru metadata JSON block to index");
IndexEntry index_entry;
index_entry.blockType = AaruMetadataJsonBlock;
index_entry.dataType = 0;
index_entry.offset = block_position;
utarray_push_back(ctx->index_entries, &index_entry);
ctx->dirty_index_block = true;
TRACE("Added Aaru metadata JSON block index entry at offset %" PRIu64, block_position);
}
}
static int32_t write_flux_capture_payload(aaruformat_context *ctx, FluxCaptureRecord *record, FluxEntry *entry)
{
uint64_t data_length = record->data_length;
uint64_t index_length = record->index_length;
uint64_t raw_length = data_length + index_length;
if(raw_length > UINT32_MAX)
{
FATAL("Flux capture raw length exceeds 32-bit limit (%" PRIu64 ")", raw_length);
return AARUF_ERROR_INCORRECT_DATA_SIZE;
}
uint8_t *raw_buffer = NULL;
if(raw_length != 0)
{
raw_buffer = malloc(raw_length);
if(raw_buffer == NULL)
{
FATAL("Could not allocate %" PRIu64 " bytes for flux serialization", raw_length);
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
if(data_length != 0 && record->data_buffer != NULL) memcpy(raw_buffer, record->data_buffer, data_length);
if(index_length != 0 && record->index_buffer != NULL)
memcpy(raw_buffer + data_length, record->index_buffer, index_length);
}
uint64_t raw_crc = raw_length != 0 && raw_buffer != NULL ? aaruf_crc64_data(raw_buffer, raw_length) : 0;
CompressionType compression = ctx->compression_enabled ? Lzma : None;
uint8_t *compressed_buffer = NULL;
uint32_t cmp_length = 0;
uint64_t cmp_crc = 0;
if(compression == Lzma)
{
size_t cmp_capacity = raw_length ? raw_length * 2 + 65536 : LZMA_PROPERTIES_LENGTH + 16;
if(cmp_capacity < raw_length + LZMA_PROPERTIES_LENGTH) cmp_capacity = raw_length + LZMA_PROPERTIES_LENGTH;
uint8_t *cmp_stream = malloc(cmp_capacity);
if(cmp_stream == NULL)
{
free(raw_buffer);
FATAL("Could not allocate %zu bytes for LZMA flux compression", cmp_capacity);
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
size_t dst_size = cmp_capacity;
uint8_t lzma_props[LZMA_PROPERTIES_LENGTH] = {0};
size_t props_size = LZMA_PROPERTIES_LENGTH;
int32_t error_no = aaruf_lzma_encode_buffer(cmp_stream, &dst_size, raw_buffer, raw_length, lzma_props,
&props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
if(error_no != 0 || props_size != LZMA_PROPERTIES_LENGTH || dst_size >= raw_length)
{
TRACE("Flux capture compression fell back to uncompressed (err=%d, dst=%zu, raw=%" PRIu64 ")", error_no,
dst_size, raw_length);
compression = None;
free(cmp_stream);
}
else
{
cmp_length = (uint32_t)(dst_size + LZMA_PROPERTIES_LENGTH);
compressed_buffer = malloc(cmp_length);
if(compressed_buffer == NULL)
{
free(cmp_stream);
free(raw_buffer);
FATAL("Could not allocate %u bytes for flux compressed payload", cmp_length);
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
memcpy(compressed_buffer, lzma_props, LZMA_PROPERTIES_LENGTH);
memcpy(compressed_buffer + LZMA_PROPERTIES_LENGTH, cmp_stream, dst_size);
cmp_crc = aaruf_crc64_data(compressed_buffer, cmp_length);
free(cmp_stream);
free(raw_buffer);
raw_buffer = NULL;
}
}
if(compression == None)
{
cmp_length = (uint32_t)raw_length;
cmp_crc = raw_crc;
compressed_buffer = raw_buffer;
raw_buffer = NULL;
}
// Align stream position to block boundary
fseek(ctx->imageStream, 0, SEEK_END);
long payload_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(payload_position & alignment_mask)
{
const uint64_t aligned_position = payload_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
payload_position = aligned_position;
}
DataStreamPayloadHeader payload_header = {0};
payload_header.identifier = DataStreamPayloadBlock;
payload_header.dataType = FluxData;
payload_header.compression = (uint16_t)compression;
payload_header.cmpLength = cmp_length;
payload_header.length = (uint32_t)raw_length;
payload_header.cmpCrc64 = cmp_crc;
payload_header.crc64 = raw_crc;
if(fwrite(&payload_header, sizeof(DataStreamPayloadHeader), 1, ctx->imageStream) != 1)
{
free(compressed_buffer);
free(raw_buffer);
FATAL("Could not write flux payload header");
return AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER;
}
if(cmp_length != 0 && compressed_buffer != NULL &&
fwrite(compressed_buffer, cmp_length, 1, ctx->imageStream) != 1)
{
free(compressed_buffer);
free(raw_buffer);
FATAL("Could not write flux payload data");
return AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA;
}
IndexEntry payload_entry;
payload_entry.blockType = DataStreamPayloadBlock;
payload_entry.dataType = FluxData;
payload_entry.offset = payload_position;
utarray_push_back(ctx->index_entries, &payload_entry);
entry->head = record->entry.head;
entry->track = record->entry.track;
entry->subtrack = record->entry.subtrack;
entry->captureIndex = record->entry.captureIndex;
entry->dataResolution = record->entry.dataResolution;
entry->indexResolution = record->entry.indexResolution;
entry->indexOffset = record->data_length;
entry->payloadOffset = payload_position >> ctx->user_data_ddt_header.blockAlignmentShift;
record->entry = *entry;
free(compressed_buffer);
free(raw_buffer);
return AARUF_STATUS_OK;
}
static int32_t write_flux_blocks(aaruformat_context *ctx)
{
TRACE("Entering write_flux_blocks(%p)", ctx);
if(ctx == NULL || ctx->imageStream == NULL)
{
FATAL("Invalid context when writing flux blocks");
return AARUF_ERROR_NOT_AARUFORMAT;
}
if(ctx->flux_captures == NULL || utarray_len(ctx->flux_captures) == 0)
{
TRACE("No flux captures enqueued, skipping flux block serialization");
return AARUF_STATUS_OK;
}
size_t capture_count = utarray_len(ctx->flux_captures);
if(capture_count > UINT16_MAX)
{
FATAL("Flux capture count exceeds header capacity (%zu > %u)", capture_count, UINT16_MAX);
return AARUF_ERROR_INCORRECT_DATA_SIZE;
}
FluxEntry *previous_entries = ctx->flux_entries;
ctx->flux_entries = NULL;
FluxEntry *entries = malloc(capture_count * sizeof(FluxEntry));
if(entries == NULL)
{
ctx->flux_entries = previous_entries;
FATAL("Could not allocate %zu bytes for flux entries", capture_count * sizeof(FluxEntry));
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
size_t idx = 0;
for(FluxCaptureRecord *record = (FluxCaptureRecord *)utarray_front(ctx->flux_captures); record != NULL;
record = (FluxCaptureRecord *)utarray_next(ctx->flux_captures, record), ++idx)
{
int32_t res = write_flux_capture_payload(ctx, record, &entries[idx]);
if(res != AARUF_STATUS_OK)
{
free(entries);
ctx->flux_entries = previous_entries;
return res;
}
}
FluxHeader header = {0};
header.identifier = FluxDataBlock;
header.entries = (uint16_t)capture_count;
header.blockAlignmentShift = ctx->user_data_ddt_header.blockAlignmentShift;
header.crc64 =
capture_count == 0 ? 0 : aaruf_crc64_data((const uint8_t *)entries, capture_count * sizeof(FluxEntry));
// Align stream position to block boundary
fseek(ctx->imageStream, 0, SEEK_END);
long metadata_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(metadata_position & alignment_mask)
{
const uint64_t aligned_position = metadata_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
metadata_position = aligned_position;
}
if(fwrite(&header, sizeof(FluxHeader), 1, ctx->imageStream) != 1)
{
free(entries);
ctx->flux_entries = previous_entries;
FATAL("Could not write flux metadata header");
return AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER;
}
if(capture_count != 0)
{
size_t written_entries = fwrite(entries, sizeof(FluxEntry), capture_count, ctx->imageStream);
if(written_entries != capture_count)
{
free(entries);
ctx->flux_entries = previous_entries;
FATAL("Could not write %zu flux entries (wrote %zu)", capture_count, written_entries);
return AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA;
}
}
IndexEntry metadata_entry;
metadata_entry.blockType = FluxDataBlock;
metadata_entry.dataType = 0;
metadata_entry.offset = metadata_position;
utarray_push_back(ctx->index_entries, &metadata_entry);
if(previous_entries != NULL) free(previous_entries);
ctx->flux_entries = entries;
ctx->flux_data_header = header;
utarray_free(ctx->flux_captures);
ctx->flux_captures = NULL;
int32_t map_result = flux_map_rebuild_from_entries(ctx);
if(map_result != AARUF_STATUS_OK) return map_result;
TRACE("Wrote %zu flux captures", capture_count);
return AARUF_STATUS_OK;
}
static int32_t write_index_block(aaruformat_context *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
uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
if(index_position & 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);
}
// Prepare index header
IndexHeader3 index_header;
index_header.identifier = IndexBlock3;
index_header.entries = utarray_len(ctx->index_entries);
index_header.previous = 0; // No previous index for now
TRACE("Writing index with %" PRIu64 " entries at position %ld", index_header.entries, index_position);
// Calculate CRC64 of index entries
crc64_ctx *index_crc64_context = aaruf_crc64_init();
if(index_crc64_context != NULL && index_header.entries > 0)
{
size_t index_data_size = index_header.entries * sizeof(IndexEntry);
aaruf_crc64_update(index_crc64_context, utarray_front(ctx->index_entries), index_data_size);
aaruf_crc64_final(index_crc64_context, &index_header.crc64);
TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64);
}
else
index_header.crc64 = 0;
// Free CRC64 context
aaruf_crc64_free(index_crc64_context);
// Write index header
if(fwrite(&index_header, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
{
TRACE("Successfully wrote index header");
// Write index entries
if(index_header.entries > 0)
{
size_t entries_written = 0;
IndexEntry *entry = NULL;
for(entry = (IndexEntry *)utarray_front(ctx->index_entries); entry != NULL;
entry = (IndexEntry *)utarray_next(ctx->index_entries, entry))
if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1)
{
entries_written++;
TRACE("Wrote index entry: blockType=0x%08X dataType=%u offset=%" PRIu64, entry->blockType,
entry->dataType, entry->offset);
}
else
{
TRACE("Failed to write index entry %zu", entries_written);
break;
}
if(entries_written == index_header.entries)
{
TRACE("Successfully wrote all %zu index entries", entries_written);
// Update header with index offset and rewrite it
ctx->header.indexOffset = index_position;
TRACE("Updating header with index offset: %" PRIu64, ctx->header.indexOffset);
// 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");
return AARUF_ERROR_CANNOT_WRITE_HEADER;
}
}
else
{
TRACE("Failed to write all index entries (wrote %zu of %" PRIu64 ")", entries_written,
index_header.entries);
return AARUF_ERROR_CANNOT_WRITE_HEADER;
}
}
}
else
{
TRACE("Failed to write index header");
return AARUF_ERROR_CANNOT_WRITE_HEADER;
}
return AARUF_STATUS_OK;
}
AARU_EXPORT int AARU_CALL 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;
}
aaruformat_context *ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
errno = EINVAL;
return -1;
}
if(ctx->is_writing)
{
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->writing_buffer != NULL)
{
int error = aaruf_close_current_block(ctx);
if(error != AARUF_STATUS_OK) return error;
}
int32_t res;
if(ctx->is_tape)
{
// Write tape DDT
if(ctx->dirty_tape_ddt)
{
res = write_tape_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
}
}
else
{
// Write cached secondary DDT table if any
if(ctx->dirty_secondary_ddt)
{
res = write_cached_secondary_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
}
// Write primary DDT table (multi-level) if applicable
if(ctx->dirty_primary_ddt)
{
res = write_primary_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
}
// Write single-level DDT table if applicable
if(ctx->dirty_single_level_ddt)
{
res = write_single_level_ddt(ctx);
if(res != AARUF_STATUS_OK) return res;
}
}
// Finalize checksums and write checksum block
if(ctx->dirty_checksum_block) write_checksum_block(ctx);
// Write tracks block
if(ctx->dirty_tracks_block) write_tracks_block(ctx);
// Write flux capture blocks
if(ctx->dirty_flux_block) {
res = write_flux_blocks(ctx);
if(res != AARUF_STATUS_OK) return res;
}
// Write MODE 2 subheader data block
if(ctx->dirty_mode2_subheaders_block) write_mode2_subheaders_block(ctx);
// Write CD sector prefix data block
if(ctx->dirty_sector_prefix_block) write_sector_prefix(ctx);
// Write sector prefix DDT (statuses + optional indexes)
if(ctx->dirty_sector_prefix_ddt) write_sector_prefix_ddt(ctx);
// Write CD sector suffix data block (EDC/ECC captures)
if(ctx->dirty_sector_suffix_block) write_sector_suffix(ctx);
// Write sector prefix DDT (EDC/ECC captures)
if(ctx->dirty_sector_suffix_ddt) write_sector_suffix_ddt(ctx);
// Write sector subchannel data block
if(ctx->dirty_sector_subchannel_block) write_sector_subchannel(ctx);
// Write DVD long sector data blocks
if(ctx->dirty_dvd_long_sector_blocks) write_dvd_long_sector_blocks(ctx);
// Write DVD decrypted title keys
if(ctx->dirty_dvd_title_key_decrypted_block) write_dvd_title_key_decrypted_block(ctx);
// Write media tags data blocks
if(ctx->dirty_media_tags) write_media_tags(ctx);
// Write tape files
if(ctx->dirty_tape_file_block) write_tape_file_block(ctx);
// Write tape partitions
if(ctx->dirty_tape_partition_block) write_tape_partition_block(ctx);
// Write geometry block if any
if(ctx->dirty_geometry_block) write_geometry_block(ctx);
// Write metadata block
if(ctx->dirty_metadata_block) write_metadata_block(ctx);
// Write dump hardware block if any
if(ctx->dirty_dumphw_block) write_dumphw_block(ctx);
// Write CICM XML block if any
if(ctx->dirty_cicm_block) write_cicm_block(ctx);
// Write Aaru metadata JSON block if any
if(ctx->dirty_json_block) write_aaru_json_block(ctx);
// Write the complete index at the end of the file
if(ctx->dirty_index_block)
{
res = write_index_block(ctx);
if(res != AARUF_STATUS_OK) return res;
}
if(ctx->deduplicate && ctx->sector_hash_map != NULL)
{
TRACE("Clearing sector hash map");
// Clear sector hash map
free_map(ctx->sector_hash_map);
ctx->sector_hash_map = NULL;
}
}
TRACE("Freeing memory pointers");
// This may do nothing if imageStream is NULL, but as the behaviour is undefined, better sure than sorry
if(ctx->imageStream != NULL)
{
fclose(ctx->imageStream);
ctx->imageStream = NULL;
}
// Free index entries array
if(ctx->index_entries != NULL)
{
utarray_free(ctx->index_entries);
ctx->index_entries = NULL;
}
free(ctx->sector_prefix);
ctx->sector_prefix = NULL;
free(ctx->sector_prefix_corrected);
ctx->sector_prefix_corrected = NULL;
free(ctx->sector_suffix);
ctx->sector_suffix = NULL;
free(ctx->sector_suffix_corrected);
ctx->sector_suffix_corrected = NULL;
free(ctx->sector_subchannel);
ctx->sector_subchannel = NULL;
free(ctx->mode2_subheaders);
ctx->mode2_subheaders = NULL;
TRACE("Freeing media tags");
if(ctx->mediaTags != NULL) HASH_ITER(hh, ctx->mediaTags, media_tag, tmp_media_tag)
{
HASH_DEL(ctx->mediaTags, media_tag);
free(media_tag->data);
free(media_tag);
}
#ifdef __linux__ // TODO: Implement
TRACE("Unmapping user data DDT if it is not in memory");
if(!ctx->in_memory_ddt)
{
munmap(ctx->user_data_ddt, ctx->mapped_memory_ddt_size);
ctx->user_data_ddt = NULL;
}
#endif
free(ctx->sector_prefix_ddt2);
ctx->sector_prefix_ddt2 = NULL;
free(ctx->sector_prefix_ddt);
ctx->sector_prefix_ddt = NULL;
free(ctx->sector_suffix_ddt2);
ctx->sector_suffix_ddt2 = NULL;
free(ctx->sector_suffix_ddt);
ctx->sector_suffix_ddt = NULL;
free(ctx->metadata_block);
ctx->metadata_block = NULL;
free(ctx->track_entries);
ctx->track_entries = NULL;
free(ctx->data_tracks);
ctx->data_tracks = NULL;
free(ctx->cicm_block);
ctx->cicm_block = NULL;
if(ctx->dump_hardware_entries_with_data != NULL)
{
for(int i = 0; i < ctx->dump_hardware_header.entries; i++)
{
free(ctx->dump_hardware_entries_with_data[i].extents);
ctx->dump_hardware_entries_with_data[i].extents = NULL;
free(ctx->dump_hardware_entries_with_data[i].manufacturer);
ctx->dump_hardware_entries_with_data[i].manufacturer = NULL;
free(ctx->dump_hardware_entries_with_data[i].model);
ctx->dump_hardware_entries_with_data[i].model = NULL;
free(ctx->dump_hardware_entries_with_data[i].revision);
ctx->dump_hardware_entries_with_data[i].revision = NULL;
free(ctx->dump_hardware_entries_with_data[i].firmware);
ctx->dump_hardware_entries_with_data[i].firmware = NULL;
free(ctx->dump_hardware_entries_with_data[i].serial);
ctx->dump_hardware_entries_with_data[i].serial = NULL;
free(ctx->dump_hardware_entries_with_data[i].softwareName);
ctx->dump_hardware_entries_with_data[i].softwareName = NULL;
free(ctx->dump_hardware_entries_with_data[i].softwareVersion);
ctx->dump_hardware_entries_with_data[i].softwareVersion = NULL;
free(ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem);
ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem = NULL;
}
free(ctx->dump_hardware_entries_with_data); // Free the array itself
ctx->dump_hardware_entries_with_data = NULL;
}
free(ctx->readableSectorTags);
ctx->readableSectorTags = NULL;
aaruf_ecc_cd_free(ctx->ecc_cd_context);
ctx->ecc_cd_context = NULL;
free(ctx->checksums.spamsum);
ctx->checksums.spamsum = NULL;
free(ctx->sector_id);
free(ctx->sector_ied);
free(ctx->sector_cpr_mai);
free(ctx->sector_edc);
// Free DDT allocations (v1 and v2)
free(ctx->user_data_ddt); // Legacy v1 DDT
free(ctx->user_data_ddt2); // v2 DDT primary/secondary
free(ctx->cached_secondary_ddt2); // Cached secondary DDT (read operations)
// Free LRU caches (uses cache->free_func to free cached values)
free_cache(&ctx->block_header_cache);
free_cache(&ctx->block_cache);
free(context);
TRACE("Exiting aaruf_close() = 0");
return 0;
}
aaruformat.h
write_flux_capture_payload
static int32_t write_flux_capture_payload(aaruformat_context *ctx, FluxCaptureRecord *record, FluxEntry *entry)
Serialize a single flux capture payload block to the image file.
Definition close.c:4371
write_dvd_long_sector_blocks
void write_dvd_long_sector_blocks(aaruformat_context *ctx)
Serialize DVD long sector auxiliary data blocks to the image file.
Definition close.c:1964
write_primary_ddt
static int32_t write_primary_ddt(aaruformat_context *ctx)
Write (flush) the multi-level primary DDT table header and data back to its file offset.
Definition close.c:288
write_cicm_block
static void write_cicm_block(aaruformat_context *ctx)
Serialize the CICM XML metadata block to the image file.
Definition close.c:3991
write_sector_subchannel
static void write_sector_subchannel(aaruformat_context *ctx)
Serialize the per-sector subchannel or tag data block.
Definition close.c:1647
write_index_block
static int32_t write_index_block(aaruformat_context *ctx)
Serialize the accumulated index entries at the end of the image and back-patch the header.
Definition close.c:4875
write_media_tags
static void write_media_tags(aaruformat_context *ctx)
Serialize all accumulated media tags to the image file.
Definition close.c:2640
write_dvd_title_key_decrypted_block
static void write_dvd_title_key_decrypted_block(aaruformat_context *ctx)
Serialize the DVD decrypted title key data block to the image file.
Definition close.c:2461
aaruf_close
int aaruf_close(void *context)
Close an Aaru image context, flushing pending data structures and releasing resources.
Definition close.c:5011
write_single_level_ddt
static int32_t write_single_level_ddt(aaruformat_context *ctx)
Serialize a single-level DDT (tableShift == 0) directly after its header.
Definition close.c:390
write_tape_file_block
static void write_tape_file_block(aaruformat_context *ctx)
Serialize the tape file metadata block to the image file.
Definition close.c:2916
write_dumphw_block
static void write_dumphw_block(aaruformat_context *ctx)
Serialize the dump hardware block containing acquisition environment information.
Definition close.c:3748
write_checksum_block
static void write_checksum_block(aaruformat_context *ctx)
Finalize any active checksum calculations and append a checksum block.
Definition close.c:691
write_sector_suffix
static void write_sector_suffix(aaruformat_context *ctx)
Serialize the optional CD sector suffix block (EDC/ECC region capture).
Definition close.c:1182
write_sector_suffix_ddt
static void write_sector_suffix_ddt(aaruformat_context *ctx)
Serialize the per-sector CD suffix status / index DeDuplication Table (DDT v2, suffix variant).
Definition close.c:1474
write_tracks_block
static void write_tracks_block(aaruformat_context *ctx)
Serialize the tracks metadata block and add it to the index.
Definition close.c:848
write_aaru_json_block
static void write_aaru_json_block(aaruformat_context *ctx)
Serialize the Aaru metadata JSON block to the image file.
Definition close.c:4141
write_geometry_block
static void write_geometry_block(aaruformat_context *ctx)
Serialize the geometry metadata block to the image file.
Definition close.c:3301
write_mode2_subheaders_block
static void write_mode2_subheaders_block(aaruformat_context *ctx)
Serialize a MODE 2 (XA) subheaders data block.
Definition close.c:914
write_tape_partition_block
static void write_tape_partition_block(aaruformat_context *ctx)
Serialize the tape partition metadata block to the image file.
Definition close.c:3162
write_sector_prefix_ddt
static void write_sector_prefix_ddt(aaruformat_context *ctx)
Serialize the per-sector CD prefix status / index DeDuplication Table (DDT v2, prefix variant).
Definition close.c:1315
write_tape_ddt
static int32_t write_tape_ddt(aaruformat_context *ctx)
Converts tape DDT hash table to array format and writes it as a single-level DDT.
Definition close.c:633
write_cached_secondary_ddt
static int32_t write_cached_secondary_ddt(aaruformat_context *ctx)
Flush a cached secondary (child) DeDuplication Table (DDT) to the image.
Definition close.c:77
write_metadata_block
static void write_metadata_block(aaruformat_context *ctx)
Serialize the metadata block containing image and media descriptive information.
Definition close.c:3450
write_sector_prefix
static void write_sector_prefix(aaruformat_context *ctx)
Serialize the optional CD sector prefix block.
Definition close.c:1045
write_flux_blocks
static int32_t write_flux_blocks(aaruformat_context *ctx)
Serialize all accumulated flux capture blocks to the image file.
Definition close.c:4754
LZMA_PROPERTIES_LENGTH
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
AARU_MAGIC
#define AARU_MAGIC
Magic identifier for AaruFormat container (ASCII "AARUFRMT").
Definition consts.h:64
MD5_DIGEST_LENGTH
#define MD5_DIGEST_LENGTH
Definition context.h:72
tapeFileHashEntry
struct TapeFileHashEntry tapeFileHashEntry
AARU_CALL
#define AARU_CALL
Definition decls.h:46
aaruf_sha1_final
void aaruf_sha1_final(sha1_ctx *ctx, unsigned char *result)
Definition sha1.c:124
aaruf_cst_transform
int32_t aaruf_cst_transform(const uint8_t *interleaved, uint8_t *sequential, size_t length)
Transforms interleaved subchannel data to sequential format.
Definition cst.c:35
aaruf_lzma_encode_buffer
int32_t aaruf_lzma_encode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, uint8_t *out_props, size_t *out_props_size, int32_t level, uint32_t dict_size, int32_t lc, int32_t lp, int32_t pb, int32_t fb, int32_t num_threads)
Encodes a buffer using LZMA compression.
Definition lzma.c:65
aaruf_crc64_data
uint64_t aaruf_crc64_data(const uint8_t *data, uint32_t len)
Definition crc64.c:160
aaruf_crc64_update
int aaruf_crc64_update(crc64_ctx *ctx, const uint8_t *data, uint32_t len)
Updates the CRC64 context with new data.
Definition crc64.c:55
aaruf_crc64_free
void aaruf_crc64_free(crc64_ctx *ctx)
Frees a CRC64 context.
Definition crc64.c:155
aaruf_sha256_final
void aaruf_sha256_final(sha256_ctx *ctx, unsigned char *result)
Definition sha256.c:115
aaruf_crc64_init
crc64_ctx * aaruf_crc64_init()
Initializes a CRC64 context.
Definition crc64.c:32
aaruf_md5_final
void aaruf_md5_final(md5_ctx *ctx, unsigned char *result)
Definition md5.c:485
aaruf_spamsum_free
void aaruf_spamsum_free(spamsum_ctx *ctx)
Frees a spamsum (fuzzy hash) context.
Definition spamsum.c:75
aaruf_get_datatype_for_media_tag_type
int32_t aaruf_get_datatype_for_media_tag_type(int32_t tag_type)
Converts an Aaru media tag type to an image data type.
Definition helpers.c:199
AARU_EXPORT
#define AARU_EXPORT
Definition decls.h:55
aaruf_spamsum_final
int aaruf_spamsum_final(spamsum_ctx *ctx, uint8_t *result)
Definition spamsum.c:191
aaruf_ecc_cd_free
void aaruf_ecc_cd_free(void *context)
Frees a Compact Disc ECC context and its internal tables.
Definition ecc_cd.c:99
aaruf_crc64_final
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
IndexBlock3
@ IndexBlock3
Block containing the index v3.
Definition enums.h:151
ChecksumBlock
@ ChecksumBlock
Block containing contents checksums.
Definition enums.h:156
DataBlock
@ DataBlock
Block containing data.
Definition enums.h:144
TapePartitionBlock
@ TapePartitionBlock
Block containing list of partitions for a tape image.
Definition enums.h:162
GeometryBlock
@ GeometryBlock
Block containing logical geometry.
Definition enums.h:152
DeDuplicationTableSecondary
@ DeDuplicationTableSecondary
Block containing a secondary deduplication table (v2).
Definition enums.h:148
AaruMetadataJsonBlock
@ AaruMetadataJsonBlock
Block containing JSON version of Aaru Metadata.
Definition enums.h:163
CicmBlock
@ CicmBlock
Block containing CICM XML metadata.
Definition enums.h:155
DeDuplicationTable2
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:146
TapeFileBlock
@ TapeFileBlock
Block containing list of files for a tape image.
Definition enums.h:161
DeDuplicationTableSAlpha
@ DeDuplicationTableSAlpha
Block containing a secondary deduplication table (v2) (mistake).
Definition enums.h:147
DumpHardwareBlock
@ DumpHardwareBlock
Block containing an array of hardware used to create the image.
Definition enums.h:160
MetadataBlock
@ MetadataBlock
Block containing metadata.
Definition enums.h:153
TracksBlock
@ TracksBlock
Block containing optical disc tracks.
Definition enums.h:154
FluxDataBlock
@ FluxDataBlock
Block containing flux data metadata.
Definition enums.h:164
DataStreamPayloadBlock
@ DataStreamPayloadBlock
Block containing compressed data stream payload (e.g., flux data, bitstreams).
Definition enums.h:165
OpticalDisc
@ OpticalDisc
Purely optical discs.
Definition enums.h:224
BlockMedia
@ BlockMedia
Media that is physically block-based or abstracted like that.
Definition enums.h:225
Blake3
@ Blake3
BLAKE3 hash.
Definition enums.h:179
Sha1
@ Sha1
SHA-1 hash.
Definition enums.h:176
Md5
@ Md5
MD5 hash.
Definition enums.h:175
Sha256
@ Sha256
SHA-256 hash.
Definition enums.h:177
SpamSum
@ SpamSum
SpamSum (context-triggered piecewise hash).
Definition enums.h:178
CdSectorSubchannel
@ CdSectorSubchannel
Compact Disc subchannel data.
Definition enums.h:116
AppleProfileTag
@ AppleProfileTag
Apple Profile (20‑byte) tag.
Definition enums.h:117
DvdSectorIed
@ DvdSectorIed
DVD ID Error Detection Code (IED).
Definition enums.h:129
FluxData
@ FluxData
Flux data.
Definition enums.h:134
DvdSectorCprMai
@ DvdSectorCprMai
DVD Copyright Management Information (CPR_MAI).
Definition enums.h:126
AppleSonyTag
@ AppleSonyTag
Apple Sony (12‑byte) tag.
Definition enums.h:118
CdSectorPrefix
@ CdSectorPrefix
Compact Disc sector prefix (sync, header).
Definition enums.h:114
PriamDataTowerTag
@ PriamDataTowerTag
Priam Data Tower (24‑byte) tag.
Definition enums.h:119
UserData
@ UserData
User (main) data.
Definition enums.h:46
DvdSectorEdc
@ DvdSectorEdc
DVD Error Detection Code (EDC).
Definition enums.h:130
DvdSectorTitleKeyDecrypted
@ DvdSectorTitleKeyDecrypted
Decrypted DVD Title Key.
Definition enums.h:127
DvdSectorId
@ DvdSectorId
DVD Identification Data (ID).
Definition enums.h:128
CdSectorSuffix
@ CdSectorSuffix
Compact Disc sector suffix (EDC, ECC P, ECC Q).
Definition enums.h:115
CompactDiscMode2Subheader
@ CompactDiscMode2Subheader
Compact Disc MODE 2 subheader.
Definition enums.h:123
AARU_FEATURE_RW_BLAKE3
@ AARU_FEATURE_RW_BLAKE3
BLAKE3 checksum is present (read/write support for BLAKE3 hashes).
Definition enums.h:271
AARUF_STATUS_INVALID_CONTEXT
@ AARUF_STATUS_INVALID_CONTEXT
Provided context/handle is invalid.
Definition enums.h:215
CompressionType
CompressionType
List of known compression types.
Definition enums.h:32
Lzma
@ Lzma
LZMA compression.
Definition enums.h:34
LzmaClauniaSubchannelTransform
@ LzmaClauniaSubchannelTransform
LZMA applied to Claunia Subchannel Transform processed data.
Definition enums.h:36
None
@ None
Not compressed.
Definition enums.h:33
AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA
#define AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA
Failure writing block payload.
Definition errors.h:63
AARUF_STATUS_OK
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:77
AARUF_ERROR_NOT_ENOUGH_MEMORY
#define AARUF_ERROR_NOT_ENOUGH_MEMORY
Memory allocation failure (critical).
Definition errors.h:48
AARUF_ERROR_INCORRECT_DATA_SIZE
#define AARUF_ERROR_INCORRECT_DATA_SIZE
Data size does not match expected size.
Definition errors.h:65
AARUF_ERROR_NOT_AARUFORMAT
#define AARUF_ERROR_NOT_AARUFORMAT
Input file/stream failed magic or structural validation.
Definition errors.h:40
AARUF_ERROR_CANNOT_WRITE_HEADER
#define AARUF_ERROR_CANNOT_WRITE_HEADER
Failure writing container header.
Definition errors.h:60
AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER
#define AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER
Failure writing block header.
Definition errors.h:62
AppleProfile
@ AppleProfile
Definition aaru.h:708
AppleSonySS
@ AppleSonySS
3.5", SS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
Definition aaru.h:250
PriamDataTower
@ PriamDataTower
Definition aaru.h:711
AppleFileWare
@ AppleFileWare
5.25", DS, ?D, ?? tracks, ?? spt, 512 bytes/sector, GCR, opposite side heads, aka Twiggy
Definition aaru.h:252
AppleSonyDS
@ AppleSonyDS
3.5", DS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
Definition aaru.h:251
free_map
void free_map(hash_map_t *map)
Frees all memory associated with a hash map.
Definition hash_map.c:78
internal.h
aaruf_close_current_block
int32_t aaruf_close_current_block(aaruformat_context *ctx)
Finalizes and writes the current data block to the AaruFormat image file.
Definition write.c:1438
flux_map_rebuild_from_entries
int32_t flux_map_rebuild_from_entries(aaruformat_context *ctx)
Rebuild the flux capture lookup map from the flux_entries array.
Definition flux.c:171
log.h
FATAL
#define FATAL(fmt,...)
Definition log.h:40
TRACE
#define TRACE(fmt,...)
Definition log.h:25
free_cache
void free_cache(struct CacheHeader *cache)
Frees all entries in the cache and clears it.
Definition lru.c:130
SHA1_DIGEST_LENGTH
#define SHA1_DIGEST_LENGTH
Definition sha1.h:39
SHA256_DIGEST_LENGTH
#define SHA256_DIGEST_LENGTH
Definition sha256.h:38
FUZZY_MAX_RESULT
#define FUZZY_MAX_RESULT
Definition spamsum.h:30
AaruHeaderV2
Version 2 container header with GUID, alignment shifts, and feature negotiation bitmaps.
Definition header.h:107
AaruHeaderV2::featureCompatible
uint64_t featureCompatible
Feature bits: unimplemented bits are ignorable (still R/W safe).
Definition header.h:121
AaruHeaderV2::indexOffset
uint64_t indexOffset
Absolute byte offset to primary index block (MUST be > 0; 0 => corrupt/unreadable).
Definition header.h:115
AaruMetadataJsonBlockHeader
Header for an Aaru metadata JSON block (identifier == BlockType::AaruMetadataJsonBlock).
Definition metadata.h:120
AaruMetadataJsonBlockHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::AaruMetadataJsonBlock.
Definition metadata.h:121
AaruMetadataJsonBlockHeader::length
uint32_t length
Length in bytes of the Aaru metadata JSON payload that follows.
Definition metadata.h:122
BlockHeader
Header preceding the compressed data payload of a data block (BlockType::DataBlock).
Definition data.h:71
BlockHeader::cmpLength
uint32_t cmpLength
Size in bytes of the compressed payload immediately following this header.
Definition data.h:76
BlockHeader::length
uint32_t length
Size in bytes of the uncompressed payload resulting after decompression.
Definition data.h:77
BlockHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::DataBlock.
Definition data.h:72
BlockHeader::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload (cmpLength bytes).
Definition data.h:78
BlockHeader::crc64
uint64_t crc64
CRC64-ECMA of the uncompressed payload (length bytes).
Definition data.h:79
BlockHeader::type
uint16_t type
Logical data classification (value from DataType).
Definition data.h:73
BlockHeader::compression
uint16_t compression
Compression algorithm used (value from CompressionType).
Definition data.h:74
ChecksumEntry
Per-checksum metadata immediately followed by the digest / signature bytes.
Definition checksum.h:91
ChecksumEntry::length
uint32_t length
Length in bytes of the digest that immediately follows this structure.
Definition checksum.h:93
ChecksumEntry::type
uint8_t type
Algorithm used (value from ChecksumAlgorithm).
Definition checksum.h:92
ChecksumHeader
Header that precedes the sequence of checksum entries for a checksum block.
Definition checksum.h:74
ChecksumHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::ChecksumBlock.
Definition checksum.h:75
ChecksumHeader::length
uint32_t length
Length in bytes of the payload (all entries + their digest data, excluding this header).
Definition checksum.h:76
ChecksumHeader::entries
uint8_t entries
Number of checksum entries that follow in the payload.
Definition checksum.h:77
Checksums::spamsum
uint8_t * spamsum
SpamSum fuzzy hash (ASCII), allocated length+1 with trailing 0.
Definition context.h:113
Checksums::hasSha256
bool hasSha256
True if sha256[] buffer populated.
Definition context.h:106
Checksums::sha1
uint8_t sha1[20]
SHA-1 digest (20 bytes).
Definition context.h:110
Checksums::sha256
uint8_t sha256[32]
SHA-256 digest (32 bytes).
Definition context.h:111
Checksums::md5
uint8_t md5[16]
MD5 digest (16 bytes).
Definition context.h:109
Checksums::hasSpamSum
bool hasSpamSum
True if spamsum pointer allocated and signature read.
Definition context.h:108
Checksums::hasSha1
bool hasSha1
True if sha1[] buffer populated.
Definition context.h:105
Checksums::blake3
uint8_t blake3[BLAKE3_OUT_LEN]
BLAKE3 digest (32 bytes).
Definition context.h:112
Checksums::hasMd5
bool hasMd5
True if md5[] buffer populated.
Definition context.h:104
Checksums::hasBlake3
bool hasBlake3
True if blake3[] buffer populated.
Definition context.h:107
CicmMetadataBlock
Header for a CICM XML metadata block (identifier == BlockType::CicmBlock).
Definition metadata.h:108
CicmMetadataBlock::length
uint32_t length
Length in bytes of the CICM metadata payload that follows.
Definition metadata.h:110
CicmMetadataBlock::identifier
uint32_t identifier
Block identifier, must be BlockType::CicmBlock.
Definition metadata.h:109
DataStreamPayloadHeader
Header structure for a DataStreamPayloadBlock containing data stream payload.
Definition flux.h:243
DataStreamPayloadHeader::dataType
uint16_t dataType
Data type classification (value from DataType), e.g., FluxData or BitstreamData.
Definition flux.h:245
DataStreamPayloadHeader::cmpLength
uint32_t cmpLength
Compressed length in bytes (includes LZMA properties if compression = Lzma).
Definition flux.h:247
DataStreamPayloadHeader::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA checksum of the compressed payload (or same as crc64 if uncompressed).
Definition flux.h:249
DataStreamPayloadHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::DataStreamPayloadBlock (0x4C505344, "DSPL").
Definition flux.h:244
DataStreamPayloadHeader::compression
uint16_t compression
Compression type: 0 = None, 1 = Lzma.
Definition flux.h:246
DataStreamPayloadHeader::length
uint32_t length
Uncompressed length in bytes.
Definition flux.h:248
DataStreamPayloadHeader::crc64
uint64_t crc64
CRC64-ECMA checksum of the uncompressed payload.
Definition flux.h:250
DdtHeader2
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
DdtHeader2::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA of compressed table payload.
Definition ddt.h:161
DdtHeader2::type
uint16_t type
Data classification (DataType) for sectors referenced by this table.
Definition ddt.h:144
DdtHeader2::start
uint64_t start
Base internal index covered by this table (used for secondary tables; currently informational).
Definition ddt.h:153
DdtHeader2::crc64
uint64_t crc64
CRC64-ECMA of uncompressed table payload.
Definition ddt.h:162
DdtHeader2::entries
uint64_t entries
Number of entries contained in (uncompressed) table payload.
Definition ddt.h:158
DdtHeader2::levels
uint8_t levels
Total number of hierarchy levels (root depth); > 0.
Definition ddt.h:146
DdtHeader2::length
uint64_t length
Uncompressed payload size in bytes.
Definition ddt.h:160
DdtHeader2::identifier
uint32_t identifier
Block identifier, must be BlockType::DeDuplicationTable2.
Definition ddt.h:143
DdtHeader2::tableShift
uint8_t tableShift
2^tableShift = number of logical sectors per primary entry (multi-level only; 0 for single-level or s...
Definition ddt.h:156
DdtHeader2::blocks
uint64_t blocks
Total internal span (negative + usable + overflow) in logical sectors.
Definition ddt.h:150
DdtHeader2::blockAlignmentShift
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
DdtHeader2::overflow
uint32_t overflow
Trailing dumped sectors beyond user area (overflow range), still mapped with entries.
Definition ddt.h:151
DdtHeader2::negative
uint32_t negative
Leading negative LBA count; added to external L to build internal index.
Definition ddt.h:149
DdtHeader2::tableLevel
uint8_t tableLevel
Zero-based level index of this table (0 = root, increases downward).
Definition ddt.h:147
DdtHeader2::compression
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
DdtHeader2::dataShift
uint8_t dataShift
2^dataShift = sectors represented per increment in blockIndex field.
Definition ddt.h:155
DdtHeader2::cmpLength
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
DdtHeader2::previousLevelOffset
uint64_t previousLevelOffset
Absolute byte offset of the parent (previous) level table; 0 if root.
Definition ddt.h:148
DumpExtent
Inclusive [start,end] logical sector range contributed by a single hardware environment.
Definition context.h:369
DumpHardwareEntriesWithData::firmware
uint8_t * firmware
Firmware version string or NULL.
Definition context.h:356
DumpHardwareEntriesWithData::revision
uint8_t * revision
Hardware revision string or NULL.
Definition context.h:355
DumpHardwareEntriesWithData::model
uint8_t * model
Model string or NULL.
Definition context.h:354
DumpHardwareEntriesWithData::softwareName
uint8_t * softwareName
Dump software name or NULL.
Definition context.h:358
DumpHardwareEntriesWithData::extents
struct DumpExtent * extents
Array of extents (entry.extents elements) or NULL.
Definition context.h:352
DumpHardwareEntriesWithData::manufacturer
uint8_t * manufacturer
Manufacturer string (UTF-8) or NULL.
Definition context.h:353
DumpHardwareEntriesWithData::softwareVersion
uint8_t * softwareVersion
Dump software version or NULL.
Definition context.h:359
DumpHardwareEntriesWithData::serial
uint8_t * serial
Serial number string or NULL.
Definition context.h:357
DumpHardwareEntriesWithData::entry
DumpHardwareEntry entry
Fixed-size header with lengths & counts.
Definition context.h:351
DumpHardwareEntriesWithData::softwareOperatingSystem
uint8_t * softwareOperatingSystem
Host operating system string or NULL.
Definition context.h:360
DumpHardwareEntry
Per-environment length table describing subsequent UTF-8 strings and optional extent array.
Definition dump.h:113
DumpHardwareEntry::softwareNameLength
uint32_t softwareNameLength
Length in bytes of dumping software name string.
Definition dump.h:119
DumpHardwareEntry::manufacturerLength
uint32_t manufacturerLength
Length in bytes of manufacturer UTF-8 string.
Definition dump.h:114
DumpHardwareEntry::softwareVersionLength
uint32_t softwareVersionLength
Length in bytes of dumping software version string.
Definition dump.h:120
DumpHardwareEntry::firmwareLength
uint32_t firmwareLength
Length in bytes of firmware version string.
Definition dump.h:117
DumpHardwareEntry::extents
uint32_t extents
Number of DumpExtent records following the strings (0 = none).
Definition dump.h:122
DumpHardwareEntry::modelLength
uint32_t modelLength
Length in bytes of model UTF-8 string.
Definition dump.h:115
DumpHardwareEntry::serialLength
uint32_t serialLength
Length in bytes of device serial number string.
Definition dump.h:118
DumpHardwareEntry::revisionLength
uint32_t revisionLength
Length in bytes of revision / hardware revision string.
Definition dump.h:116
DumpHardwareEntry::softwareOperatingSystemLength
uint32_t softwareOperatingSystemLength
Length in bytes of host operating system string.
Definition dump.h:121
DumpHardwareHeader
Header that precedes a sequence of dump hardware entries and their variable-length payload.
Definition dump.h:91
DumpHardwareHeader::crc64
uint64_t crc64
CRC64-ECMA of the payload (byte-swapped for legacy v1 images, handled automatically).
Definition dump.h:95
DumpHardwareHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::DumpHardwareBlock.
Definition dump.h:92
DumpHardwareHeader::length
uint32_t length
Total payload bytes after this header (sum of entries, strings, and extents arrays).
Definition dump.h:94
DumpHardwareHeader::entries
uint16_t entries
Number of DumpHardwareEntry records that follow.
Definition dump.h:93
FluxCaptureRecord
Internal structure for storing flux capture data during write mode.
Definition flux.h:271
FluxCaptureRecord::entry
FluxEntry entry
Flux entry metadata describing this capture.
Definition flux.h:272
FluxCaptureRecord::data_length
uint32_t data_length
Length of the data buffer in bytes.
Definition flux.h:274
FluxCaptureRecord::index_length
uint32_t index_length
Length of the index buffer in bytes.
Definition flux.h:276
FluxCaptureRecord::index_buffer
uint8_t * index_buffer
Pointer to the flux index buffer. Owned by the utarray, freed automatically.
Definition flux.h:275
FluxCaptureRecord::data_buffer
uint8_t * data_buffer
Pointer to the flux data buffer. Owned by the utarray, freed automatically.
Definition flux.h:273
FluxEntry
Metadata entry describing a single flux capture in the FluxDataBlock.
Definition flux.h:156
FluxEntry::indexResolution
uint64_t indexResolution
Resolution in picoseconds at which the index stream was sampled.
Definition flux.h:161
FluxEntry::dataResolution
uint64_t dataResolution
Resolution in picoseconds at which the data stream was sampled.
Definition flux.h:162
FluxEntry::head
uint32_t head
Head number the flux capture corresponds to. Typically 0 or 1 for double-sided media.
Definition flux.h:157
FluxEntry::payloadOffset
uint64_t payloadOffset
Block-aligned file offset where the DataStreamPayloadBlock containing this capture's data is stored,...
Definition flux.h:164
FluxEntry::track
uint16_t track
Track number the flux capture corresponds to. Track numbering is format-dependent.
Definition flux.h:158
FluxEntry::indexOffset
uint64_t indexOffset
Byte offset within the payload where the index buffer starts (equals data_length).
Definition flux.h:163
FluxEntry::subtrack
uint8_t subtrack
Subtrack number, allowing sub-stepping within a track. Used for fine positioning.
Definition flux.h:159
FluxEntry::captureIndex
uint32_t captureIndex
Capture index, allowing multiple captures for the same location (e.g., multiple revolutions).
Definition flux.h:160
FluxHeader
Header structure for a FluxDataBlock containing flux capture metadata.
Definition flux.h:117
FluxHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::FluxDataBlock (0x58554C46, "FLUX").
Definition flux.h:118
FluxHeader::entries
uint16_t entries
Number of FluxEntry records following this header. Maximum value: 65535.
Definition flux.h:119
FluxHeader::crc64
uint64_t crc64
CRC64-ECMA checksum of the FluxEntry array (header excluded).
Definition flux.h:121
FluxHeader::blockAlignmentShift
uint8_t blockAlignmentShift
Block alignment shift: 2^blockAlignmentShift = block alignment boundary in bytes.
Definition flux.h:120
GeometryBlockHeader
Legacy CHS style logical geometry metadata (BlockType::GeometryBlock).
Definition data.h:91
GeometryBlockHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::GeometryBlock.
Definition data.h:92
ImageInfo::MediaType
uint32_t MediaType
Media type identifier (see MediaType enum; 0=Unknown).
Definition aaru.h:943
ImageInfo::MetadataMediaType
uint8_t MetadataMediaType
Media type for sidecar generation (internal archival use).
Definition aaru.h:944
ImageInfo::Sectors
uint64_t Sectors
Total count of addressable logical sectors/blocks.
Definition aaru.h:936
IndexEntry
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
IndexEntry::blockType
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
IndexEntry::offset
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
IndexEntry::dataType
uint16_t dataType
Data classification (value from DataType) or unused for untyped blocks.
Definition index.h:111
IndexHeader3
Index header (version 3) adding hierarchical chaining (identifier == IndexBlock3).
Definition index.h:93
IndexHeader3::previous
uint64_t previous
File offset of a previous IndexBlock3 header (0 if none / root segment).
Definition index.h:97
IndexHeader3::identifier
uint32_t identifier
Block identifier (must be BlockType::IndexBlock3).
Definition index.h:94
IndexHeader3::crc64
uint64_t crc64
CRC64-ECMA of the local entries array (does NOT cover subindexes or previous chains).
Definition index.h:96
IndexHeader3::entries
uint64_t entries
Number of IndexEntry records that follow in this (sub)index block.
Definition index.h:95
MetadataBlockHeader
Header for a metadata block containing offsets and lengths to UTF-16LE descriptive strings.
Definition metadata.h:69
MetadataBlockHeader::commentsLength
uint32_t commentsLength
Length in bytes (including null) of comments string.
Definition metadata.h:78
MetadataBlockHeader::mediaSequence
int32_t mediaSequence
Sequence number within a multi-disc / multi-volume set (0-based or 1-based as producer defines).
Definition metadata.h:72
MetadataBlockHeader::identifier
uint32_t identifier
Block identifier, must be BlockType::MetadataBlock.
Definition metadata.h:70
MetadataBlockHeader::mediaTitleOffset
uint32_t mediaTitleOffset
Offset to UTF-16LE media title string.
Definition metadata.h:79
MetadataBlockHeader::driveModelLength
uint32_t driveModelLength
Length in bytes (including null) of drive model string.
Definition metadata.h:94
MetadataBlockHeader::driveManufacturerLength
uint32_t driveManufacturerLength
Length in bytes (including null) of drive manufacturer string.
Definition metadata.h:92
MetadataBlockHeader::blockSize
uint32_t blockSize
Total size in bytes of the entire metadata block (header + strings).
Definition metadata.h:71
MetadataBlockHeader::driveModelOffset
uint32_t driveModelOffset
Offset to UTF-16LE drive model string.
Definition metadata.h:93
MetadataBlockHeader::mediaModelOffset
uint32_t mediaModelOffset
Offset to UTF-16LE media model string.
Definition metadata.h:83
MetadataBlockHeader::creatorOffset
uint32_t creatorOffset
Offset to UTF-16LE creator string (or undefined if creatorLength==0).
Definition metadata.h:75
MetadataBlockHeader::mediaTitleLength
uint32_t mediaTitleLength
Length in bytes (including null) of media title string.
Definition metadata.h:80
MetadataBlockHeader::mediaManufacturerOffset
uint32_t mediaManufacturerOffset
Offset to UTF-16LE media manufacturer string.
Definition metadata.h:81
MetadataBlockHeader::driveSerialNumberLength
uint32_t driveSerialNumberLength
Length in bytes (including null) of drive serial number string.
Definition metadata.h:96
MetadataBlockHeader::driveSerialNumberOffset
uint32_t driveSerialNumberOffset
Offset to UTF-16LE drive serial number string.
Definition metadata.h:95
MetadataBlockHeader::mediaManufacturerLength
uint32_t mediaManufacturerLength
Length in bytes (including null) of media manufacturer string.
Definition metadata.h:82
MetadataBlockHeader::mediaModelLength
uint32_t mediaModelLength
Length in bytes (including null) of media model string.
Definition metadata.h:84
MetadataBlockHeader::driveFirmwareRevisionOffset
uint32_t driveFirmwareRevisionOffset
Offset to UTF-16LE drive firmware revision string.
Definition metadata.h:97
MetadataBlockHeader::lastMediaSequence
int32_t lastMediaSequence
Total number of media in the set; 0 or 1 if single item.
Definition metadata.h:74
MetadataBlockHeader::commentsOffset
uint32_t commentsOffset
Offset to UTF-16LE comments string.
Definition metadata.h:77
MetadataBlockHeader::driveManufacturerOffset
uint32_t driveManufacturerOffset
Offset to UTF-16LE drive manufacturer string.
Definition metadata.h:91
MetadataBlockHeader::mediaSerialNumberOffset
uint32_t mediaSerialNumberOffset
Offset to UTF-16LE media serial number string.
Definition metadata.h:85
MetadataBlockHeader::mediaSerialNumberLength
uint32_t mediaSerialNumberLength
Length in bytes (including null) of media serial number string.
Definition metadata.h:86
MetadataBlockHeader::mediaPartNumberOffset
uint32_t mediaPartNumberOffset
Offset to UTF-16LE media part number string.
Definition metadata.h:89
MetadataBlockHeader::mediaPartNumberLength
uint32_t mediaPartNumberLength
Length in bytes (including null) of media part number string.
Definition metadata.h:90
MetadataBlockHeader::mediaBarcodeLength
uint32_t mediaBarcodeLength
Length in bytes (including null) of media barcode string.
Definition metadata.h:88
MetadataBlockHeader::creatorLength
uint32_t creatorLength
Length in bytes (including null) of creator string (0 if absent).
Definition metadata.h:76
MetadataBlockHeader::driveFirmwareRevisionLength
uint32_t driveFirmwareRevisionLength
Length in bytes (including null) of drive firmware revision string.
Definition metadata.h:98
MetadataBlockHeader::mediaBarcodeOffset
uint32_t mediaBarcodeOffset
Offset to UTF-16LE media barcode string.
Definition metadata.h:87
TapeDdtHashEntry
Definition context.h:144
TapeDdtHashEntry::key
uint64_t key
Key: sector address.
Definition context.h:145
TapeDdtHashEntry::value
uint64_t value
Value: DDT entry.
Definition context.h:146
TapeFileEntry
Describes a single logical file on a tape medium.
Definition tape.h:134
TapeFileHashEntry::fileEntry
TapeFileEntry fileEntry
The actual tape file data.
Definition context.h:132
TapeFileHeader
Header for a tape file metadata block containing file layout information.
Definition tape.h:238
TapeFileHeader::identifier
uint32_t identifier
Block type identifier.
Definition tape.h:239
TapeFileHeader::crc64
uint64_t crc64
CRC64-ECMA checksum of the entry data.
Definition tape.h:245
TapeFileHeader::length
uint64_t length
Size of entry data in bytes (excluding this header).
Definition tape.h:243
TapePartitionEntry
Describes a single physical partition on a tape medium.
Definition tape.h:320
TapePartitionHashEntry
Definition context.h:137
TapePartitionHashEntry::partitionEntry
TapePartitionEntry partitionEntry
The actual tape partition data.
Definition context.h:139
TapePartitionHeader
Header for a tape partition metadata block containing partition layout information.
Definition tape.h:441
TapePartitionHeader::crc64
uint64_t crc64
CRC64-ECMA checksum of the entry data.
Definition tape.h:448
TapePartitionHeader::length
uint64_t length
Size of entry data in bytes (excluding this header).
Definition tape.h:446
TapePartitionHeader::identifier
uint32_t identifier
Block type identifier.
Definition tape.h:442
TrackEntry
Single optical disc track descriptor (sequence, type, LBAs, session, ISRC, flags).
Definition optical.h:72
TracksHeader
Header for an optical tracks block listing track entries.
Definition optical.h:62
TracksHeader::entries
uint16_t entries
Number of TrackEntry records following this header.
Definition optical.h:64
aaruformat_context
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
aaruformat_context::media_barcode
uint8_t * media_barcode
Barcode of the media represented by the image.
Definition context.h:225
aaruformat_context::user_data_ddt_header
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:192
aaruformat_context::checksums
Checksums checksums
Whole-image checksums discovered.
Definition context.h:272
aaruformat_context::creator
uint8_t * creator
Who (person) created the image?
Definition context.h:219
aaruformat_context::dirty_checksum_block
bool dirty_checksum_block
True if checksum block should be written during close.
Definition context.h:320
aaruformat_context::deduplicate
bool deduplicate
Storage deduplication active (duplicates coalesce).
Definition context.h:302
aaruformat_context::compression_enabled
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:303
aaruformat_context::cicm_block
uint8_t * cicm_block
CICM XML payload.
Definition context.h:217
aaruformat_context::sector_cpr_mai
uint8_t * sector_cpr_mai
DVD sector CPR_MAI (6 bytes) if present.
Definition context.h:210
aaruformat_context::dirty_media_tags
bool dirty_media_tags
True if media tags should be written during close.
Definition context.h:330
aaruformat_context::sector_hash_map
hash_map_t * sector_hash_map
Deduplication hash map (fingerprint->entry mapping).
Definition context.h:256
aaruformat_context::flux_data_header
FluxHeader flux_data_header
Flux data header (if present).
Definition context.h:311
aaruformat_context::sector_prefix_corrected
uint8_t * sector_prefix_corrected
Corrected variant (post error correction) if stored.
Definition context.h:203
aaruformat_context::user_data_ddt
uint64_t * user_data_ddt
Legacy flat DDT pointer (NULL when using v2 mini/big arrays).
Definition context.h:184
aaruformat_context::flux_captures
UT_array * flux_captures
Pending flux capture payloads (write path).
Definition context.h:313
aaruformat_context::sha256_context
sha256_ctx sha256_context
Opaque SHA-256 context for streaming updates.
Definition context.h:275
aaruformat_context::data_tracks
TrackEntry * data_tracks
Filtered list of data tracks (subset of trackEntries).
Definition context.h:246
aaruformat_context::calculating_sha256
bool calculating_sha256
True if whole-image SHA-256 being calculated on-the-fly.
Definition context.h:278
aaruformat_context::dirty_primary_ddt
bool dirty_primary_ddt
True if primary DDT table should be written during close.
Definition context.h:318
aaruformat_context::drive_firmware_revision
uint8_t * drive_firmware_revision
Firmware revision of the drive used to read the media represented by the image.
Definition context.h:231
aaruformat_context::media_serial_number
uint8_t * media_serial_number
Serial number of the media represented by the image.
Definition context.h:224
aaruformat_context::block_header_cache
struct CacheHeader block_header_cache
LRU/Cache header for block headers.
Definition context.h:259
aaruformat_context::sector_ied
uint8_t * sector_ied
DVD sector IED (2 bytes) if present.
Definition context.h:209
aaruformat_context::md5_context
md5_ctx md5_context
Opaque MD5 context for streaming updates.
Definition context.h:273
aaruformat_context::dirty_sector_suffix_block
bool dirty_sector_suffix_block
True if sector suffix block should be written during close.
Definition context.h:325
aaruformat_context::user_data_ddt2
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:190
aaruformat_context::metadata_block_header
MetadataBlockHeader metadata_block_header
Metadata block header.
Definition context.h:233
aaruformat_context::sector_prefix
uint8_t * sector_prefix
Raw per-sector prefix (e.g., sync+header) uncorrected.
Definition context.h:202
aaruformat_context::dirty_dvd_title_key_decrypted_block
bool dirty_dvd_title_key_decrypted_block
True if decrypted title key block should be written during close.
Definition context.h:329
aaruformat_context::sector_suffix_ddt2
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:189
aaruformat_context::tape_files
tapeFileHashEntry * tape_files
Hash table root for tape files.
Definition context.h:306
aaruformat_context::dirty_mode2_subheaders_block
bool dirty_mode2_subheaders_block
True if MODE2 subheader block should be written during close.
Definition context.h:322
aaruformat_context::dirty_tape_partition_block
bool dirty_tape_partition_block
True if tape partition block should be written during close.
Definition context.h:333
aaruformat_context::cached_ddt_offset
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:193
aaruformat_context::dirty_cicm_block
bool dirty_cicm_block
True if CICM metadata block should be written during close.
Definition context.h:337
aaruformat_context::is_tape
bool is_tape
True if the image is a tape image.
Definition context.h:308
aaruformat_context::sector_edc
uint8_t * sector_edc
DVD sector EDC (4 bytes) if present.
Definition context.h:211
aaruformat_context::calculating_sha1
bool calculating_sha1
True if whole-image SHA-1 being calculated on-the-fly.
Definition context.h:277
aaruformat_context::media_model
uint8_t * media_model
Model of the media represented by the image.
Definition context.h:223
aaruformat_context::dirty_tracks_block
bool dirty_tracks_block
True if tracks block should be written during close.
Definition context.h:321
aaruformat_context::drive_serial_number
uint8_t * drive_serial_number
Serial number of the drive used to read the media represented by the image.
Definition context.h:229
aaruformat_context::ecc_cd_context
CdEccContext * ecc_cd_context
CD ECC/EDC helper tables (allocated on demand).
Definition context.h:251
aaruformat_context::block_cache
struct CacheHeader block_cache
LRU/Cache header for block payloads.
Definition context.h:260
aaruformat_context::sector_suffix_ddt
uint32_t * sector_suffix_ddt
Legacy CD sector suffix DDT.
Definition context.h:187
aaruformat_context::dirty_sector_prefix_block
bool dirty_sector_prefix_block
True if sector prefix block should be written during close.
Definition context.h:323
aaruformat_context::drive_manufacturer
uint8_t * drive_manufacturer
Manufacturer of the drive used to read the media represented by the image.
Definition context.h:227
aaruformat_context::in_memory_ddt
bool in_memory_ddt
True if primary (and possibly secondary) DDT loaded.
Definition context.h:199
aaruformat_context::dirty_index_block
bool dirty_index_block
True if index block should be written during close.
Definition context.h:340
aaruformat_context::sector_suffix
uint8_t * sector_suffix
Raw per-sector suffix (EDC/ECC) uncorrected.
Definition context.h:204
aaruformat_context::header
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:178
aaruformat_context::flux_entries
FluxEntry * flux_entries
Array of flux entries (flux_data_header.entries elements).
Definition context.h:312
aaruformat_context::dirty_json_block
bool dirty_json_block
True if JSON metadata block should be written during close.
Definition context.h:338
aaruformat_context::is_writing
bool is_writing
True if context opened/created for writing.
Definition context.h:295
aaruformat_context::tape_ddt
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:185
aaruformat_context::spamsum_context
spamsum_ctx * spamsum_context
Opaque SpamSum context for streaming updates.
Definition context.h:270
aaruformat_context::cicm_block_header
CicmMetadataBlock cicm_block_header
CICM metadata header (if present).
Definition context.h:234
aaruformat_context::sector_prefix_offset
size_t sector_prefix_offset
Current position in sector_prefix.
Definition context.h:289
aaruformat_context::drive_model
uint8_t * drive_model
Model of the drive used to read the media represented by the image.
Definition context.h:228
aaruformat_context::dirty_single_level_ddt
bool dirty_single_level_ddt
True if single-level DDT should be written during close.
Definition context.h:319
aaruformat_context::magic
uint64_t magic
File magic (AARU_MAGIC) post-open.
Definition context.h:177
aaruformat_context::writing_buffer
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:283
aaruformat_context::sector_prefix_ddt2
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:188
aaruformat_context::calculating_spamsum
bool calculating_spamsum
True if whole-image SpamSum being calculated on-the-fly.
Definition context.h:279
aaruformat_context::primary_ddt_offset
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:195
aaruformat_context::mediaTags
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:267
aaruformat_context::blake3_context
blake3_hasher * blake3_context
Opaque BLAKE3 context for streaming updates.
Definition context.h:271
aaruformat_context::calculating_blake3
bool calculating_blake3
True if whole-image BLAKE3 being calculated on-the-fly.
Definition context.h:280
aaruformat_context::dump_hardware_entries_with_data
struct DumpHardwareEntriesWithData * dump_hardware_entries_with_data
Array of dump hardware entries + strings.
Definition context.h:215
aaruformat_context::calculating_md5
bool calculating_md5
True if whole-image MD5 being calculated on-the-fly.
Definition context.h:276
aaruformat_context::dirty_sector_suffix_ddt
bool dirty_sector_suffix_ddt
True if sector suffix DDT should be written during close.
Definition context.h:326
aaruformat_context::geometry_block
GeometryBlockHeader geometry_block
Logical geometry block (if present).
Definition context.h:232
aaruformat_context::sector_suffix_offset
size_t sector_suffix_offset
Current position in sector_suffix.
Definition context.h:290
aaruformat_context::cached_secondary_ddt2
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:191
aaruformat_context::json_block
uint8_t * json_block
JSON metadata block payload (UTF-8).
Definition context.h:218
aaruformat_context::media_part_number
uint8_t * media_part_number
Part number of the media represented by the image.
Definition context.h:226
aaruformat_context::sector_decrypted_title_key
uint8_t * sector_decrypted_title_key
DVD decrypted title key (5 bytes) if present.
Definition context.h:212
aaruformat_context::dirty_dumphw_block
bool dirty_dumphw_block
True if dump hardware block should be written during close.
Definition context.h:336
aaruformat_context::json_block_header
AaruMetadataJsonBlockHeader json_block_header
JSON metadata block header (if present).
Definition context.h:236
aaruformat_context::dirty_metadata_block
bool dirty_metadata_block
True if metadata block should be written during close.
Definition context.h:335
aaruformat_context::sector_subchannel
uint8_t * sector_subchannel
Raw 96-byte subchannel (if captured).
Definition context.h:206
aaruformat_context::comments
uint8_t * comments
Image comments.
Definition context.h:221
aaruformat_context::dirty_sector_subchannel_block
bool dirty_sector_subchannel_block
True if subchannel block should be written during close.
Definition context.h:327
aaruformat_context::imageStream
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:179
aaruformat_context::dirty_dvd_long_sector_blocks
bool dirty_dvd_long_sector_blocks
True if DVD long sector blocks should be written during close.
Definition context.h:328
aaruformat_context::dirty_sector_prefix_ddt
bool dirty_sector_prefix_ddt
True if sector prefix DDT should be written during close.
Definition context.h:324
aaruformat_context::index_entries
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:255
aaruformat_context::dirty_tape_file_block
bool dirty_tape_file_block
True if tape file block should be written during close.
Definition context.h:332
aaruformat_context::mode2_subheaders
uint8_t * mode2_subheaders
MODE2 Form1/Form2 8-byte subheaders (concatenated).
Definition context.h:207
aaruformat_context::image_info
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:263
aaruformat_context::dirty_flux_block
bool dirty_flux_block
True if flux block should be written during close.
Definition context.h:339
aaruformat_context::sector_id
uint8_t * sector_id
DVD sector ID (4 bytes) if present.
Definition context.h:208
aaruformat_context::dump_hardware_header
DumpHardwareHeader dump_hardware_header
Dump hardware header.
Definition context.h:235
aaruformat_context::sha1_context
sha1_ctx sha1_context
Opaque SHA-1 context for streaming updates.
Definition context.h:274
aaruformat_context::dirty_secondary_ddt
bool dirty_secondary_ddt
True if secondary DDT tables should be written during close.
Definition context.h:317
aaruformat_context::readableSectorTags
bool * readableSectorTags
Per-sector boolean array (optical tags read successfully?).
Definition context.h:266
aaruformat_context::dirty_tape_ddt
bool dirty_tape_ddt
True if tape DDT should be written during close.
Definition context.h:331
aaruformat_context::tape_partitions
TapePartitionHashEntry * tape_partitions
Hash table root for tape partitions.
Definition context.h:307
aaruformat_context::sector_prefix_ddt
uint32_t * sector_prefix_ddt
Legacy CD sector prefix DDT (deprecated by *2).
Definition context.h:186
aaruformat_context::lzma_dict_size
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:301
aaruformat_context::track_entries
TrackEntry * track_entries
Full track list (tracksHeader.entries elements).
Definition context.h:245
aaruformat_context::sector_suffix_corrected
uint8_t * sector_suffix_corrected
Corrected suffix if stored separately.
Definition context.h:205
aaruformat_context::metadata_block
uint8_t * metadata_block
Raw metadata UTF-16LE concatenated strings.
Definition context.h:216
aaruformat_context::cached_ddt_position
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:194
aaruformat_context::media_title
uint8_t * media_title
Title of the media represented by the image.
Definition context.h:220
aaruformat_context::dirty_geometry_block
bool dirty_geometry_block
True if geometry block should be written during close.
Definition context.h:334
aaruformat_context::mapped_memory_ddt_size
size_t mapped_memory_ddt_size
Length of mmapped DDT if userDataDdt is mmapped.
Definition context.h:196
aaruformat_context::media_manufacturer
uint8_t * media_manufacturer
Manufacturer of the media represented by the image.
Definition context.h:222
aaruformat_context::tracks_header
TracksHeader tracks_header
Tracks header (optical) if present.
Definition context.h:247
crc64_ctx
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56
mediaTagEntry
Hash table entry for an arbitrary media tag (e.g., proprietary drive/medium descriptor).
Definition context.h:122
mediaTagEntry::data
uint8_t * data
Tag data blob (opaque to library core); length bytes long.
Definition context.h:123
mediaTagEntry::type
int32_t type
Numeric type identifier.
Definition context.h:124
mediaTagEntry::length
uint32_t length
Length in bytes of data.
Definition context.h:125