diff --git a/CMakeLists.txt b/CMakeLists.txt index 82a46f9..f189c42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,7 +136,8 @@ add_library(aaruformat SHARED include/aaruformat/consts.h include/aaruformat/enu include/aaruformat/structs/lisa_tag.h src/metadata.c src/dump.c - include/aaruformat/structs/tape.h) + include/aaruformat/structs/tape.h + src/blocks/tape.c) include_directories(include include/aaruformat 3rdparty/uthash/include 3rdparty/uthash/src) diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index c6443fd..34439ba 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -123,6 +123,13 @@ typedef struct mediaTagEntry UT_hash_handle hh; ///< uthash linkage. } mediaTagEntry; +typedef struct TapeFileHashEntry +{ + uint64_t key; ///< Composite key: partition << 32 | file + TapeFileEntry fileEntry; ///< The actual tape file data + UT_hash_handle hh; ///< UTHASH handle +} tapeFileHashEntry; + /** \struct aaruformatContext * \brief Master context representing an open or in‑creation Aaru image. * @@ -248,6 +255,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 } aaruformatContext; /** \struct DumpHardwareEntriesWithData diff --git a/include/internal.h b/include/internal.h index ecdf4ac..432dbd4 100644 --- a/include/internal.h +++ b/include/internal.h @@ -37,6 +37,7 @@ void process_cicm_block(aaruformatContext *ctx, const IndexEntry *entry); void process_aaru_metadata_json_block(aaruformatContext *ctx, const IndexEntry *entry); 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 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 new file mode 100644 index 0000000..4422500 --- /dev/null +++ b/src/blocks/tape.c @@ -0,0 +1,221 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2025 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 . + * */ + +#include "aaruformat.h" +#include "log.h" + +/** + * @brief Processes a tape file metadata block from the image stream. + * + * Reads and parses a TapeFileBlock from the Aaru image, validates its integrity, + * and populates the context's tape file hash table with file layout information. + * Each tape file entry defines a logical file on the tape medium by specifying + * its partition, file 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 TapeFileHeader structure + * 3. Allocates and reads the array of TapeFileEntry structures + * 4. Validates data integrity using CRC64-ECMA checksum + * 5. Inserts each file entry into the context's UTHASH table with a composite key + * 6. Updates image size statistics + * + * **Composite Key Construction:** + * Each tape file is uniquely identified by a 64-bit composite key: + * key = (partition << 32) | file_number + * This allows files with the same file number in different partitions to coexist + * in the hash table without conflicts. + * + * **Hash Table Management:** + * The function uses HASH_REPLACE to insert entries, which automatically: + * - Adds new entries if the key doesn't exist + * - Replaces existing entries if the key is found (freeing the old entry) + * This ensures that duplicate entries (same partition/file combination) 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 file block consists of: + * ``` + * +-------------------------+ + * | TapeFileHeader (24 B) | <- identifier, entries, length, crc64 + * +-------------------------+ + * | TapeFileEntry 0 (21 B) | <- File, Partition, FirstBlock, LastBlock + * | TapeFileEntry 1 (21 B) | + * | ... | + * | TapeFileEntry (n-1) | + * +-------------------------+ + * ``` + * + * **CRC64 Validation:** + * The CRC64 checksum in the header is computed over the entire array of + * TapeFileEntry structures (excluding the header itself). This provides + * integrity verification to detect corruption in the file table. + * + * **Memory Management:** + * - Allocates a temporary buffer to read all file entries + * - Allocates individual hash table entries for each file + * - 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->tapeFiles hash table will be populated with file entries. + * The ctx->imageInfo.ImageSize will be updated with the block size. + * + * @param entry Pointer to the index entry describing the tape file block. + * Must not be NULL. The entry->offset field indicates the file + * position where the TapeFileHeader 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 file hash table (ctx->tapeFiles) must be initialized to NULL + * before the first call to this function. UTHASH will manage the table + * automatically as entries are added. + * + * @note Files are ordered in the hash table by their composite key value, not + * by insertion order. To iterate files in partition/file number order, + * use HASH_SORT with an appropriate comparison function. + * + * @note The function updates ctx->imageInfo.ImageSize by adding the size of + * all file entries (entries × sizeof(TapeFileEntry)). This contributes + * to the total reported image size but does not include the header size. + * + * @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 file + * entry is skipped but processing continues with remaining entries. + * + * @see TapeFileHeader for the block header structure + * @see TapeFileEntry for individual file entry structure + * @see tapeFileHashEntry for the hash table entry structure + * @see process_tape_partition_block() for partition metadata processing + */ +void process_tape_files_block(aaruformatContext *ctx, const IndexEntry *entry) +{ + long pos = 0; + size_t read_bytes = 0; + TapeFileHeader tape_file_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...\n", entry->offset); + + return; + } + + // Even if those two checks shall have been done before + read_bytes = fread(&tape_file_header, 1, sizeof(TapeFileHeader), ctx->imageStream); + + if(read_bytes != sizeof(TapeFileHeader)) + { + TRACE("Could not read tape files header, continuing...\n"); + return; + } + + if(tape_file_header.identifier != TapeFileBlock) + TRACE("Incorrect identifier for data block at position %" PRIu64 "\n", entry->offset); + + ctx->imageInfo.ImageSize += sizeof(TapeFileEntry) * tape_file_header.entries; + + uint8_t *buffer = malloc(sizeof(TapeFileEntry) * tape_file_header.entries); + if(buffer == NULL) + { + FATAL("Could not allocate memory for tape files block, continuing...\n"); + return; + } + read_bytes = fread(buffer, sizeof(TapeFileEntry), tape_file_header.entries, ctx->imageStream); + if(read_bytes != tape_file_header.entries) + { + free(buffer); + FATAL("Could not read tape files block, continuing...\n"); + return; + } + // Check CRC64 + uint64_t crc64 = aaruf_crc64_data(buffer, sizeof(TapeFileEntry) * tape_file_header.entries); + + if(crc64 != tape_file_header.crc64) + { + TRACE("Incorrect CRC found: 0x%" PRIx64 " found, expected 0x%" PRIx64 ", continuing...\n", crc64, + tape_file_header.crc64); + free(buffer); + return; + } + + // Insert entries into UTHASH array indexed by partition << 32 | file number + const TapeFileEntry *entries = (TapeFileEntry *)buffer; + + for(uint32_t i = 0; i < tape_file_header.entries; i++) + { + // Create hash table entry + tapeFileHashEntry *hash_entry = malloc(sizeof(tapeFileHashEntry)); + if(hash_entry == NULL) + { + FATAL("Could not allocate memory for tape file hash entry\n"); + continue; + } + + // Create composite key: partition << 32 | file number + hash_entry->key = (uint64_t)entries[i].Partition << 32 | entries[i].File; + + // Copy the tape file entry data + hash_entry->fileEntry = entries[i]; + + // Replace if exists, add if new + tapeFileHashEntry *old_entry = NULL; + HASH_REPLACE(hh, ctx->tapeFiles, key, sizeof(uint64_t), hash_entry, old_entry); + + // Free old entry if it was replaced + if(old_entry != NULL) + { + TRACE("Replaced existing tape file entry for partition %u, file %u\n", entries[i].Partition, + entries[i].File); + free(old_entry); + } + else + TRACE("Added new tape file entry for partition %u, file %u\n", entries[i].Partition, entries[i].File); + } + + free(buffer); +} \ No newline at end of file diff --git a/src/open.c b/src/open.c index 2c2027d..8b9cf34 100644 --- a/src/open.c +++ b/src/open.c @@ -385,6 +385,9 @@ void *aaruf_open(const char *filepath) process_checksum_block(ctx, entry); break; + case TapeFileBlock: + process_tape_files_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);