From ca60bc53ed70a777724012a1dc73ba336b7e85bf Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Tue, 7 Oct 2025 14:40:57 +0100 Subject: [PATCH] Add support for processing tape partition metadata blocks --- include/aaruformat/context.h | 10 +- include/internal.h | 1 + src/blocks/tape.c | 221 ++++++++++++++++++++++++++++++++++- src/open.c | 3 + 4 files changed, 232 insertions(+), 3 deletions(-) diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index 34439ba..b1856de 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -130,6 +130,13 @@ typedef struct TapeFileHashEntry UT_hash_handle hh; ///< UTHASH handle } tapeFileHashEntry; +typedef struct TapePartitionHashEntry +{ + uint8_t key; ///< Key: partition + TapePartitionEntry partitionEntry; ///< The actual tape partition data + UT_hash_handle hh; ///< UTHASH handle +} TapePartitionHashEntry; + /** \struct aaruformatContext * \brief Master context representing an open or in‑creation Aaru image. * @@ -256,7 +263,8 @@ typedef struct aaruformatContext bool compression_enabled; ///< True if block compression enabled (writing path). uint32_t lzma_dict_size; ///< LZMA dictionary size (writing path). - tapeFileHashEntry *tapeFiles; ///< Hash table root for tape files + tapeFileHashEntry *tapeFiles; ///< Hash table root for tape files + TapePartitionHashEntry *tapePartitions; ///< Hash table root for tape partitions } aaruformatContext; /** \struct DumpHardwareEntriesWithData diff --git a/include/internal.h b/include/internal.h index 432dbd4..8b6745e 100644 --- a/include/internal.h +++ b/include/internal.h @@ -38,6 +38,7 @@ void process_aaru_metadata_json_block(aaruformatContext *ctx, const IndexEn void process_dumphw_block(aaruformatContext *ctx, const IndexEntry *entry); void process_checksum_block(aaruformatContext *ctx, const IndexEntry *entry); void process_tape_files_block(aaruformatContext *ctx, const IndexEntry *entry); +void process_tape_partitions_block(aaruformatContext *ctx, const IndexEntry *entry); void add_subindex_entries(aaruformatContext *ctx, UT_array *index_entries, IndexEntry *subindex_entry); int32_t decode_ddt_entry_v1(aaruformatContext *ctx, uint64_t sector_address, uint64_t *offset, uint64_t *block_offset, uint8_t *sector_status); diff --git a/src/blocks/tape.c b/src/blocks/tape.c index 0ef75b7..3f0ca59 100644 --- a/src/blocks/tape.c +++ b/src/blocks/tape.c @@ -209,8 +209,8 @@ void process_tape_files_block(aaruformatContext *ctx, const IndexEntry *entry) // Free old entry if it was replaced if(old_entry != NULL) { - TRACE("Replaced existing tape file entry for partition %u, file %u", entries[i].Partition, - entries[i].File); free(old_entry); + TRACE("Replaced existing tape file entry for partition %u, file %u", entries[i].Partition, entries[i].File); + free(old_entry); } else TRACE("Added new tape file entry for partition %u, file %u", entries[i].Partition, entries[i].File); @@ -219,6 +219,223 @@ void process_tape_files_block(aaruformatContext *ctx, const IndexEntry *entry) free(buffer); } +/** + * @brief Processes a tape partition metadata block from the image stream. + * + * Reads and parses a TapePartitionBlock from the Aaru image, validates its integrity, + * and populates the context's tape partition hash table with partition layout information. + * Each tape partition entry defines a physical division of the tape medium by specifying + * its partition number and block range (FirstBlock to LastBlock inclusive). + * + * The function performs the following operations: + * 1. Seeks to the block position indicated by the index entry + * 2. Reads and validates the TapePartitionHeader structure + * 3. Allocates and reads the array of TapePartitionEntry structures + * 4. Validates data integrity using CRC64-ECMA checksum + * 5. Inserts each partition entry into the context's UTHASH table with partition number as key + * 6. Updates image size statistics + * + * **Partition Identification:** + * Each tape partition is uniquely identified by its partition number (0-255). + * This number serves as the hash table key for fast lookup operations. Most tapes + * have a single partition (partition 0), but multi-partition formats like LTO, DLT, + * and AIT support multiple partitions with independent block address spaces. + * + * **Hash Table Management:** + * The function uses HASH_REPLACE to insert entries, which automatically: + * - Adds new entries if the partition number doesn't exist + * - Replaces existing entries if the partition number is found (freeing the old entry) + * This ensures that duplicate partition definitions are properly handled by keeping + * only the most recent definition. + * + * **Error Handling:** + * The function treats most errors as non-fatal and continues processing: + * - Invalid context or stream: Returns immediately (FATAL log) + * - Seek failures: Returns immediately (FATAL log) + * - Header read failures: Returns early (TRACE log) + * - Incorrect block identifier: Logs warning but continues + * - Memory allocation failures: Logs error and returns + * - Entry read failures: Frees buffer and returns + * - CRC64 mismatch: Logs warning, frees buffer, and returns + * - Per-entry allocation failures: Logs error and skips that entry + * + * **Block Structure:** + * The tape partition block consists of: + * ``` + * +-----------------------------+ + * | TapePartitionHeader (24 B) | <- identifier, entries, length, crc64 + * +-----------------------------+ + * | TapePartitionEntry 0 (17 B) | <- Number, FirstBlock, LastBlock + * | TapePartitionEntry 1 (17 B) | + * | ... | + * | TapePartitionEntry (n-1) | + * +-----------------------------+ + * ``` + * + * **CRC64 Validation:** + * The CRC64 checksum in the header is computed over the entire array of + * TapePartitionEntry structures (excluding the header itself). This provides + * integrity verification to detect corruption in the partition table. + * + * **Partition Block Ranges:** + * Each partition defines a block address space: + * - FirstBlock: Starting block address (often 0, but format-dependent) + * - LastBlock: Ending block address (inclusive) + * - Block count: (LastBlock - FirstBlock + 1) + * + * Block addresses are local to each partition. Different partitions may have + * overlapping logical block numbers (e.g., both partition 0 and partition 1 + * can have blocks numbered 0-1000). + * + * **Memory Management:** + * - Allocates a temporary buffer to read all partition entries + * - Allocates individual hash table entries for each partition + * - Frees the temporary buffer before returning + * - Frees replaced hash entries automatically + * - Hash table entries remain in context until cleanup + * + * @param ctx Pointer to the aaruformat context. Must not be NULL. + * The context must have a valid imageStream open for reading. + * The ctx->tapePartitions hash table will be populated with partition entries. + * The ctx->imageInfo.ImageSize will be updated with the block size. + * + * @param entry Pointer to the index entry describing the tape partition block. + * Must not be NULL. The entry->offset field indicates the file + * position where the TapePartitionHeader begins. + * + * @note This function does not return a status code. All errors are handled + * internally with appropriate logging and the function returns early + * on fatal errors. + * + * @note The tape partition hash table (ctx->tapePartitions) must be initialized to NULL + * before the first call to this function. UTHASH will manage the table + * automatically as entries are added. + * + * @note Partitions are ordered in the hash table by their partition number, not + * by insertion order. To iterate partitions in numerical order, use HASH_SORT + * with an appropriate comparison function. + * + * @note The function updates ctx->imageInfo.ImageSize by adding the size of + * all partition entries (entries × sizeof(TapePartitionEntry)). This contributes + * to the total reported image size but does not include the header size. + * + * @note The partition metadata is essential for correctly interpreting tape file + * locations, as files reference partition numbers in their definitions. + * Without partition metadata, tape file block ranges may be ambiguous. + * + * @warning The context and imageStream must be valid. Passing NULL pointers + * will result in immediate return with a FATAL log message. + * + * @warning If the CRC64 checksum validation fails, all entries in the block + * are discarded. The function does not attempt partial recovery. + * + * @warning If memory allocation fails for a hash entry, that specific partition + * entry is skipped but processing continues with remaining entries. + * + * @warning If multiple partition entries have the same Number field, only the + * last occurrence is retained. This should not occur in valid images. + * + * @see TapePartitionHeader for the block header structure + * @see TapePartitionEntry for individual partition entry structure + * @see TapePartitionHashEntry for the hash table entry structure + * @see process_tape_files_block() for tape file metadata processing + */ +void process_tape_partitions_block(aaruformatContext *ctx, const IndexEntry *entry) +{ + long pos = 0; + size_t read_bytes = 0; + TapePartitionHeader tape_partition_header = {0}; + + // Check if the context and image stream are valid + if(ctx == NULL || ctx->imageStream == NULL) + { + FATAL("Invalid context or image stream."); + return; + } + + // Seek to block + pos = fseek(ctx->imageStream, entry->offset, SEEK_SET); + if(pos < 0 || ftell(ctx->imageStream) != entry->offset) + { + FATAL("Could not seek to %" PRIu64 " as indicated by index entry...", entry->offset); + + return; + } + + // Even if those two checks shall have been done before + read_bytes = fread(&tape_partition_header, 1, sizeof(TapePartitionHeader), ctx->imageStream); + + if(read_bytes != sizeof(TapePartitionHeader)) + { + TRACE("Could not read tape partitions header, continuing..."); + return; + } + + if(tape_partition_header.identifier != TapePartitionBlock) + TRACE("Incorrect identifier for data block at position %" PRIu64 "\n", entry->offset); + + ctx->imageInfo.ImageSize += sizeof(TapePartitionEntry) * tape_partition_header.entries; + + uint8_t *buffer = malloc(sizeof(TapePartitionEntry) * tape_partition_header.entries); + if(buffer == NULL) + { + FATAL("Could not allocate memory for tape partitions block, continuing..."); + return; + } + read_bytes = fread(buffer, sizeof(TapePartitionEntry), tape_partition_header.entries, ctx->imageStream); + if(read_bytes != tape_partition_header.entries) + { + free(buffer); + FATAL("Could not read tape partitions block, continuing..."); + return; + } + // Check CRC64 + uint64_t crc64 = aaruf_crc64_data(buffer, sizeof(TapePartitionEntry) * tape_partition_header.entries); + + if(crc64 != tape_partition_header.crc64) + { + TRACE("Incorrect CRC found: 0x%" PRIx64 " found, expected 0x%" PRIx64 ", continuing...", crc64, + tape_partition_header.crc64); + free(buffer); + return; + } + + // Insert entries into UTHASH array indexed by partition + const TapePartitionEntry *entries = (TapePartitionEntry *)buffer; + + for(uint32_t i = 0; i < tape_partition_header.entries; i++) + { + // Create hash table entry + TapePartitionHashEntry *hash_entry = malloc(sizeof(TapePartitionHashEntry)); + if(hash_entry == NULL) + { + FATAL("Could not allocate memory for tape partition hash entry"); + continue; + } + + // Create key: partition + hash_entry->key = entries[i].Number; + + // Copy the tape partition entry data + hash_entry->partitionEntry = entries[i]; + + // Replace if exists, add if new + TapePartitionHashEntry *old_entry = NULL; + HASH_REPLACE(hh, ctx->tapePartitions, key, sizeof(uint8_t), hash_entry, old_entry); + + // Free old entry if it was replaced + if(old_entry != NULL) + { + TRACE("Replaced existing tape partition entry for partition %u", entries[i].Number); + free(old_entry); + } + else + TRACE("Added new tape partition entry for partition %u", entries[i].Number); + } + + free(buffer); +} + /** * @brief Retrieves the block range for a specific tape file from an Aaru tape image. * diff --git a/src/open.c b/src/open.c index 8b9cf34..58f743c 100644 --- a/src/open.c +++ b/src/open.c @@ -388,6 +388,9 @@ void *aaruf_open(const char *filepath) case TapeFileBlock: process_tape_files_block(ctx, entry); + case TapePartitionBlock: + process_tape_partitions_block(ctx, entry); + default: TRACE("Unhandled block type %4.4s with data type %d is indexed to be at %" PRIu64 "", (char *)&entry->blockType, entry->dataType, entry->offset);