Add function to serialize dump hardware block metadata

This commit is contained in:
2025-10-05 15:39:10 +01:00
parent 139a442682
commit 31229f2050

View File

@@ -1630,6 +1630,285 @@ static void write_metadata_block(aaruformatContext *ctx)
free(buffer); free(buffer);
} }
/**
* @brief Serialize the dump hardware block containing acquisition environment information.
*
* This function writes a DumpHardwareBlock to the image file, documenting the hardware and software
* environments used to create the image. A dump hardware block records one or more "dump environments"
* typically combinations of physical devices (drives, controllers, adapters) and the software stacks
* that performed the read operations. This metadata is essential for understanding the imaging context,
* validating acquisition integrity, reproducing imaging conditions, and supporting forensic or archival
* documentation requirements.
*
* Each environment entry includes hardware identification (manufacturer, model, revision, firmware,
* serial number), software identification (name, version, operating system), and optional extent ranges
* that specify which logical sectors or units were contributed by that particular environment. This
* structure supports complex imaging scenarios where multiple devices or software configurations were
* used to create a composite image.
*
* The dump hardware block is optional; if no dump hardware information has been populated
* (dumpHardwareEntriesWithData is NULL, entries count is zero, or identifier is not set to
* DumpHardwareBlock), the function returns immediately without writing anything. This no-op behavior
* allows the close operation to proceed gracefully whether or not dump hardware metadata was included
* during image creation.
*
* **Block structure:**
* The serialized block consists of:
* 1. DumpHardwareHeader (16 bytes: identifier, entries count, payload length, CRC64)
* 2. For each dump hardware entry (variable size):
* - DumpHardwareEntry (36 bytes: length fields for all strings and extent count)
* - Variable-length UTF-8 strings in order: manufacturer, model, revision, firmware, serial,
* software name, software version, software operating system
* - Array of DumpExtent structures (16 bytes each) if extent count > 0
*
* All strings are UTF-8 encoded and NOT null-terminated in the serialized block. String lengths
* are measured in bytes, not character counts.
*
* **Serialization process:**
* 1. Allocate a temporary buffer sized to hold the complete block (header + all payload data)
* 2. Iterate through all dump hardware entries in ctx->dumpHardwareEntriesWithData
* 3. For each entry, copy the DumpHardwareEntry structure followed by each non-NULL string
* (only if the corresponding length > 0), then copy the extent array (if extents > 0)
* 4. Calculate CRC64-ECMA over the payload (everything after the header)
* 5. Copy the DumpHardwareHeader with calculated CRC64 to the beginning of the buffer
* 6. Align file position to block boundary
* 7. Write the complete buffer to the image file
* 8. Add index entry on successful write
* 9. Free the temporary buffer
*
* **Alignment and file positioning:**
* Before writing the block, the file position is moved to EOF and then aligned forward to the
* next boundary satisfying (position & alignment_mask) == 0, where alignment_mask is derived
* from ctx->userDataDdtHeader.blockAlignmentShift. This ensures the dump hardware block begins
* on a properly aligned offset for efficient I/O and compliance with the Aaru format specification.
*
* **CRC64 calculation:**
* The function calculates CRC64-ECMA over the payload portion of the buffer (everything after
* the DumpHardwareHeader) and stores it in the header before writing. This checksum allows
* verification of dump hardware block integrity when reading the image.
*
* **Index registration:**
* After successfully writing the complete block, an IndexEntry is appended to ctx->indexEntries with:
* - blockType = DumpHardwareBlock
* - dataType = 0 (dump hardware blocks have no subtype)
* - offset = the aligned file position where the block was written
*
* **Error handling:**
* Memory allocation failures (calloc returning NULL) cause immediate return without writing.
* Bounds checking is performed during serialization; if calculated entry sizes exceed the allocated
* buffer, the buffer is freed and the function returns without writing. Write errors (fwrite
* returning < 1) are silently ignored; no index entry is added if the write fails. Diagnostic
* TRACE logs report success or failure. The function does not propagate error codes; higher-level
* close logic must validate overall integrity if needed.
*
* **No-op conditions:**
* - ctx->dumpHardwareEntriesWithData is NULL (no hardware data loaded) OR
* - ctx->dumpHardwareHeader.entries == 0 (no entries to write) OR
* - ctx->dumpHardwareHeader.identifier != DumpHardwareBlock (block not properly initialized)
*
* @param ctx Pointer to an initialized aaruformatContext in write mode. Must not be NULL.
* ctx->dumpHardwareHeader contains the header with identifier, entry count, and
* total payload length. ctx->dumpHardwareEntriesWithData contains the array of
* dump hardware entries with their associated string data and extents (may be NULL
* if no dump hardware was added). ctx->imageStream must be open and writable.
* ctx->indexEntries must be initialized (utarray) to accept new index entries.
*
* @note Dump Hardware Environments:
* - Each entry represents one hardware/software combination used during imaging
* - Multiple entries support scenarios where different devices contributed different sectors
* - Extent arrays specify which logical sector ranges each environment contributed
* - Empty extent arrays (extents == 0) indicate the environment dumped the entire medium
* - Overlapping extents between entries may indicate verification passes or redundancy
*
* @note Hardware Identification Fields:
* - manufacturer: Device manufacturer (e.g., "Plextor", "Sony", "Samsung")
* - model: Device model number (e.g., "PX-716A", "DRU-820A")
* - revision: Hardware revision identifier
* - firmware: Firmware version (e.g., "1.11", "KY08")
* - serial: Device serial number for unique identification
*
* @note Software Identification Fields:
* - softwareName: Dumping software name (e.g., "Aaru", "ddrescue", "IsoBuster")
* - softwareVersion: Software version (e.g., "5.3.0", "1.25")
* - softwareOperatingSystem: Host OS (e.g., "Linux 5.10.0", "Windows 10", "macOS 12.0")
*
* @note String Encoding:
* - All strings are UTF-8 encoded
* - Strings are NOT null-terminated in the serialized block
* - String lengths in DumpHardwareEntry are in bytes, not character counts
* - The library maintains null-terminated strings in memory for convenience
* - Only non-null-terminated data is written to the file
*
* @note Memory Management:
* - The function allocates a temporary buffer to serialize the entire block
* - The buffer is freed before the function returns, regardless of success or failure
* - The source data in ctx->dumpHardwareEntriesWithData is not modified
* - The source data is freed later during context cleanup (aaruf_close)
*
* @note Use Cases:
* - Forensic documentation requiring complete equipment chain of custody
* - Archival metadata for long-term preservation requirements
* - Reproducing imaging conditions for verification or re-imaging
* - Identifying firmware-specific issues or drive-specific behaviors
* - Multi-device imaging scenario documentation
* - Correlating imaging artifacts with specific hardware/software combinations
*
* @note Order in Close Sequence:
* - Dump hardware blocks are typically written after sector data but before metadata blocks
* - The exact position in the file depends on what other blocks precede it
* - The index entry ensures the dump hardware block can be located during subsequent opens
*
* @warning The temporary buffer allocation may fail on systems with limited memory or when the
* dump hardware block is extremely large (many entries with long strings and extents).
* Allocation failures result in silent no-op; the image is created without dump hardware.
*
* @warning Bounds checking during serialization protects against buffer overruns. If calculated
* entry sizes exceed the allocated buffer length (which should never occur if the
* header's length field is correct), the function aborts without writing. This is a
* sanity check against data corruption.
*
* @see DumpHardwareHeader for the block header structure definition.
* @see DumpHardwareEntry for the per-environment entry structure definition.
* @see DumpExtent for the extent range structure definition.
* @see aaruf_get_dumphw() for retrieving dump hardware from opened images.
* @see process_dumphw_block() for the loading process during image opening.
*
* @internal
*/
static void write_dumphw_block(aaruformatContext *ctx)
{
if(ctx->dumpHardwareEntriesWithData == NULL || ctx->dumpHardwareHeader.entries == 0 ||
ctx->dumpHardwareHeader.identifier != DumpHardwareBlock)
return;
const size_t required_length = sizeof(DumpHardwareHeader) + ctx->dumpHardwareHeader.length;
uint8_t *buffer = calloc(1, required_length);
if(buffer == NULL) return;
// Start to iterate and copy the data
size_t offset = 0;
for(int i = 0; i < ctx->dumpHardwareHeader.entries; i++)
{
size_t entry_size = sizeof(DumpHardwareEntry) + ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength +
ctx->dumpHardwareEntriesWithData[i].entry.modelLength +
ctx->dumpHardwareEntriesWithData[i].entry.revisionLength +
ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength +
ctx->dumpHardwareEntriesWithData[i].entry.serialLength +
ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength +
ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength +
ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength +
ctx->dumpHardwareEntriesWithData[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->dumpHardwareEntriesWithData[i].entry, sizeof(DumpHardwareEntry));
offset += sizeof(DumpHardwareEntry);
if(ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].manufacturer != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].manufacturer,
ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.modelLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].model != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].model,
ctx->dumpHardwareEntriesWithData[i].entry.modelLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.modelLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.revisionLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].revision != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].revision,
ctx->dumpHardwareEntriesWithData[i].entry.revisionLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.revisionLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].firmware != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].firmware,
ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.serialLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].serial != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].serial,
ctx->dumpHardwareEntriesWithData[i].entry.serialLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.serialLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].softwareName != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].softwareName,
ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].softwareVersion != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].softwareVersion,
ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength > 0 &&
ctx->dumpHardwareEntriesWithData[i].softwareOperatingSystem != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].softwareOperatingSystem,
ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength);
offset += ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength;
}
if(ctx->dumpHardwareEntriesWithData[i].entry.extents > 0 && ctx->dumpHardwareEntriesWithData[i].extents != NULL)
{
memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].extents,
ctx->dumpHardwareEntriesWithData[i].entry.extents * sizeof(DumpExtent));
offset += ctx->dumpHardwareEntriesWithData[i].entry.extents * sizeof(DumpExtent);
}
}
// Calculate CRC64
ctx->dumpHardwareHeader.crc64 =
aaruf_crc64_data(buffer + sizeof(DumpHardwareHeader), ctx->dumpHardwareHeader.length);
// Copy header
memcpy(buffer, &ctx->dumpHardwareHeader, sizeof(DumpHardwareHeader));
fseek(ctx->imageStream, 0, SEEK_END);
long block_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.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");
// 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->indexEntries, &index_entry);
TRACE("Added dump hardware block index entry at offset %" PRIu64, block_position);
}
free(buffer);
}
/** /**
* @brief Serialize the CICM XML metadata block to the image file. * @brief Serialize the CICM XML metadata block to the image file.
* *
@@ -2139,6 +2418,9 @@ int aaruf_close(void *context)
// Write metadata block // Write metadata block
write_metadata_block(ctx); write_metadata_block(ctx);
// Write dump hardware block if any
write_dumphw_block(ctx);
// Write CICM XML block if any // Write CICM XML block if any
write_cicm_block(ctx); write_cicm_block(ctx);