Files
libaaruformat/src/blocks/tape.c

1478 lines
67 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 "aaruformat.h"
#include "internal.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(aaruformat_context *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 = aaruf_fseek(ctx->imageStream, (aaru_off_t)entry->offset, SEEK_SET);
if(pos < 0 || aaruf_ftell(ctx->imageStream) != (aaru_off_t)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_file_header, 1, sizeof(TapeFileHeader), ctx->imageStream);
if(read_bytes != sizeof(TapeFileHeader))
{
TRACE("Could not read tape files header, continuing...");
return;
}
if(tape_file_header.identifier != TapeFileBlock)
{
TRACE("Incorrect identifier for data block at position %" PRIu64 "\n", entry->offset);
return;
}
ctx->image_info.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...");
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...");
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...", 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");
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->tape_files, key, sizeof(uint64_t), hash_entry, old_entry);
ctx->dirty_tape_file_block = true; // Mark tape file block as dirty
// 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);
}
else
TRACE("Added new tape file entry for partition %u, file %u", entries[i].Partition, entries[i].File);
}
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(aaruformat_context *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 = aaruf_fseek(ctx->imageStream, (aaru_off_t)entry->offset, SEEK_SET);
if(pos < 0 || aaruf_ftell(ctx->imageStream) != (aaru_off_t)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);
return;
}
ctx->image_info.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->tape_partitions, key, sizeof(uint8_t), hash_entry, old_entry);
ctx->dirty_tape_partition_block = true; // Mark tape partition block as dirty
// 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.
*
* Queries the tape file hash table to locate a file by its partition and file number,
* returning the first and last block addresses that define the file's extent on the
* tape medium. This function provides the core lookup mechanism for accessing logical
* files stored in tape images.
*
* **Tape File Identification:**
* Each tape file is uniquely identified by a combination of:
* - **Partition number** (8-bit): The tape partition containing the file
* - **File number** (32-bit): The sequential file number within that partition
*
* These two values are combined into a 64-bit composite key:
* key = (partition << 32) | file_number
*
* This composite key is used to perform a hash table lookup in the context's
* tapeFiles table, which was previously populated by process_tape_files_block()
* during image initialization.
*
* **Block Range Semantics:**
* The returned block range [FirstBlock, LastBlock] is inclusive on both ends:
* - FirstBlock: The first block address where the file begins
* - LastBlock: The final block address where the file ends (inclusive)
* - Block count: (LastBlock - FirstBlock + 1)
*
* Block addresses are absolute positions within the tape image's logical
* block space, not relative to the partition or file.
*
* **Typical Usage Flow:**
* 1. Open an Aaru tape image with aaruf_open()
* 2. Call aaruf_get_tape_file() to get the block range for a specific file
* 3. Use the returned block range to read the file's data blocks
* 4. Repeat for other files as needed
*
* **Error Handling:**
* The function performs validation in the following order:
* 1. Context pointer validation (NULL check)
* 2. Magic number verification (ensures valid aaruformat context)
* 3. Hash table lookup for the specified partition/file combination
*
* If any validation fails, an appropriate error code is returned and the
* output parameters (starting_block, ending_block) are left unmodified.
*
* **Thread Safety:**
* This function performs read-only operations on the context and is safe
* to call from multiple threads concurrently, provided the context is not
* being modified by other operations (e.g., during image opening/closing).
*
* **Performance Characteristics:**
* - Hash table lookup: O(1) average case
* - No I/O operations performed
* - Minimal stack usage
* - Suitable for high-frequency queries
*
* @param context Pointer to an initialized aaruformat context. Must not be NULL.
* The context must have been successfully opened with aaruf_open()
* and contain a valid tape file hash table. The context is treated
* as const and is not modified by this operation.
*
* @param partition The partition number (0-255) containing the requested file.
* For single-partition tapes, this is typically 0. Multi-partition
* tapes may have files in different partitions with potentially
* overlapping file numbers.
*
* @param file The file number within the specified partition. File numbers are
* typically sequential starting from 0 or 1, but gaps may exist if
* files were deleted or the tape was written non-sequentially.
*
* @param[out] starting_block Pointer to receive the first block address of the file.
* Must not be NULL. Only modified on success.
* The value written represents the inclusive start of the
* file's block range.
*
* @param[out] ending_block Pointer to receive the last block address of the file.
* Must not be NULL. Only modified on success.
* The value written represents the inclusive end of the
* file's block range.
*
* @retval AARUF_STATUS_OK (0) Successfully retrieved tape file information. Both
* output parameters have been populated with valid block addresses. The
* requested partition/file combination exists in the image's file table.
*
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) Invalid context or context validation failed.
* This is returned when:
* - The context pointer is NULL
* - The context magic number doesn't match AARU_MAGIC (corrupted or wrong type)
* The output parameters are not modified.
*
* @retval AARUF_ERROR_TAPE_FILE_NOT_FOUND (-28) The requested partition/file combination
* does not exist in the image's tape file table. This is returned when:
* - The specified partition number has no files
* - The specified file number doesn't exist in the given partition
* - The tape file block was not present or failed to load during image open
* The output parameters are not modified.
*
* @note The function logs entry and exit points via TRACE macros when tracing is
* enabled, including parameter values and return codes for debugging.
*
* @note The tape file hash table (ctx->tapeFiles) must have been populated during
* image initialization. If the image doesn't contain a TapeFileBlock, or if
* that block failed to load, all queries will return AARUF_ERROR_TAPE_FILE_NOT_FOUND.
*
* @note For images without tape file metadata, applications should fall back to
* direct block-based access or partition-level operations.
*
* @note The returned block addresses are logical block numbers. To read actual data,
* these must be translated through the appropriate read functions that handle
* the image's block encoding, compression, and DDT mapping.
*
* @warning The output parameter pointers must be valid. Passing NULL for either
* starting_block or ending_block will cause undefined behavior (likely
* a crash when the function attempts to dereference them on success).
*
* @warning If the same partition/file combination appears multiple times in the
* TapeFileBlock, only the last occurrence is retained (due to HASH_REPLACE
* behavior in process_tape_files_block). This should not occur in valid
* images but may happen with corrupted or malformed tape file metadata.
*
* @see process_tape_files_block() for tape file table initialization
* @see TapeFileEntry for the structure defining file block ranges
* @see tapeFileHashEntry for the hash table entry structure
* @see aaruf_get_tape_partition() for partition-level queries (if available)
*/
AARU_EXPORT int32_t AARU_CALL aaruf_get_tape_file(const void *context, const uint8_t partition, const uint32_t file,
uint64_t *starting_block, uint64_t *ending_block)
{
TRACE("Entering aaruf_get_tape_file(%p, %d, %d, %llu, %llu)", context, partition, file, *starting_block,
*ending_block);
const aaruformat_context *ctx = NULL;
if(context == NULL)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_tape_file() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_tape_file() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
uint64_t key = (uint64_t)partition << 32 | file;
tapeFileHashEntry *entry = NULL;
HASH_FIND(hh, ctx->tape_files, &key, sizeof(uint64_t), entry);
if(entry == NULL)
{
TRACE("Tape file not found");
return AARUF_ERROR_TAPE_FILE_NOT_FOUND;
}
*starting_block = entry->fileEntry.FirstBlock;
*ending_block = entry->fileEntry.LastBlock;
TRACE("Exiting aaruf_get_tape_file(%p, %d, %d, %llu, %llu) = AARUF_STATUS_OK", context, partition, file,
*starting_block, *ending_block);
return AARUF_STATUS_OK;
}
/**
* @brief Sets or updates the block range for a specific tape file in an Aaru tape image.
*
* Creates or modifies a tape file entry in the context's hash table, defining the logical
* file's extent on the tape medium by its partition number, file number, and block range.
* This function is the write-mode counterpart to aaruf_get_tape_file() and is used when
* creating or modifying tape images to establish the file structure metadata.
*
* **Tape File Registration:**
* When writing a tape image, this function should be called for each logical file to
* register its location. Each file is uniquely identified by:
* - **Partition number** (8-bit): The tape partition containing the file
* - **File number** (32-bit): The sequential file number within that partition
*
* These values are combined into a 64-bit composite key:
* key = (partition << 32) | file_number
*
* The file entry (including the block range) is then stored in the context's tapeFiles
* hash table and will be written to the image's TapeFileBlock during finalization.
*
* **Block Range Definition:**
* The block range [starting_block, ending_block] defines the file's extent:
* - starting_block: The first block address where the file begins (inclusive)
* - ending_block: The final block address where the file ends (inclusive)
* - Block count: (ending_block - starting_block + 1)
*
* Block addresses must be absolute positions within the tape image's logical
* block space. The caller is responsible for ensuring:
* - starting_block <= ending_block (no validation is performed)
* - Block ranges don't conflict with other files (no validation is performed)
* - All blocks in the range have been or will be written to the image
*
* **Typical Usage Flow:**
* 1. Open or create an Aaru tape image with write access
* 2. Write the file's data blocks to the image
* 3. Call aaruf_set_tape_file() to register the file's block range
* 4. Repeat for all files on the tape
* 5. Close the image (TapeFileBlock will be written during finalization)
*
* **Update/Replace Behavior:**
* If a file entry with the same partition/file combination already exists:
* - The old entry is automatically freed
* - The new entry replaces it in the hash table
* - A TRACE message indicates the replacement
*
* This allows updating file metadata or correcting errors without manual deletion.
*
* **Error Handling:**
* The function performs validation in the following order:
* 1. Context pointer validation (NULL check)
* 2. Magic number verification (ensures valid aaruformat context)
* 3. Write mode verification (ensures image is opened for writing)
* 4. Memory allocation for the hash entry
*
* If any validation fails, an appropriate error code is returned and no
* modifications are made to the context's tape file table.
*
* **Thread Safety:**
* This function modifies the shared context's hash table and is NOT thread-safe.
* Concurrent calls to aaruf_set_tape_file() or other functions that modify
* ctx->tapeFiles may result in data corruption or memory leaks. The caller must
* ensure exclusive access through external synchronization if needed.
*
* **Memory Management:**
* - Allocates a new hash table entry (tapeFileHashEntry) for each call
* - HASH_REPLACE automatically frees replaced entries
* - All hash entries are freed when the context is closed
* - On allocation failure, no entry is added and an error is returned
*
* **Performance Characteristics:**
* - Hash table insertion/replacement: O(1) average case
* - No I/O operations performed (metadata written during image close)
* - Minimal stack usage
* - Suitable for bulk file registration operations
*
* @param context Pointer to an initialized aaruformat context. Must not be NULL.
* The context must have been opened with write access (isWriting=true).
* The context's tapeFiles hash table will be updated with the new entry.
*
* @param partition The partition number (0-255) where the file is located.
* For single-partition tapes, this is typically 0. Multi-partition
* tapes can have files in different partitions with potentially
* overlapping file numbers.
*
* @param file The file number within the specified partition. File numbers are
* typically sequential (0, 1, 2, ...) but gaps are allowed. The same
* file number can exist in different partitions without conflict.
*
* @param starting_block The first block address of the file (inclusive). This should
* be the absolute block number in the image where the file's
* first byte begins. Must be <= ending_block (not validated).
*
* @param ending_block The last block address of the file (inclusive). This should
* be the absolute block number in the image where the file's
* last byte ends. Must be >= starting_block (not validated).
*
* @retval AARUF_STATUS_OK (0) Successfully set tape file information. The hash table
* has been updated with the file entry. If an entry with the same partition/file
* combination existed, it has been replaced. The metadata will be written to
* the TapeFileBlock when the image is closed.
*
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) Invalid context or context validation failed.
* This is returned when:
* - The context pointer is NULL
* - The context magic number doesn't match AARU_MAGIC (corrupted or wrong type)
* No modifications are made to the context.
*
* @retval AARUF_READ_ONLY (-22) The context is not opened for writing. This is returned
* when ctx->isWriting is false, indicating the image was opened in read-only mode.
* Tape file metadata cannot be modified in read-only mode. No modifications are
* made to the context.
*
* @retval AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) Memory allocation failed. The system could
* not allocate memory for the hash table entry (tapeFileHashEntry). This is a
* critical error indicating low memory conditions. No modifications are made to
* the context.
*
* @note The function logs entry and exit points via TRACE macros when tracing is
* enabled, including parameter values and return codes for debugging.
*
* @note The tape file metadata is not immediately written to disk. It remains in the
* context's hash table until the image is closed, at which point all entries
* are serialized and written to a TapeFileBlock.
*
* @note No validation is performed on the block range values. The caller is responsible
* for ensuring that starting_block <= ending_block and that the range is valid
* for the image being created.
*
* @note No validation is performed to detect overlapping file ranges. Multiple files
* can reference the same or overlapping block ranges, which may be intentional
* (e.g., for multi-track or multi-partition scenarios).
*
* @note If the same partition/file combination is set multiple times, only the last
* values are retained. This can be used to update file metadata but also means
* accidental duplicate calls will silently overwrite previous values.
*
* @warning This function is NOT thread-safe. Concurrent modifications to the tape file
* table may result in undefined behavior, memory corruption, or memory leaks.
*
* @warning The caller must ensure the image is opened with write access before calling
* this function. Attempting to modify read-only images will fail with
* AARUF_READ_ONLY.
*
* @warning Parameter validation is minimal. Invalid block ranges (starting_block >
* ending_block) are accepted and will be written to the image, potentially
* causing problems when reading the image later.
*
* @warning If memory allocation fails (AARUF_ERROR_NOT_ENOUGH_MEMORY), the file entry
* is not added. The caller should handle this error appropriately, potentially
* by freeing memory and retrying or aborting the write operation.
*
* @see aaruf_get_tape_file() for retrieving tape file information from images
* @see process_tape_files_block() for tape file table initialization during read
* @see TapeFileEntry for the structure defining file block ranges
* @see tapeFileHashEntry for the hash table entry structure
*/
AARU_EXPORT int32_t AARU_CALL aaruf_set_tape_file(void *context, const uint8_t partition, const uint32_t file,
const uint64_t starting_block, const uint64_t ending_block)
{
TRACE("Entering aaruf_set_tape_file(%p, %d, %d, %llu, %llu)", context, partition, file, starting_block,
ending_block);
aaruformat_context *ctx = NULL;
if(context == NULL)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_set_tape_file() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_set_tape_file() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
// Check we are writing
if(!ctx->is_writing)
{
FATAL("Trying to write a read-only image");
TRACE("Exiting aaruf_set_tape_file() = AARUF_READ_ONLY");
return AARUF_READ_ONLY;
}
// Create hash table entry
tapeFileHashEntry *hash_entry = malloc(sizeof(tapeFileHashEntry));
if(hash_entry == NULL)
{
FATAL("Could not allocate memory for tape file hash entry");
TRACE("Exiting aaruf_set_tape_file() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
// Create composite key: partition << 32 | file number
hash_entry->key = (uint64_t)partition << 32 | file;
// Copy the tape file entry data
hash_entry->fileEntry.File = file;
hash_entry->fileEntry.Partition = partition;
hash_entry->fileEntry.FirstBlock = starting_block;
hash_entry->fileEntry.LastBlock = ending_block;
// Replace if exists, add if new
tapeFileHashEntry *old_entry = NULL;
HASH_REPLACE(hh, ctx->tape_files, key, sizeof(uint64_t), hash_entry, old_entry);
ctx->dirty_tape_file_block = true; // Mark tape file block as dirty
// Free old entry if it was replaced
if(old_entry != NULL)
{
TRACE("Replaced existing tape file entry for partition %u, file %u", partition, file);
free(old_entry);
}
else
TRACE("Added new tape file entry for partition %u, file %u", partition, file);
TRACE("Exiting aaruf_set_tape_file(%p, %d, %d, %llu, %llu) = AARUF_STATUS_OK", context, partition, file,
starting_block, ending_block);
return AARUF_STATUS_OK;
}
/**
* @brief Retrieves the block range for a specific tape partition from an Aaru tape image.
*
* Queries the tape partition hash table to locate a partition by its partition number,
* returning the first and last block addresses that define the partition's extent on the
* tape medium. This function provides the core lookup mechanism for accessing partition
* layout information in tape images.
*
* **Tape Partition Identification:**
* Each tape partition is uniquely identified by its partition number (0-255). 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.
*
* The partition number is used as the hash table key to perform a lookup in the context's
* tapePartitions table, which was previously populated by process_tape_partitions_block()
* during image initialization.
*
* **Block Range Semantics:**
* The returned block range [FirstBlock, LastBlock] is inclusive on both ends:
* - FirstBlock: The first block address in the partition (often 0, but format-dependent)
* - LastBlock: The final block address in the partition (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 0-1000).
* When accessing blocks, both the partition number and block number are required for
* unique identification.
*
* **Typical Usage Flow:**
* 1. Open an Aaru tape image with aaruf_open()
* 2. Call aaruf_get_tape_partition() to get the block range for a specific partition
* 3. Use the returned block range to understand partition boundaries
* 4. Access files within the partition using aaruf_get_tape_file()
* 5. Repeat for other partitions as needed
*
* **Error Handling:**
* The function performs validation in the following order:
* 1. Context pointer validation (NULL check)
* 2. Magic number verification (ensures valid aaruformat context)
* 3. Hash table lookup for the specified partition number
*
* If any validation fails, an appropriate error code is returned and the
* output parameters (starting_block, ending_block) are left unmodified.
*
* **Thread Safety:**
* This function performs read-only operations on the context and is safe
* to call from multiple threads concurrently, provided the context is not
* being modified by other operations (e.g., during image opening/closing).
*
* **Performance Characteristics:**
* - Hash table lookup: O(1) average case
* - No I/O operations performed
* - Minimal stack usage
* - Suitable for high-frequency queries
*
* **Partition Layout Information:**
* The partition metadata is essential for:
* - Understanding the physical organization of the tape
* - Determining partition boundaries for file access
* - Validating that file block ranges fall within partition limits
* - Supporting multi-partition tape formats correctly
* - Preserving original tape partitioning schemes
*
* @param context Pointer to an initialized aaruformat context. Must not be NULL.
* The context must have been successfully opened with aaruf_open()
* and contain a valid tape partition hash table. The context is treated
* as const and is not modified by this operation.
*
* @param partition The partition number (0-255) to query. For single-partition tapes,
* this is typically 0. Multi-partition tapes may have multiple
* partitions numbered sequentially from 0.
*
* @param[out] starting_block Pointer to receive the first block address of the partition.
* Must not be NULL. Only modified on success.
* The value written represents the inclusive start of the
* partition's block range (often 0, but format-dependent).
*
* @param[out] ending_block Pointer to receive the last block address of the partition.
* Must not be NULL. Only modified on success.
* The value written represents the inclusive end of the
* partition's block range.
*
* @retval AARUF_STATUS_OK (0) Successfully retrieved tape partition information. Both
* output parameters have been populated with valid block addresses. The
* requested partition exists in the image's partition table.
*
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) Invalid context or context validation failed.
* This is returned when:
* - The context pointer is NULL
* - The context magic number doesn't match AARU_MAGIC (corrupted or wrong type)
* The output parameters are not modified.
*
* @retval AARUF_ERROR_TAPE_PARTITION_NOT_FOUND (-29) The requested partition number
* does not exist in the image's tape partition table. This is returned when:
* - The specified partition number was not defined in the TapePartitionBlock
* - The tape partition block was not present or failed to load during image open
* - The partition number is out of range for this tape
* The output parameters are not modified.
*
* @note The function logs entry and exit points via TRACE macros when tracing is
* enabled, including parameter values and return codes for debugging.
*
* @note The tape partition hash table (ctx->tapePartitions) must have been populated
* during image initialization. If the image doesn't contain a TapePartitionBlock,
* or if that block failed to load, all queries will return
* AARUF_ERROR_TAPE_PARTITION_NOT_FOUND.
*
* @note For images without tape partition metadata, the entire tape may be treated
* as a single implicit partition, and applications should handle the absence
* of partition information gracefully.
*
* @note The returned block addresses are logical block numbers within the partition's
* address space. To read actual data, these must be combined with the partition
* number and translated through the appropriate read functions.
*
* @note Partition metadata is primarily informational and used for validation. File
* access is primarily driven by file metadata (TapeFileBlock), which references
* partition numbers to establish context.
*
* @warning The output parameter pointers must be valid. Passing NULL for either
* starting_block or ending_block will cause undefined behavior (likely
* a crash when the function attempts to dereference them on success).
*
* @warning If the same partition number appears multiple times in the TapePartitionBlock,
* only the last occurrence is retained (due to HASH_REPLACE behavior in
* process_tape_partitions_block). This should not occur in valid images but
* may happen with corrupted or malformed partition metadata.
*
* @warning Single-partition tapes may not include a TapePartitionBlock at all, in which
* case this function will always return AARUF_ERROR_TAPE_PARTITION_NOT_FOUND.
* Applications should handle this case and assume a default partition 0
* spanning the entire tape.
*
* @see process_tape_partitions_block() for partition table initialization
* @see TapePartitionEntry for the structure defining partition block ranges
* @see TapePartitionHashEntry for the hash table entry structure
* @see aaruf_get_tape_file() for file-level queries within partitions
* @see aaruf_set_tape_partition() for setting partition information during write
*/
AARU_EXPORT int32_t AARU_CALL aaruf_get_tape_partition(const void *context, const uint8_t partition,
uint64_t *starting_block, uint64_t *ending_block)
{
TRACE("Entering aaruf_get_tape_partition(%p, %d, %llu, %llu)", context, partition, *starting_block, *ending_block);
const aaruformat_context *ctx = NULL;
if(context == NULL)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_tape_partition() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_tape_partition() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
uint8_t key = partition;
TapePartitionHashEntry *entry = NULL;
HASH_FIND(hh, ctx->tape_partitions, &key, sizeof(uint8_t), entry);
if(entry == NULL)
{
TRACE("Tape partition not found");
return AARUF_ERROR_TAPE_PARTITION_NOT_FOUND;
}
*starting_block = entry->partitionEntry.FirstBlock;
*ending_block = entry->partitionEntry.LastBlock;
TRACE("Exiting aaruf_get_tape_partition(%p, %d, %llu, %llu) = AARUF_STATUS_OK", context, partition, *starting_block,
*ending_block);
return AARUF_STATUS_OK;
}
/**
* @brief Sets or updates the block range for a specific tape partition in an Aaru tape image.
*
* Creates or modifies a tape partition entry in the context's hash table, defining the
* physical partition's extent on the tape medium by its partition number and block range.
* This function is the write-mode counterpart to aaruf_get_tape_partition() and is used
* when creating or modifying tape images to establish the partition structure metadata.
*
* **Tape Partition Registration:**
* When writing a tape image, this function should be called for each physical partition
* to register its block range. Each partition is uniquely identified by its partition
* number (0-255), and most tapes have a single partition (partition 0), though formats
* like LTO, DLT, and AIT support multiple partitions.
*
* The partition entry (including the block range) is stored in the context's tapePartitions
* hash table and will be written to the image's TapePartitionBlock during finalization.
*
* **Block Range Definition:**
* The block range [starting_block, ending_block] defines the partition's extent:
* - starting_block: The first block address in the partition (often 0, but format-dependent)
* - ending_block: The final block address in the partition (inclusive)
* - Block count: (ending_block - starting_block + 1)
*
* Block addresses are local to each partition. The caller is responsible for ensuring:
* - starting_block <= ending_block (no validation is performed)
* - Partition ranges don't overlap (no validation is performed)
* - All blocks in the range have been or will be written to the image
* - Files referencing this partition have block addresses within this range
*
* **Typical Usage Flow:**
* 1. Open or create an Aaru tape image with write access
* 2. Define partition layout by calling aaruf_set_tape_partition() for each partition
* 3. Write data blocks to the image within the defined partition ranges
* 4. Register files within partitions using aaruf_set_tape_file()
* 5. Close the image (TapePartitionBlock will be written during finalization)
*
* **Update/Replace Behavior:**
* If a partition entry with the same partition number already exists:
* - The old entry is automatically freed
* - The new entry replaces it in the hash table
* - A TRACE message indicates the replacement
*
* This allows updating partition metadata or correcting errors without manual deletion.
*
* **Error Handling:**
* The function performs validation in the following order:
* 1. Context pointer validation (NULL check)
* 2. Magic number verification (ensures valid aaruformat context)
* 3. Write mode verification (ensures image is opened for writing)
* 4. Memory allocation for the hash entry
*
* If any validation fails, an appropriate error code is returned and no
* modifications are made to the context's tape partition table.
*
* **Thread Safety:**
* This function modifies the shared context's hash table and is NOT thread-safe.
* Concurrent calls to aaruf_set_tape_partition() or other functions that modify
* ctx->tapePartitions may result in data corruption or memory leaks. The caller must
* ensure exclusive access through external synchronization if needed.
*
* **Memory Management:**
* - Allocates a new hash table entry (TapePartitionHashEntry) for each call
* - HASH_REPLACE automatically frees replaced entries
* - All hash entries are freed when the context is closed
* - On allocation failure, no entry is added and an error is returned
*
* **Performance Characteristics:**
* - Hash table insertion/replacement: O(1) average case
* - No I/O operations performed (metadata written during image close)
* - Minimal stack usage
* - Suitable for bulk partition registration operations
*
* **Partition Organization:**
* Proper partition metadata is essential for:
* - Documenting the physical layout of multi-partition tapes
* - Validating file block ranges against partition boundaries
* - Preserving the original tape's partitioning scheme for archival purposes
* - Supporting tape formats that require specific partition structures
* - Enabling applications to understand and navigate complex tape layouts
*
* @param context Pointer to an initialized aaruformat context. Must not be NULL.
* The context must have been opened with write access (isWriting=true).
* The context's tapePartitions hash table will be updated with the new entry.
*
* @param partition The partition number (0-255) to set. For single-partition tapes,
* this is typically 0. Multi-partition tapes use sequential numbers
* (0, 1, 2, ...) though the numbering scheme is format-specific.
*
* @param starting_block The first block address of the partition (inclusive). This
* defines where the partition begins in the tape's block address
* space. Often 0 for the first partition, but format-dependent.
* Must be <= ending_block (not validated).
*
* @param ending_block The last block address of the partition (inclusive). This defines
* where the partition ends in the tape's block address space.
* Must be >= starting_block (not validated).
*
* @retval AARUF_STATUS_OK (0) Successfully set tape partition information. The hash table
* has been updated with the partition entry. If an entry with the same partition
* number existed, it has been replaced. The metadata will be written to the
* TapePartitionBlock when the image is closed.
*
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) Invalid context or context validation failed.
* This is returned when:
* - The context pointer is NULL
* - The context magic number doesn't match AARU_MAGIC (corrupted or wrong type)
* No modifications are made to the context.
*
* @retval AARUF_READ_ONLY (-22) The context is not opened for writing. This is returned
* when ctx->isWriting is false, indicating the image was opened in read-only mode.
* Tape partition metadata cannot be modified in read-only mode. No modifications
* are made to the context.
*
* @retval AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) Memory allocation failed. The system could
* not allocate memory for the hash table entry (TapePartitionHashEntry). This is
* a critical error indicating low memory conditions. No modifications are made to
* the context.
*
* @note The function logs entry and exit points via TRACE macros when tracing is
* enabled, including parameter values and return codes for debugging.
*
* @note The tape partition metadata is not immediately written to disk. It remains in the
* context's hash table until the image is closed, at which point all entries
* are serialized and written to a TapePartitionBlock.
*
* @note No validation is performed on the block range values. The caller is responsible
* for ensuring that starting_block <= ending_block and that the range is valid
* for the image being created.
*
* @note No validation is performed to detect overlapping partition ranges. Partitions
* with overlapping block addresses may be accepted but will likely cause problems
* when reading the image or accessing files.
*
* @note If the same partition number is set multiple times, only the last values are
* retained. This can be used to update partition metadata but also means
* accidental duplicate calls will silently overwrite previous values.
*
* @note For single-partition tapes, it may be acceptable to omit the TapePartitionBlock
* entirely. Applications reading such images should assume a default partition 0
* spanning the entire tape if no partition metadata is present.
*
* @note Block addresses are local to each partition. Files within a partition reference
* blocks relative to that partition's address space, not the global tape address
* space (though in practice, many formats use absolute addressing).
*
* @warning This function is NOT thread-safe. Concurrent modifications to the tape partition
* table may result in undefined behavior, memory corruption, or memory leaks.
*
* @warning The caller must ensure the image is opened with write access before calling
* this function. Attempting to modify read-only images will fail with
* AARUF_READ_ONLY.
*
* @warning Parameter validation is minimal. Invalid block ranges (starting_block >
* ending_block) are accepted and will be written to the image, potentially
* causing problems when reading the image later.
*
* @warning If memory allocation fails (AARUF_ERROR_NOT_ENOUGH_MEMORY), the partition entry
* is not added. The caller should handle this error appropriately, potentially
* by freeing memory and retrying or aborting the write operation.
*
* @warning Partition metadata should be consistent with file metadata. Files should only
* reference partitions that have been defined, and their block ranges should
* fall within the partition boundaries. No automatic validation is performed.
*
* @see aaruf_get_tape_partition() for retrieving tape partition information from images
* @see process_tape_partitions_block() for partition table initialization during read
* @see TapePartitionEntry for the structure defining partition block ranges
* @see TapePartitionHashEntry for the hash table entry structure
* @see aaruf_set_tape_file() for setting file metadata within partitions
*/
AARU_EXPORT int32_t AARU_CALL aaruf_set_tape_partition(void *context, const uint8_t partition,
const uint64_t starting_block, const uint64_t ending_block)
{
TRACE("Entering aaruf_set_tape_partition(%p, %d, %llu, %llu)", context, partition, starting_block, ending_block);
aaruformat_context *ctx = NULL;
if(context == NULL)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_set_tape_partition() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_set_tape_partition() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
// Check we are writing
if(!ctx->is_writing)
{
FATAL("Trying to write a read-only image");
TRACE("Exiting aaruf_set_tape_partition() = AARUF_READ_ONLY");
return AARUF_READ_ONLY;
}
// Create hash table entry
TapePartitionHashEntry *hash_entry = malloc(sizeof(TapePartitionHashEntry));
if(hash_entry == NULL)
{
FATAL("Could not allocate memory for tape partition hash entry");
TRACE("Exiting aaruf_set_tape_partition() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
// Create key: partition
hash_entry->key = partition;
// Copy the tape partition entry data
hash_entry->partitionEntry.Number = partition;
hash_entry->partitionEntry.FirstBlock = starting_block;
hash_entry->partitionEntry.LastBlock = ending_block;
// Replace if exists, add if new
TapePartitionHashEntry *old_entry = NULL;
HASH_REPLACE(hh, ctx->tape_partitions, key, sizeof(uint8_t), hash_entry, old_entry);
ctx->dirty_tape_partition_block = true; // Mark tape partition block as dirty
// Free old entry if it was replaced
if(old_entry != NULL)
{
TRACE("Replaced existing tape partition entry for partition %u", partition);
free(old_entry);
}
else
TRACE("Added new tape partition entry for partition %u", partition);
TRACE("Exiting aaruf_set_tape_partition(%p, %d, %llu, %llu) = AARUF_STATUS_OK", context, partition, starting_block,
ending_block);
return AARUF_STATUS_OK;
}
/**
* @brief Retrieves all tape file entries from the image.
*
* Extracts the complete set of tape file metadata entries from an Aaru tape image, returning
* an array of TapeFileEntry structures. Tape files represent logical file divisions on sequential
* access media (magnetic tapes) where data is organized into numbered files within numbered partitions.
* Each file entry defines a contiguous range of tape blocks (FirstBlock to LastBlock inclusive) that
* comprise one logical file unit on the tape medium.
*
* Tape file organization is hierarchical:
* - **Partition**: Physical or logical division of the tape (0-255)
* - **File**: Numbered file within a partition (0-4,294,967,295)
* - **Block Range**: Contiguous sequence of tape blocks containing the file data
*
* This function is essential for applications that need to:
* - Enumerate all logical files stored on a tape image
* - Build file system catalogs or directory structures for tape media
* - Implement tape navigation and seeking operations
* - Extract individual files from multi-file tape archives
* - Verify tape image completeness and structure
* - Generate tape content manifests for archival documentation
*
* The function supports a two-call pattern for buffer size determination:
* 1. First call with NULL buffer or insufficient length returns AARUF_ERROR_BUFFER_TOO_SMALL
* and sets *length to the required size (entry_count × sizeof(TapeFileEntry))
* 2. Second call with properly sized buffer retrieves all file entries
*
* Alternatively, if the caller knows or pre-allocates sufficient buffer space, a single call
* will succeed and populate the buffer with all tape file entries.
*
* @param context Pointer to the aaruformat context (must be a valid, opened tape image context).
* @param buffer Pointer to a buffer that will receive the array of TapeFileEntry structures.
* May be NULL to query the required buffer size without retrieving data.
* @param length Pointer to a size_t that serves dual purpose:
* - On input: size of the provided buffer in bytes (ignored if buffer is NULL)
* - On output: actual size required/used for all entries in bytes
*
* @return Returns one of the following status codes:
* @retval AARUF_STATUS_OK (0) Successfully retrieved all tape file entries.
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) The context is invalid.
* @retval AARUF_ERROR_TAPE_FILE_NOT_FOUND (-19) No tape file metadata exists.
* @retval AARUF_ERROR_BUFFER_TOO_SMALL (-12) The provided buffer is insufficient.
*
* @see aaruf_get_tape_file() to retrieve a specific file's block range
* @see aaruf_set_tape_file() to define or update a tape file entry
* @see aaruf_get_all_tape_partitions() to retrieve partition boundaries
*/
AARU_EXPORT int32_t AARU_CALL aaruf_get_all_tape_files(const void *context, uint8_t *buffer, size_t *length)
{
TRACE("Entering aaruf_get_all_tape_files(%p, %p, %zu)", context, buffer, (length ? *length : 0));
// Check context is correct AaruFormat context
if(context == NULL)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_all_tape_files() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
const aaruformat_context *ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_all_tape_files() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
if(ctx->tape_files == NULL)
{
FATAL("Image contains no tape files");
TRACE("Exiting aaruf_get_all_tape_files() = AARUF_ERROR_TAPE_FILE_NOT_FOUND");
return AARUF_ERROR_TAPE_FILE_NOT_FOUND;
}
// Iterate all tape files to count how many do we have
const size_t count = HASH_COUNT(ctx->tape_files);
const size_t required_size = count * sizeof(TapeFileEntry);
if(buffer == NULL || length == NULL || *length < required_size)
{
if(length) *length = required_size;
TRACE("Buffer too small for tape files, required %zu bytes", required_size);
TRACE("Exiting aaruf_get_all_tape_files() = AARUF_ERROR_BUFFER_TOO_SMALL");
return AARUF_ERROR_BUFFER_TOO_SMALL;
}
size_t index = 0;
const tapeFileHashEntry *entry;
const tapeFileHashEntry *tmp;
HASH_ITER(hh, ctx->tape_files, entry, tmp)
{
if(index < count)
{
memcpy(&((TapeFileEntry *)buffer)[index], &entry->fileEntry, sizeof(TapeFileEntry));
index++;
}
}
*length = required_size;
TRACE("Exiting aaruf_get_all_tape_files(%p, %p, %zu) = AARUF_STATUS_OK", context, buffer, *length);
return AARUF_STATUS_OK;
}
/**
* @brief Retrieves all tape partition entries from the image.
*
* Extracts the complete set of tape partition metadata entries from an Aaru tape image, returning
* an array of TapePartitionEntry structures. Tape partitions represent physical or logical divisions
* of sequential access media (magnetic tapes) that segment the tape into independent data regions.
* Each partition entry defines a contiguous range of tape blocks (FirstBlock to LastBlock inclusive)
* that comprise one partition on the tape medium.
*
* Tape partitions enable:
* - **Multi-partition tapes**: Modern tape formats (LTO-5+, SDLT) support multiple partitions
* - **Data organization**: Separating metadata, indexes, and data into distinct partitions
* - **Fast access**: Partition switching is faster than sequential seeking across the entire tape
* - **Logical separation**: Isolating different data sets or file systems on the same tape
*
* This function is essential for applications that need to:
* - Enumerate all partitions on a multi-partition tape image
* - Determine partition boundaries and sizes for navigation
* - Implement partition-aware file system drivers or extraction tools
* - Validate tape structure and partition layout integrity
* - Generate tape structure documentation for archival purposes
*
* The function supports a two-call pattern for buffer size determination:
* 1. First call with NULL buffer or insufficient length returns AARUF_ERROR_BUFFER_TOO_SMALL
* and sets *length to the required size (partition_count × sizeof(TapePartitionEntry))
* 2. Second call with properly sized buffer retrieves all partition entries
*
* @param context Pointer to the aaruformat context (must be a valid, opened tape image context).
* @param buffer Pointer to a buffer that will receive the array of TapePartitionEntry structures.
* May be NULL to query the required buffer size without retrieving data.
* @param length Pointer to a size_t that serves dual purpose:
* - On input: size of the provided buffer in bytes (ignored if buffer is NULL)
* - On output: actual size required/used for all entries in bytes
*
* @return Returns one of the following status codes:
* @retval AARUF_STATUS_OK (0) Successfully retrieved all tape partition entries.
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) The context is invalid.
* @retval AARUF_ERROR_TAPE_PARTITION_NOT_FOUND (-20) No tape partition metadata exists.
* @retval AARUF_ERROR_BUFFER_TOO_SMALL (-12) The provided buffer is insufficient.
*
* @see aaruf_get_tape_partition() to retrieve a specific partition's block range
* @see aaruf_set_tape_partition() to define or update a tape partition entry
* @see aaruf_get_all_tape_files() to retrieve all tape file entries
*/
AARU_EXPORT int32_t AARU_CALL aaruf_get_all_tape_partitions(const void *context, uint8_t *buffer, size_t *length)
{
TRACE("Entering aaruf_get_all_tape_partitions(%p, %p, %zu)", context, buffer, (length ? *length : 0));
// Check context is correct AaruFormat context
if(context == NULL)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_all_tape_partitions() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
const aaruformat_context *ctx = context;
// Not a libaaruformat context
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
TRACE("Exiting aaruf_get_all_tape_partitions() = AARUF_ERROR_NOT_AARUFORMAT");
return AARUF_ERROR_NOT_AARUFORMAT;
}
if(ctx->tape_partitions == NULL)
{
FATAL("Image contains no tape partitions");
TRACE("Exiting aaruf_get_all_tape_partitions() = AARUF_ERROR_TAPE_PARTITION_NOT_FOUND");
return AARUF_ERROR_TAPE_PARTITION_NOT_FOUND;
}
// Iterate all tape partitions to count how many do we have
const size_t count = HASH_COUNT(ctx->tape_partitions);
const size_t required_size = count * sizeof(TapePartitionEntry);
if(buffer == NULL || length == NULL || *length < required_size)
{
if(length) *length = required_size;
TRACE("Buffer too small for tape partitions, required %zu bytes", required_size);
TRACE("Exiting aaruf_get_all_tape_partitions() = AARUF_ERROR_BUFFER_TOO_SMALL");
return AARUF_ERROR_BUFFER_TOO_SMALL;
}
size_t index = 0;
const TapePartitionHashEntry *entry;
const TapePartitionHashEntry *tmp;
HASH_ITER(hh, ctx->tape_partitions, entry, tmp)
{
if(index < count)
{
memcpy(&((TapePartitionEntry *)buffer)[index], &entry->partitionEntry, sizeof(TapePartitionEntry));
index++;
}
}
*length = required_size;
TRACE("Exiting aaruf_get_all_tape_partitions(%p, %p, %zu) = AARUF_STATUS_OK", context, buffer, *length);
return AARUF_STATUS_OK;
}