libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
close_write.c
Go to the documentation of this file.
1/*
2 * This file is part of the Aaru Data Preservation Suite.
3 * Copyright (c) 2019-2026 Natalia Portillo.
4 *
5 * This library is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of the
8 * License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
31
32#include <errno.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36
37#include <aaruformat.h>
38
39#include "erasure_internal.h"
40#include "internal.h"
41#include "log.h"
42
71{
72 // Write cached secondary table to file end and update primary table entry with its position
73 // Check if we have a cached table that needs to be written (either it has an offset or exists in memory)
74 bool has_cached_secondary_ddt =
75 ctx->user_data_ddt_header.tableShift > 0 && (ctx->cached_ddt_offset != 0 || ctx->cached_secondary_ddt2 != NULL);
76
77 if(!has_cached_secondary_ddt) return AARUF_STATUS_OK;
78
79 TRACE("Writing cached secondary DDT table to file");
80
81 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
82 aaru_off_t end_of_file = aaruf_ftell(ctx->imageStream);
83
84 // Align the position according to block alignment shift
85 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
86 if(end_of_file & alignment_mask)
87 {
88 // Calculate the next aligned position
89 uint64_t aligned_position = end_of_file + alignment_mask & ~alignment_mask;
90
91 // Seek to the aligned position and pad with zeros if necessary
92 aaruf_fseek(ctx->imageStream, (aaru_off_t)aligned_position, SEEK_SET);
93 end_of_file = aligned_position;
94
95 TRACE("Aligned DDT write position from %ld to %" PRIu64 " (alignment shift: %d)",
96 aaruf_ftell(ctx->imageStream) - (aligned_position - end_of_file), aligned_position,
98 }
99
100 // Prepare DDT header for the cached table
101 DdtHeader2 ddt_header = {0};
103 ddt_header.type = kDataTypeUserData;
104 ddt_header.compression =
106 ddt_header.levels = ctx->user_data_ddt_header.levels;
107 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
108 ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
109 ddt_header.negative = ctx->user_data_ddt_header.negative;
110 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
112 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
113 ddt_header.tableShift = 0; // Secondary tables are single level
114
115 uint64_t items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
116 ddt_header.blocks = items_per_ddt_entry;
117 ddt_header.entries = items_per_ddt_entry;
118 ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry;
119
120 // Calculate data size
121 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
122
123 // Calculate CRC64 of the data
124 crc64_ctx *crc64_context = aaruf_crc64_init();
125 if(crc64_context != NULL)
126 {
127 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
128
129 uint64_t crc64;
130 aaruf_crc64_final(crc64_context, &crc64);
131 ddt_header.crc64 = crc64;
132 }
133
134 // Free CRC64 context
135 aaruf_crc64_free(crc64_context);
136
137 uint8_t *buffer = NULL;
138 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
139
140 if(ddt_header.compression == kCompressionNone)
141 {
142 buffer = (uint8_t *)ctx->cached_secondary_ddt2;
143 ddt_header.cmpCrc64 = ddt_header.crc64;
144 }
145 else
146 {
147 buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
148 if(buffer == NULL)
149 {
150 TRACE("Failed to allocate memory for secondary DDT v2 compression");
152 }
153
154 size_t dst_size;
155
156 if(ctx->use_zstd)
157 {
158 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ddt_header.length * 2,
159 (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
160 ctx->zstd_level, ctx->num_threads);
161 if(dst_size == 0) dst_size = ddt_header.length; // compression failed, fall through to None
162 }
163 else
164 {
165 dst_size = (size_t)ddt_header.length * 2 * 2;
166 size_t props_size = LZMA_PROPERTIES_LENGTH;
167 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
168 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273,
169 LZMA_THREADS(ctx));
170 }
171
172 ddt_header.cmpLength = (uint32_t)dst_size;
173
174 if(ddt_header.cmpLength >= ddt_header.length)
175 {
176 ddt_header.compression = kCompressionNone;
177 free(buffer);
178 buffer = (uint8_t *)ctx->cached_secondary_ddt2;
179 }
180 }
181
182 if(ddt_header.compression == kCompressionNone)
183 {
184 ddt_header.cmpLength = ddt_header.length;
185 ddt_header.cmpCrc64 = ddt_header.crc64;
186 }
187 else
188 {
189 ddt_header.cmpCrc64 = aaruf_crc64_data(buffer, ddt_header.cmpLength);
190 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
191 }
192
193 if(ddt_header.compression == kCompressionLzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
194
195 // Write header
196 if(fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
197 {
198 if(ddt_header.compression == kCompressionLzma)
199 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
200
201 // Write data
202 if(fwrite(buffer, ddt_header.cmpLength, 1, ctx->imageStream) == 1)
203 {
204 // Update primary table entry to point to new location
205 const uint64_t new_secondary_table_block_offset =
206 end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
207
208 ctx->user_data_ddt2[ctx->cached_ddt_position] = (uint64_t)new_secondary_table_block_offset;
209
210 // Update index: remove old entry for cached DDT and add new one
211 TRACE("Updating index for cached secondary DDT");
212
213 // Remove old index entry for the cached DDT
214 if(ctx->cached_ddt_offset != 0)
215 {
216 TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
217 const IndexEntry *entry = NULL;
218
219 // Find and remove the old index entry
220 for(unsigned int k = 0; k < utarray_len(ctx->index_entries); k++)
221 {
222 entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
223 if(entry && entry->offset == ctx->cached_ddt_offset &&
226 {
227 TRACE("Found old DDT index entry at position %u, removing", k);
228 utarray_erase(ctx->index_entries, k, 1);
229 break;
230 }
231 }
232 }
233
234 // Add new index entry for the newly written secondary DDT
235 IndexEntry new_ddt_entry;
237 new_ddt_entry.dataType = kDataTypeUserData;
238 new_ddt_entry.offset = end_of_file;
239
240 utarray_push_back(ctx->index_entries, &new_ddt_entry);
241 ctx->dirty_index_block = true;
242 TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
243
244 // Write the updated primary table back to its original position in the file
245 aaru_off_t saved_pos = aaruf_ftell(ctx->imageStream);
246 aaruf_fseek(ctx->imageStream, (aaru_off_t)(ctx->primary_ddt_offset + sizeof(DdtHeader2)), SEEK_SET);
247
248 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
249
250 size_t primary_written_bytes = 0;
251 primary_written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
252
253 if(primary_written_bytes != 1)
254 {
255 TRACE("Could not flush primary DDT table to file.");
257 }
258
259 aaruf_fseek(ctx->imageStream, saved_pos, SEEK_SET);
260 }
261 else
262 TRACE("Failed to write cached secondary DDT data");
263 }
264 else
265 TRACE("Failed to write cached secondary DDT header");
266
267 // Free the cached table
268 free(ctx->cached_secondary_ddt2);
269 ctx->cached_secondary_ddt2 = NULL;
270 ctx->cached_ddt_offset = 0;
271
272 // Set position
273 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
274
275 if(ddt_header.compression != kCompressionNone) free(buffer);
276
277 return AARUF_STATUS_OK;
278}
279
298{
299 // Write the cached primary DDT table back to its position in the file
300 if(ctx->user_data_ddt_header.tableShift <= 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
301
302 TRACE("Writing cached primary DDT table back to file");
303
304 // Calculate CRC64 of the primary DDT table data first
305 crc64_ctx *crc64_context = aaruf_crc64_init();
306 if(crc64_context != NULL)
307 {
308 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
309
310 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->user_data_ddt2, primary_table_size);
311
312 uint64_t crc64;
313 aaruf_crc64_final(crc64_context, &crc64);
314
315 // Properly populate all header fields for multi-level DDT primary table
319 // levels, tableLevel, previousLevelOffset, negative, overflow, blockAlignmentShift,
320 // dataShift, tableShift, sizeType, entries, blocks, start are already set during creation
321 ctx->user_data_ddt_header.crc64 = crc64;
322 ctx->user_data_ddt_header.cmpCrc64 = crc64;
323 ctx->user_data_ddt_header.length = primary_table_size;
324 ctx->user_data_ddt_header.cmpLength = primary_table_size;
325
326 TRACE("Calculated CRC64 for primary DDT: 0x%16lX", crc64);
327 }
328
329 // Free CRC64 context
330 aaruf_crc64_free(crc64_context);
331
332 // First write the DDT header
334
335 size_t headerWritten = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
336 if(headerWritten != 1)
337 {
338 TRACE("Failed to write primary DDT header to file");
340 }
341
342 // Then write the table data (position is already after the header)
343 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
344
345 // Write the primary table data
346 size_t written_bytes = 0;
347 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
348
349 if(written_bytes == 1)
350 {
351 TRACE("Successfully wrote primary DDT header and table to file (%" PRIu64 " entries, %zu bytes)",
352 ctx->user_data_ddt_header.entries, primary_table_size);
353
354 // Remove any previously existing index entries of the same type before adding
355 TRACE("Removing any previously existing primary DDT index entries");
356 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
357 {
358 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
359 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeUserData)
360 {
361 TRACE("Found existing primary DDT index entry at position %d, removing", k);
362 utarray_erase(ctx->index_entries, k, 1);
363 }
364 }
365
366 // Add primary DDT to index
367 TRACE("Adding primary DDT to index");
368 IndexEntry primary_ddt_entry;
369 primary_ddt_entry.blockType = DeDuplicationTable2;
370 primary_ddt_entry.dataType = kDataTypeUserData;
371 primary_ddt_entry.offset = ctx->primary_ddt_offset;
372
373 utarray_push_back(ctx->index_entries, &primary_ddt_entry);
374 ctx->dirty_index_block = true;
375 TRACE("Added primary DDT index entry at offset %" PRIu64, ctx->primary_ddt_offset);
376 }
377 else
378 TRACE("Failed to write primary DDT table to file");
379
380 return AARUF_STATUS_OK;
381}
382
400{
401 // Write the single level DDT table block aligned just after the header
402 if(ctx->user_data_ddt_header.tableShift != 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
403
404 TRACE("Writing single-level DDT table to file");
405
406 // Calculate CRC64 of the primary DDT table data
407 const size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
408
409 // Properly populate all header fields
414 ctx->user_data_ddt_header.levels = 1; // Single level
415 ctx->user_data_ddt_header.tableLevel = 0; // Top level
416 ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
417 // negative and overflow are already set during creation
418 // blockAlignmentShift, dataShift, tableShift, sizeType, entries, blocks, start are already set
419 ctx->user_data_ddt_header.length = primary_table_size;
420 ctx->user_data_ddt_header.cmpLength = primary_table_size;
421
422 ctx->user_data_ddt_header.crc64 = aaruf_crc64_data((uint8_t *)ctx->user_data_ddt2, primary_table_size);
423
424 TRACE("Calculated CRC64 for single-level DDT: 0x%16lX", ctx->user_data_ddt_header.crc64);
425
426 uint8_t *cmp_buffer = NULL;
427 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
428
430 {
431
432 cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
434 }
435 else
436 {
437 cmp_buffer = malloc((size_t)ctx->user_data_ddt_header.length * 2); // Allocate double size for compression
438 if(cmp_buffer == NULL)
439 {
440 TRACE("Failed to allocate memory for secondary DDT v2 compression");
442 }
443
444 size_t dst_size;
445
446 if(ctx->use_zstd)
447 {
448 dst_size = aaruf_zstd_encode_buffer(cmp_buffer, (size_t)ctx->user_data_ddt_header.length * 2,
449 (uint8_t *)ctx->user_data_ddt2, ctx->user_data_ddt_header.length,
450 ctx->zstd_level, ctx->num_threads);
451 if(dst_size == 0) dst_size = ctx->user_data_ddt_header.length; // fall through to None
452 }
453 else
454 {
455 dst_size = (size_t)ctx->user_data_ddt_header.length * 2 * 2;
456 size_t props_size = LZMA_PROPERTIES_LENGTH;
457 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->user_data_ddt2,
458 ctx->user_data_ddt_header.length, lzma_properties, &props_size, 9,
459 ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
460 }
461
462 ctx->user_data_ddt_header.cmpLength = (uint32_t)dst_size;
463
465 {
467 free(cmp_buffer);
468
469 cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
470 }
471 }
472
474 {
477 }
478 else
479 {
481 aaruf_crc64_data(cmp_buffer, (uint32_t)ctx->user_data_ddt_header.cmpLength);
482 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
483 }
484
487
488 // Write the DDT header first
489 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
490 aaru_off_t ddt_position = aaruf_ftell(ctx->imageStream);
491 // Align index position to block boundary if needed
492 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
493 if(ddt_position & alignment_mask)
494 {
495 const uint64_t aligned_position = ddt_position + alignment_mask & ~alignment_mask;
496 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
497 ddt_position = aligned_position;
498 }
499
500 const size_t header_written = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
501 if(header_written != 1)
502 {
503 TRACE("Failed to write single-level DDT header to file");
505 }
506
507 // Write the primary table data
508 size_t written_bytes = 0;
510 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
511
512 written_bytes = fwrite(cmp_buffer, ctx->user_data_ddt_header.cmpLength, 1, ctx->imageStream);
513
514 if(written_bytes == 1)
515 {
516 TRACE("Successfully wrote single-level DDT header and table to file (%" PRIu64
517 " entries, %zu bytes, %zu compressed bytes)",
519
520 // Remove any previously existing index entries of the same type before adding
521 TRACE("Removing any previously existing single-level DDT index entries");
522 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
523 {
524 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
525 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeUserData)
526 {
527 TRACE("Found existing single-level DDT index entry at position %d, removing", k);
528 utarray_erase(ctx->index_entries, k, 1);
529 }
530 }
531
532 // Add single-level DDT to index
533 TRACE("Adding single-level DDT to index");
534 IndexEntry single_ddt_entry;
535 single_ddt_entry.blockType = DeDuplicationTable2;
536 single_ddt_entry.dataType = kDataTypeUserData;
537 single_ddt_entry.offset = ddt_position;
538
539 utarray_push_back(ctx->index_entries, &single_ddt_entry);
540 ctx->dirty_index_block = true;
541 TRACE("Added single-level DDT index entry at offset %" PRIu64, ddt_position);
542 }
543 else
544 TRACE("Failed to write single-level DDT table data to file");
545
546 // Free compression buffer if it was allocated
547 if(ctx->user_data_ddt_header.compression != kCompressionNone && cmp_buffer != (uint8_t *)ctx->user_data_ddt2)
548 free(cmp_buffer);
549
550 return AARUF_STATUS_OK;
551}
552
661{
662 if(!ctx->is_tape) return AARUF_STATUS_INVALID_CONTEXT;
663
664 // Traverse the tape DDT uthash and find the biggest key
665 uint64_t max_key = 0;
666 TapeDdtHashEntry *entry, *tmp;
667 HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
668 if(entry->key > max_key) max_key = entry->key;
669
670 // Initialize context user data DDT header
675 ctx->user_data_ddt_header.levels = 1; // Single level
676 ctx->user_data_ddt_header.tableLevel = 0; // Top level
677 ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
680 ctx->user_data_ddt_header.tableShift = 0; // Single level
681 ctx->user_data_ddt_header.entries = max_key + 1;
682 ctx->user_data_ddt_header.blocks = max_key + 1;
684 ctx->user_data_ddt_header.length = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
686
687 // Initialize memory for user data DDT
688 ctx->user_data_ddt2 = calloc(ctx->user_data_ddt_header.entries, sizeof(uint64_t));
689 if(ctx->user_data_ddt2 == NULL)
690 {
691 TRACE("Failed to allocate memory for tape DDT table");
693 }
694
695 // Populate user data DDT from tape DDT uthash
696 HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
697 if(entry->key < ctx->user_data_ddt_header.blocks) ctx->user_data_ddt2[entry->key] = entry->value;
698
699 // Do not repeat code
700 return write_single_level_ddt(ctx);
701}
702
720{
721 uint64_t alignment_mask;
722 uint64_t aligned_position;
723
724 // Finalize pending checksums
725 if(ctx->calculating_md5)
726 {
727 ctx->checksums.hasMd5 = true;
729 }
730 if(ctx->calculating_sha1)
731 {
732 ctx->checksums.hasSha1 = true;
734 }
735 if(ctx->calculating_sha256)
736 {
737 ctx->checksums.hasSha256 = true;
739 }
740 if(ctx->calculating_spamsum)
741 {
742 ctx->checksums.hasSpamSum = true;
743 ctx->checksums.spamsum = calloc(1, FUZZY_MAX_RESULT);
746 }
747 if(ctx->calculating_blake3)
748 {
749 ctx->checksums.hasBlake3 = true;
750 blake3_hasher_finalize(ctx->blake3_context, ctx->checksums.blake3, BLAKE3_OUT_LEN);
751 free(ctx->blake3_context);
752 }
753
754 // Write the checksums block
755 bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 ||
757
758 if(!has_checksums) return;
759
760 ChecksumHeader checksum_header = {0};
761 checksum_header.identifier = ChecksumBlock;
762
763 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
764 aaru_off_t checksum_position = aaruf_ftell(ctx->imageStream);
765 // Align index position to block boundary if needed
766 alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
767 if(checksum_position & alignment_mask)
768 {
769 aligned_position = checksum_position + alignment_mask & ~alignment_mask;
770 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
771 checksum_position = aligned_position;
772 }
773
774 // Skip checksum_header
775 aaruf_fseek(ctx->imageStream, sizeof(checksum_header), SEEK_CUR);
776
777 if(ctx->checksums.hasMd5)
778 {
779 TRACE("Writing MD5 checksum entry");
780 ChecksumEntry md5_entry = {0};
781 md5_entry.length = MD5_DIGEST_LENGTH;
782 md5_entry.type = Md5;
783 fwrite(&md5_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
784 fwrite(&ctx->checksums.md5, MD5_DIGEST_LENGTH, 1, ctx->imageStream);
785 checksum_header.length += sizeof(ChecksumEntry) + MD5_DIGEST_LENGTH;
786 checksum_header.entries++;
787 }
788
789 if(ctx->checksums.hasSha1)
790 {
791 TRACE("Writing SHA1 checksum entry");
792 ChecksumEntry sha1_entry = {0};
793 sha1_entry.length = SHA1_DIGEST_LENGTH;
794 sha1_entry.type = Sha1;
795 fwrite(&sha1_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
796 fwrite(&ctx->checksums.sha1, SHA1_DIGEST_LENGTH, 1, ctx->imageStream);
797 checksum_header.length += sizeof(ChecksumEntry) + SHA1_DIGEST_LENGTH;
798 checksum_header.entries++;
799 }
800
801 if(ctx->checksums.hasSha256)
802 {
803 TRACE("Writing SHA256 checksum entry");
804 ChecksumEntry sha256_entry = {0};
805 sha256_entry.length = SHA256_DIGEST_LENGTH;
806 sha256_entry.type = Sha256;
807 fwrite(&sha256_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
808 fwrite(&ctx->checksums.sha256, SHA256_DIGEST_LENGTH, 1, ctx->imageStream);
809 checksum_header.length += sizeof(ChecksumEntry) + SHA256_DIGEST_LENGTH;
810 checksum_header.entries++;
811 }
812
813 if(ctx->checksums.hasSpamSum)
814 {
815 TRACE("Writing SpamSum checksum entry");
816 ChecksumEntry spamsum_entry = {0};
817 spamsum_entry.length = strlen((const char *)ctx->checksums.spamsum);
818 spamsum_entry.type = SpamSum;
819 fwrite(&spamsum_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
820 fwrite(ctx->checksums.spamsum, spamsum_entry.length, 1, ctx->imageStream);
821 checksum_header.length += sizeof(ChecksumEntry) + spamsum_entry.length;
822 checksum_header.entries++;
823 }
824
825 if(ctx->checksums.hasBlake3)
826 {
827 TRACE("Writing BLAKE3 checksum entry");
828 ChecksumEntry blake3_entry = {0};
829 blake3_entry.length = BLAKE3_OUT_LEN;
830 blake3_entry.type = Blake3;
831 fwrite(&blake3_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
832 fwrite(&ctx->checksums.blake3, BLAKE3_OUT_LEN, 1, ctx->imageStream);
833 checksum_header.length += sizeof(ChecksumEntry) + BLAKE3_OUT_LEN;
834 checksum_header.entries++;
836 }
837
838 aaruf_fseek(ctx->imageStream, checksum_position, SEEK_SET);
839 TRACE("Writing checksum header");
840 fwrite(&checksum_header, sizeof(ChecksumHeader), 1, ctx->imageStream);
841
842 // Remove any previously existing index entries of the same type before adding
843 TRACE("Removing any previously existing checksum block index entries");
844 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
845 {
846 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
847 if(entry && entry->blockType == ChecksumBlock && entry->dataType == 0)
848 {
849 TRACE("Found existing checksum block index entry at position %d, removing", k);
850 utarray_erase(ctx->index_entries, k, 1);
851 }
852 }
853
854 // Add checksum block to index
855 TRACE("Adding checksum block to index");
856 IndexEntry checksum_index_entry;
857 checksum_index_entry.blockType = ChecksumBlock;
858 checksum_index_entry.dataType = 0;
859 checksum_index_entry.offset = checksum_position;
860
861 utarray_push_back(ctx->index_entries, &checksum_index_entry);
862 ctx->dirty_index_block = true;
863 TRACE("Added checksum block index entry at offset %" PRIu64, checksum_position);
864}
865
877{
878 // Write tracks block
879 if(ctx->tracks_header.entries <= 0 || ctx->track_entries == NULL) return;
880
881 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
882 aaru_off_t tracks_position = aaruf_ftell(ctx->imageStream);
883 // Align index position to block boundary if needed
884 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
885 if(tracks_position & alignment_mask)
886 {
887 uint64_t aligned_position = tracks_position + alignment_mask & ~alignment_mask;
888 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
889 tracks_position = aligned_position;
890 }
891
892 TRACE("Writing tracks block at position %ld", tracks_position);
893 // Write header
894 if(fwrite(&ctx->tracks_header, sizeof(TracksHeader), 1, ctx->imageStream) == 1)
895 {
896 // Write entries
897 size_t written_entries =
898 fwrite(ctx->track_entries, sizeof(TrackEntry), ctx->tracks_header.entries, ctx->imageStream);
899
900 if(written_entries == ctx->tracks_header.entries)
901 {
902 TRACE("Successfully wrote tracks block with %u entries", ctx->tracks_header.entries);
903
904 // Remove any previously existing index entries of the same type before adding
905 TRACE("Removing any previously existing tracks block index entries");
906 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
907 {
908 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
909 if(entry && entry->blockType == TracksBlock && entry->dataType == 0)
910 {
911 TRACE("Found existing tracks block index entry at position %d, removing", k);
912 utarray_erase(ctx->index_entries, k, 1);
913 }
914 }
915
916 // Add tracks block to index
917 TRACE("Adding tracks block to index");
918
919 IndexEntry tracks_index_entry;
920 tracks_index_entry.blockType = TracksBlock;
921 tracks_index_entry.dataType = 0;
922 tracks_index_entry.offset = tracks_position;
923 utarray_push_back(ctx->index_entries, &tracks_index_entry);
924 ctx->dirty_index_block = true;
925 TRACE("Added tracks block index entry at offset %" PRIu64, tracks_position);
926 }
927 }
928}
929
943{
944 // Write MODE 2 subheader data block
945 if(ctx->mode2_subheaders == NULL) return;
946
947 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
948 aaru_off_t mode2_subheaders_position = aaruf_ftell(ctx->imageStream);
949 // Align index position to block boundary if needed
950 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
951 if(mode2_subheaders_position & alignment_mask)
952 {
953 uint64_t aligned_position = mode2_subheaders_position + alignment_mask & ~alignment_mask;
954 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
955 mode2_subheaders_position = aligned_position;
956 }
957
958 TRACE("Writing MODE 2 subheaders block at position %ld", mode2_subheaders_position);
959 BlockHeader subheaders_block = {0};
960 subheaders_block.identifier = DataBlock;
961 subheaders_block.type = kDataTypeCdSubHeader;
962 subheaders_block.compression =
964 subheaders_block.length =
966 8;
967
968 // Calculate CRC64
969 subheaders_block.crc64 = aaruf_crc64_data(ctx->mode2_subheaders, subheaders_block.length);
970
971 uint8_t *buffer = NULL;
972 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
973
974 if(subheaders_block.compression == kCompressionNone)
975 {
976 buffer = ctx->mode2_subheaders;
977 subheaders_block.cmpCrc64 = subheaders_block.crc64;
978 }
979 else
980 {
981 buffer = malloc((size_t)subheaders_block.length * 2); // Allocate double size for compression
982 if(buffer == NULL)
983 {
984 TRACE("Failed to allocate memory for MODE 2 subheaders compression");
985 return;
986 }
987
988 size_t dst_size;
989
990 if(ctx->use_zstd)
991 {
992 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)subheaders_block.length * 2, ctx->mode2_subheaders,
993 subheaders_block.length, ctx->zstd_level, ctx->num_threads);
994 if(dst_size == 0) dst_size = subheaders_block.length;
995 }
996 else
997 {
998 dst_size = (size_t)subheaders_block.length * 2 * 2;
999 size_t props_size = LZMA_PROPERTIES_LENGTH;
1000 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->mode2_subheaders, subheaders_block.length, lzma_properties,
1001 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1002 }
1003
1004 subheaders_block.cmpLength = (uint32_t)dst_size;
1005
1006 if(subheaders_block.cmpLength >= subheaders_block.length)
1007 {
1008 subheaders_block.compression = kCompressionNone;
1009 free(buffer);
1010 buffer = ctx->mode2_subheaders;
1011 }
1012 }
1013
1014 if(subheaders_block.compression == kCompressionNone)
1015 {
1016 subheaders_block.cmpLength = subheaders_block.length;
1017 subheaders_block.cmpCrc64 = subheaders_block.crc64;
1018 }
1019 else
1020 {
1021 subheaders_block.cmpCrc64 = aaruf_crc64_data(buffer, subheaders_block.cmpLength);
1022 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1023 }
1024
1025 const size_t length_to_write = subheaders_block.cmpLength;
1026 if(subheaders_block.compression == kCompressionLzma) subheaders_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1027
1028 // Write header
1029 if(fwrite(&subheaders_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1030 {
1031 if(subheaders_block.compression == kCompressionLzma)
1032 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1033
1034 // Write data
1035 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1036 if(written_bytes == 1)
1037 {
1038 TRACE("Successfully wrote MODE 2 subheaders block (%" PRIu64 " bytes)", subheaders_block.cmpLength);
1039
1040 // Remove any previously existing index entries of the same type before adding
1041 TRACE("Removing any previously existing MODE 2 subheaders block index entries");
1042 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1043 {
1044 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1045 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeCdSubHeader)
1046 {
1047 TRACE("Found existing MODE 2 subheaders block index entry at position %d, removing", k);
1048 utarray_erase(ctx->index_entries, k, 1);
1049 }
1050 }
1051
1052 // Add MODE 2 subheaders block to index
1053 TRACE("Adding MODE 2 subheaders block to index");
1054 IndexEntry mode2_subheaders_index_entry;
1055 mode2_subheaders_index_entry.blockType = DataBlock;
1056 mode2_subheaders_index_entry.dataType = kDataTypeCdSubHeader;
1057 mode2_subheaders_index_entry.offset = mode2_subheaders_position;
1058 utarray_push_back(ctx->index_entries, &mode2_subheaders_index_entry);
1059 ctx->dirty_index_block = true;
1060 TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, mode2_subheaders_position);
1061 }
1062 }
1063
1064 if(subheaders_block.compression != kCompressionNone) free(buffer);
1065}
1066
1090{
1091 if(ctx->sector_prefix == NULL) return;
1092
1093 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1094 aaru_off_t prefix_position = aaruf_ftell(ctx->imageStream);
1095 // Align index position to block boundary if needed
1096 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1097 if(prefix_position & alignment_mask)
1098 {
1099 uint64_t aligned_position = prefix_position + alignment_mask & ~alignment_mask;
1100 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1101 prefix_position = aligned_position;
1102 }
1103
1104 TRACE("Writing sector prefix block at position %ld", prefix_position);
1105 BlockHeader prefix_block = {0};
1106 prefix_block.identifier = DataBlock;
1107 prefix_block.type = kDataTypeCdSectorPrefix;
1108 prefix_block.compression =
1110 prefix_block.length = (uint32_t)ctx->sector_prefix_offset;
1111
1112 // Calculate CRC64
1113 prefix_block.crc64 = aaruf_crc64_data(ctx->sector_prefix, prefix_block.length);
1114
1115 uint8_t *buffer = NULL;
1116 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1117
1118 if(prefix_block.compression == kCompressionNone)
1119 {
1120 buffer = ctx->sector_prefix;
1121 prefix_block.cmpCrc64 = prefix_block.crc64;
1122 }
1123 else
1124 {
1125 buffer = malloc((size_t)prefix_block.length * 2); // Allocate double size for compression
1126 if(buffer == NULL)
1127 {
1128 TRACE("Failed to allocate memory for CD sector prefix compression");
1129 return;
1130 }
1131
1132 size_t dst_size;
1133
1134 if(ctx->use_zstd)
1135 {
1136 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)prefix_block.length * 2, ctx->sector_prefix,
1137 prefix_block.length, ctx->zstd_level, ctx->num_threads);
1138 if(dst_size == 0) dst_size = prefix_block.length;
1139 }
1140 else
1141 {
1142 dst_size = (size_t)prefix_block.length * 2 * 2;
1143 size_t props_size = LZMA_PROPERTIES_LENGTH;
1144 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_prefix, prefix_block.length, lzma_properties,
1145 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1146 }
1147
1148 prefix_block.cmpLength = (uint32_t)dst_size;
1149
1150 if(prefix_block.cmpLength >= prefix_block.length)
1151 {
1152 prefix_block.compression = kCompressionNone;
1153 free(buffer);
1154 buffer = ctx->sector_prefix;
1155 }
1156 }
1157
1158 if(prefix_block.compression == kCompressionNone)
1159 {
1160 prefix_block.cmpLength = prefix_block.length;
1161 prefix_block.cmpCrc64 = prefix_block.crc64;
1162 }
1163 else
1164 {
1165 prefix_block.cmpCrc64 = aaruf_crc64_data(buffer, prefix_block.cmpLength);
1166 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1167 }
1168
1169 const size_t length_to_write = prefix_block.cmpLength;
1170 if(prefix_block.compression == kCompressionLzma) prefix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1171
1172 // Write header
1173 if(fwrite(&prefix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1174 {
1175 if(prefix_block.compression == kCompressionLzma)
1176 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1177
1178 // Write data
1179 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1180 if(written_bytes == 1)
1181 {
1182 TRACE("Successfully wrote CD sector prefix block (%" PRIu64 " bytes)", prefix_block.cmpLength);
1183
1184 // Remove any previously existing index entries of the same type before adding
1185 TRACE("Removing any previously existing CD sector prefix block index entries");
1186 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1187 {
1188 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1189 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeCdSectorPrefix)
1190 {
1191 TRACE("Found existing CD sector prefix block index entry at position %d, removing", k);
1192 utarray_erase(ctx->index_entries, k, 1);
1193 }
1194 }
1195
1196 // Add prefix block to index
1197 TRACE("Adding CD sector prefix block to index");
1198 IndexEntry prefix_index_entry;
1199 prefix_index_entry.blockType = DataBlock;
1200 prefix_index_entry.dataType = kDataTypeCdSectorPrefix;
1201 prefix_index_entry.offset = prefix_position;
1202 utarray_push_back(ctx->index_entries, &prefix_index_entry);
1203 ctx->dirty_index_block = true;
1204 TRACE("Added CD sector prefix block index entry at offset %" PRIu64, prefix_position);
1205 }
1206 }
1207
1208 if(prefix_block.compression != kCompressionNone) free(buffer);
1209}
1210
1243{
1244 if(ctx->sector_suffix == NULL) return;
1245
1246 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1247 aaru_off_t suffix_position = aaruf_ftell(ctx->imageStream);
1248 // Align index position to block boundary if needed
1249 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1250 if(suffix_position & alignment_mask)
1251 {
1252 const uint64_t aligned_position = suffix_position + alignment_mask & ~alignment_mask;
1253 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1254 suffix_position = aligned_position;
1255 }
1256
1257 TRACE("Writing sector suffix block at position %ld", suffix_position);
1258 BlockHeader suffix_block = {0};
1259 suffix_block.identifier = DataBlock;
1260 suffix_block.type = kDataTypeCdSectorSuffix;
1261 suffix_block.compression =
1263 suffix_block.length = (uint32_t)ctx->sector_suffix_offset;
1264
1265 // Calculate CRC64
1266 suffix_block.crc64 = aaruf_crc64_data(ctx->sector_suffix, suffix_block.length);
1267
1268 uint8_t *buffer = NULL;
1269 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1270
1271 if(suffix_block.compression == kCompressionNone)
1272 {
1273 buffer = ctx->sector_suffix;
1274 suffix_block.cmpCrc64 = suffix_block.crc64;
1275 }
1276 else
1277 {
1278 buffer = malloc((size_t)suffix_block.length * 2); // Allocate double size for compression
1279 if(buffer == NULL)
1280 {
1281 TRACE("Failed to allocate memory for CD sector suffix compression");
1282 return;
1283 }
1284
1285 size_t dst_size;
1286
1287 if(ctx->use_zstd)
1288 {
1289 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)suffix_block.length * 2, ctx->sector_suffix,
1290 suffix_block.length, ctx->zstd_level, ctx->num_threads);
1291 if(dst_size == 0) dst_size = suffix_block.length;
1292 }
1293 else
1294 {
1295 dst_size = (size_t)suffix_block.length * 2 * 2;
1296 size_t props_size = LZMA_PROPERTIES_LENGTH;
1297 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_suffix, suffix_block.length, lzma_properties,
1298 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1299 }
1300
1301 suffix_block.cmpLength = (uint32_t)dst_size;
1302
1303 if(suffix_block.cmpLength >= suffix_block.length)
1304 {
1305 suffix_block.compression = kCompressionNone;
1306 free(buffer);
1307 buffer = ctx->sector_suffix;
1308 }
1309 }
1310
1311 if(suffix_block.compression == kCompressionNone)
1312 {
1313 suffix_block.cmpLength = suffix_block.length;
1314 suffix_block.cmpCrc64 = suffix_block.crc64;
1315 }
1316 else
1317 {
1318 suffix_block.cmpCrc64 = aaruf_crc64_data(buffer, suffix_block.cmpLength);
1319 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1320 }
1321
1322 const size_t length_to_write = suffix_block.cmpLength;
1323 if(suffix_block.compression == kCompressionLzma) suffix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1324
1325 // Write header
1326 if(fwrite(&suffix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1327 {
1328 if(suffix_block.compression == kCompressionLzma)
1329 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1330
1331 // Write data
1332 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1333 if(written_bytes == 1)
1334 {
1335 TRACE("Successfully wrote CD sector suffix block (%" PRIu64 " bytes)", suffix_block.cmpLength);
1336
1337 // Remove any previously existing index entries of the same type before adding
1338 TRACE("Removing any previously existing CD sector suffix block index entries");
1339 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1340 {
1341 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1342 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeCdSectorSuffix)
1343 {
1344 TRACE("Found existing CD sector suffix block index entry at position %d, removing", k);
1345 utarray_erase(ctx->index_entries, k, 1);
1346 }
1347 }
1348
1349 // Add suffix block to index
1350 TRACE("Adding CD sector suffix block to index");
1351 IndexEntry suffix_index_entry;
1352 suffix_index_entry.blockType = DataBlock;
1353 suffix_index_entry.dataType = kDataTypeCdSectorSuffix;
1354 suffix_index_entry.offset = suffix_position;
1355 utarray_push_back(ctx->index_entries, &suffix_index_entry);
1356 ctx->dirty_index_block = true;
1357 TRACE("Added CD sector suffix block index entry at offset %" PRIu64, suffix_position);
1358 }
1359 }
1360
1361 if(suffix_block.compression != kCompressionNone) free(buffer);
1362}
1363
1392{
1393 if(ctx->sector_prefix_ddt2 == NULL) return;
1394
1395 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1396 aaru_off_t prefix_ddt_position = aaruf_ftell(ctx->imageStream);
1397 // Align index position to block boundary if needed
1398 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1399 if(prefix_ddt_position & alignment_mask)
1400 {
1401 const uint64_t aligned_position = prefix_ddt_position + alignment_mask & ~alignment_mask;
1402 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1403 prefix_ddt_position = aligned_position;
1404 }
1405
1406 TRACE("Writing sector prefix DDT v2 at position %ld", prefix_ddt_position);
1407 DdtHeader2 ddt_header2 = {0};
1408 ddt_header2.identifier = DeDuplicationTable2;
1409 ddt_header2.type = kDataTypeCdSectorPrefix;
1410 ddt_header2.compression =
1412 ddt_header2.levels = 1;
1413 ddt_header2.tableLevel = 0;
1414 ddt_header2.negative = ctx->user_data_ddt_header.negative;
1415 ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
1417 ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
1418 ddt_header2.tableShift = 0; // Single-level DDT
1419 ddt_header2.entries =
1421 ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
1422 ddt_header2.start = 0;
1423 ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
1424 // Calculate CRC64
1425 ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_prefix_ddt2, (uint32_t)ddt_header2.length);
1426
1427 uint8_t *buffer = NULL;
1428 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1429
1430 if(ddt_header2.compression == kCompressionNone)
1431 {
1432 buffer = (uint8_t *)ctx->sector_prefix_ddt2;
1433 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1434 }
1435 else
1436 {
1437 buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
1438 if(buffer == NULL)
1439 {
1440 TRACE("Failed to allocate memory for sector prefix DDT v2 compression");
1441 return;
1442 }
1443
1444 size_t dst_size;
1445
1446 if(ctx->use_zstd)
1447 {
1448 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ddt_header2.length * 2,
1449 (uint8_t *)ctx->sector_prefix_ddt2, ddt_header2.length,
1450 ctx->zstd_level, ctx->num_threads);
1451 if(dst_size == 0) dst_size = ddt_header2.length;
1452 }
1453 else
1454 {
1455 dst_size = (size_t)ddt_header2.length * 2 * 2;
1456 size_t props_size = LZMA_PROPERTIES_LENGTH;
1457 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_prefix_ddt2, ddt_header2.length,
1458 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273,
1459 LZMA_THREADS(ctx));
1460 }
1461
1462 ddt_header2.cmpLength = (uint32_t)dst_size;
1463
1464 if(ddt_header2.cmpLength >= ddt_header2.length)
1465 {
1466 ddt_header2.compression = kCompressionNone;
1467 free(buffer);
1468 buffer = (uint8_t *)ctx->sector_prefix_ddt2;
1469 }
1470 }
1471
1472 if(ddt_header2.compression == kCompressionNone)
1473 {
1474 ddt_header2.cmpLength = ddt_header2.length;
1475 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1476 }
1477 else
1478 {
1479 ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
1480 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1481 }
1482
1483 const size_t length_to_write = ddt_header2.cmpLength;
1484 if(ddt_header2.compression == kCompressionLzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
1485
1486 // Write header
1487 if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
1488 {
1489 if(ddt_header2.compression == kCompressionLzma)
1490 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1491
1492 // Write data
1493 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1494 if(written_bytes == 1)
1495 {
1496 TRACE("Successfully wrote sector prefix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
1497
1498 // Remove any previously existing index entries of the same type before adding
1499 TRACE("Removing any previously existing sector prefix DDT v2 index entries");
1500 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1501 {
1502 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1503 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeCdSectorPrefix)
1504 {
1505 TRACE("Found existing sector prefix DDT v2 index entry at position %d, removing", k);
1506 utarray_erase(ctx->index_entries, k, 1);
1507 }
1508 }
1509
1510 // Add prefix block to index
1511 TRACE("Adding sector prefix DDT v2 to index");
1512 IndexEntry prefix_ddt_index_entry;
1513 prefix_ddt_index_entry.blockType = DeDuplicationTable2;
1514 prefix_ddt_index_entry.dataType = kDataTypeCdSectorPrefix;
1515 prefix_ddt_index_entry.offset = prefix_ddt_position;
1516 utarray_push_back(ctx->index_entries, &prefix_ddt_index_entry);
1517 ctx->dirty_index_block = true;
1518 TRACE("Added sector prefix DDT v2 index entry at offset %" PRIu64, prefix_ddt_position);
1519 }
1520 }
1521
1522 if(ddt_header2.compression != kCompressionNone) free(buffer);
1523}
1524
1569{
1570 if(ctx->sector_suffix_ddt2 == NULL) return;
1571
1572 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1573 aaru_off_t suffix_ddt_position = aaruf_ftell(ctx->imageStream);
1574 // Align index position to block boundary if needed
1575 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1576 if(suffix_ddt_position & alignment_mask)
1577 {
1578 const uint64_t aligned_position = suffix_ddt_position + alignment_mask & ~alignment_mask;
1579 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1580 suffix_ddt_position = aligned_position;
1581 }
1582
1583 TRACE("Writing sector suffix DDT v2 at position %ld", suffix_ddt_position);
1584 DdtHeader2 ddt_header2 = {0};
1585 ddt_header2.identifier = DeDuplicationTable2;
1586 ddt_header2.type = kDataTypeCdSectorSuffix;
1587 ddt_header2.compression =
1589 ddt_header2.levels = 1;
1590 ddt_header2.tableLevel = 0;
1591 ddt_header2.negative = ctx->user_data_ddt_header.negative;
1592 ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
1594 ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
1595 ddt_header2.tableShift = 0; // Single-level DDT
1596 ddt_header2.entries =
1598 ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
1599 ddt_header2.start = 0;
1600 ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
1601 // Calculate CRC64
1602 ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_suffix_ddt2, (uint32_t)ddt_header2.length);
1603
1604 uint8_t *buffer = NULL;
1605 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1606
1607 if(ddt_header2.compression == kCompressionNone)
1608 {
1609 buffer = (uint8_t *)ctx->sector_suffix_ddt2;
1610 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1611 }
1612 else
1613 {
1614 buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
1615 if(buffer == NULL)
1616 {
1617 TRACE("Failed to allocate memory for sector suffix DDT v2 compression");
1618 return;
1619 }
1620
1621 size_t dst_size;
1622
1623 if(ctx->use_zstd)
1624 {
1625 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ddt_header2.length * 2,
1626 (uint8_t *)ctx->sector_suffix_ddt2, ddt_header2.length,
1627 ctx->zstd_level, ctx->num_threads);
1628 if(dst_size == 0) dst_size = ddt_header2.length;
1629 }
1630 else
1631 {
1632 dst_size = (size_t)ddt_header2.length * 2 * 2;
1633 size_t props_size = LZMA_PROPERTIES_LENGTH;
1634 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_suffix_ddt2, ddt_header2.length,
1635 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273,
1636 LZMA_THREADS(ctx));
1637 }
1638
1639 ddt_header2.cmpLength = (uint32_t)dst_size;
1640
1641 if(ddt_header2.cmpLength >= ddt_header2.length)
1642 {
1643 ddt_header2.compression = kCompressionNone;
1644 free(buffer);
1645 buffer = (uint8_t *)ctx->sector_suffix_ddt2;
1646 }
1647 }
1648
1649 if(ddt_header2.compression == kCompressionNone)
1650 {
1651 ddt_header2.cmpLength = ddt_header2.length;
1652 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1653 }
1654 else
1655 {
1656 ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
1657 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1658 }
1659
1660 const size_t length_to_write = ddt_header2.cmpLength;
1661 if(ddt_header2.compression == kCompressionLzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
1662
1663 // Write header
1664 if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
1665 {
1666 if(ddt_header2.compression == kCompressionLzma)
1667 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1668
1669 // Write data
1670 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1671 if(written_bytes == 1)
1672 {
1673 TRACE("Successfully wrote sector suffix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
1674
1675 // Remove any previously existing index entries of the same type before adding
1676 TRACE("Removing any previously existing sector suffix DDT v2 block index entries");
1677 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1678 {
1679 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1680 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeCdSectorSuffix)
1681 {
1682 TRACE("Found existing sector suffix DDT v2 index entry at position %d, removing", k);
1683 utarray_erase(ctx->index_entries, k, 1);
1684 }
1685 }
1686
1687 // Add suffix block to index
1688 TRACE("Adding sector suffix DDT v2 to index");
1689 IndexEntry suffix_ddt_index_entry;
1690 suffix_ddt_index_entry.blockType = DeDuplicationTable2;
1691 suffix_ddt_index_entry.dataType = kDataTypeCdSectorSuffix;
1692 suffix_ddt_index_entry.offset = suffix_ddt_position;
1693 utarray_push_back(ctx->index_entries, &suffix_ddt_index_entry);
1694 ctx->dirty_index_block = true;
1695 TRACE("Added sector suffix DDT v2 index entry at offset %" PRIu64, suffix_ddt_position);
1696 }
1697 }
1698
1699 if(ddt_header2.compression != kCompressionNone) free(buffer);
1700}
1701
1760{
1761 if(ctx->sector_subchannel == NULL) return;
1762
1763 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1764 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
1765 // Align index position to block boundary if needed
1766 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1767 if(block_position & alignment_mask)
1768 {
1769 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
1770 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1771 block_position = aligned_position;
1772 }
1773
1774 TRACE("Writing sector subchannel block at position %ld", block_position);
1775 BlockHeader subchannel_block = {0};
1776 subchannel_block.identifier = DataBlock;
1777 subchannel_block.compression = kCompressionNone;
1778
1779 uint8_t *buffer = ctx->sector_subchannel;
1780 bool owns_buffer = false;
1781 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1782
1784 {
1785 subchannel_block.type = kDataTypeCdSubchannel;
1786 subchannel_block.length = (uint32_t)(ctx->user_data_ddt_header.negative + ctx->image_info.Sectors +
1788 96;
1789 subchannel_block.cmpLength = subchannel_block.length;
1790
1791 if(ctx->compression_enabled)
1792 {
1793 uint8_t *cst_buffer = malloc(subchannel_block.length);
1794
1795 if(cst_buffer == NULL)
1796 {
1797 TRACE("Failed to allocate memory for Claunia Subchannel Transform output");
1798 return;
1799 }
1800
1801 uint8_t *dst_buffer = malloc(subchannel_block.length);
1802
1803 if(dst_buffer == NULL)
1804 {
1805 TRACE("Failed to allocate memory for compressed output");
1806 free(cst_buffer);
1807 return;
1808 }
1809
1810 aaruf_cst_transform(ctx->sector_subchannel, cst_buffer, subchannel_block.length);
1811
1812 size_t dst_size;
1813
1814 if(ctx->use_zstd)
1815 {
1816 dst_size = aaruf_zstd_encode_buffer(dst_buffer, subchannel_block.length, cst_buffer,
1817 subchannel_block.length, ctx->zstd_level, ctx->num_threads);
1818 if(dst_size == 0) dst_size = subchannel_block.length; /* compression failed, fall through to none */
1819 }
1820 else
1821 {
1822 dst_size = subchannel_block.length;
1823 size_t props_size = LZMA_PROPERTIES_LENGTH;
1824
1825 aaruf_lzma_encode_buffer(dst_buffer, &dst_size, cst_buffer, subchannel_block.length, lzma_properties,
1826 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1827 }
1828
1829 free(cst_buffer);
1830
1831 if(dst_size < subchannel_block.length)
1832 {
1833 subchannel_block.compression = ctx->use_zstd ? kCompressionZstdCst : kCompressionLzmaCst;
1834 subchannel_block.cmpLength = (uint32_t)dst_size;
1835 buffer = dst_buffer;
1836 owns_buffer = true;
1837 }
1838 else
1839 {
1840 subchannel_block.compression = kCompressionNone;
1841 free(dst_buffer);
1842 subchannel_block.cmpLength = subchannel_block.length;
1843 }
1844 }
1845 }
1846 else if(ctx->image_info.MetadataMediaType == BlockMedia)
1847 {
1848 switch(ctx->image_info.MediaType)
1849 {
1850 case AppleProfile:
1851 case AppleFileWare:
1852 subchannel_block.type = kDataTypeAppleProfileTag;
1853 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 20;
1854 break;
1855 case AppleSonyDS:
1856 case AppleSonySS:
1857 subchannel_block.type = kDataTypeAppleSonyTag;
1858 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 12;
1859 break;
1860 case PriamDataTower:
1861 subchannel_block.type = kDataTypePriamDataTowerTag;
1862 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 24;
1863 break;
1864 default:
1865 TRACE("Incorrect media type, not writing sector subchannel block");
1866 return; // Incorrect media type
1867 }
1868 subchannel_block.cmpLength = subchannel_block.length;
1869
1870 uint8_t *dst_buffer = malloc(subchannel_block.length);
1871
1872 if(dst_buffer == NULL)
1873 {
1874 TRACE("Failed to allocate memory for compressed output");
1875 return;
1876 }
1877
1878 size_t dst_size;
1879
1880 if(ctx->use_zstd)
1881 {
1882 dst_size = aaruf_zstd_encode_buffer(dst_buffer, subchannel_block.length, ctx->sector_subchannel,
1883 subchannel_block.length, ctx->zstd_level, ctx->num_threads);
1884 if(dst_size == 0) dst_size = subchannel_block.length; /* compression failed, fall through to none */
1885 }
1886 else
1887 {
1888 dst_size = subchannel_block.length;
1889 size_t props_size = LZMA_PROPERTIES_LENGTH;
1890
1891 aaruf_lzma_encode_buffer(dst_buffer, &dst_size, ctx->sector_subchannel, subchannel_block.length,
1892 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1893 }
1894
1895 if(dst_size < subchannel_block.length)
1896 {
1897 subchannel_block.compression = ctx->use_zstd ? kCompressionZstd : kCompressionLzma;
1898 subchannel_block.cmpLength = (uint32_t)dst_size;
1899 buffer = dst_buffer;
1900 owns_buffer = true;
1901 }
1902 else
1903 {
1904 subchannel_block.compression = kCompressionNone;
1905 free(dst_buffer);
1906 subchannel_block.cmpLength = subchannel_block.length;
1907 }
1908 }
1909 else
1910 {
1911 TRACE("Incorrect media type, not writing sector subchannel block");
1912 return; // Incorrect media type
1913 }
1914
1915 // Calculate CRC64 for raw subchannel data and compressed payload when present
1916 subchannel_block.crc64 = aaruf_crc64_data(ctx->sector_subchannel, subchannel_block.length);
1917 if(subchannel_block.compression == kCompressionNone)
1918 subchannel_block.cmpCrc64 = subchannel_block.crc64;
1919 else
1920 {
1921 subchannel_block.cmpCrc64 = aaruf_crc64_data(buffer, subchannel_block.cmpLength);
1922 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1923 }
1924
1925 const size_t length_to_write = subchannel_block.cmpLength;
1926 const bool has_lzma_props = subchannel_block.compression == kCompressionLzma ||
1927 subchannel_block.compression == kCompressionLzmaCst;
1928 if(has_lzma_props) subchannel_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1929
1930 // Write header
1931 if(fwrite(&subchannel_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1932 {
1933 if(has_lzma_props)
1934 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1935
1936 // Write data
1937 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1938 if(written_bytes == 1)
1939 {
1940 TRACE("Successfully wrote sector subchannel block (%" PRIu64 " bytes)", subchannel_block.cmpLength);
1941
1942 // Remove any previously existing index entries of the same type before adding
1943 TRACE("Removing any previously existing subchannel block index entries");
1944 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1945 {
1946 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1947 if(entry && entry->blockType == DataBlock && entry->dataType == subchannel_block.type)
1948 {
1949 TRACE("Found existing subchannel block index entry at position %d, removing", k);
1950 utarray_erase(ctx->index_entries, k, 1);
1951 }
1952 }
1953
1954 // Add subchannel block to index
1955 TRACE("Adding sector subchannel block to index");
1956 IndexEntry subchannel_index_entry;
1957 subchannel_index_entry.blockType = DataBlock;
1958 subchannel_index_entry.dataType = subchannel_block.type;
1959 subchannel_index_entry.offset = block_position;
1960 utarray_push_back(ctx->index_entries, &subchannel_index_entry);
1961 ctx->dirty_index_block = true;
1962 TRACE("Added sector subchannel block index entry at offset %" PRIu64, block_position);
1963 }
1964 }
1965
1966 if(owns_buffer) free(buffer);
1967}
1968
2106{
2107 if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL || ctx->sector_edc == NULL)
2108 return;
2109
2110 uint64_t total_sectors =
2112
2113 // Write DVD sector ID block
2114 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2115 aaru_off_t id_position = aaruf_ftell(ctx->imageStream);
2116 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2117 if(id_position & alignment_mask)
2118 {
2119 const uint64_t aligned_position = id_position + alignment_mask & ~alignment_mask;
2120 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2121 id_position = aligned_position;
2122 }
2123 TRACE("Writing DVD sector ID block at position %ld", id_position);
2124 BlockHeader id_block = {0};
2125 id_block.identifier = DataBlock;
2126 id_block.type = kDataTypeDvdSectorId;
2127 id_block.compression =
2129 id_block.length = (uint32_t)total_sectors * 4;
2130
2131 // Calculate CRC64
2132 id_block.crc64 = aaruf_crc64_data(ctx->sector_id, id_block.length);
2133
2134 uint8_t *buffer = NULL;
2135 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2136
2137 if(id_block.compression == kCompressionNone)
2138 {
2139 buffer = ctx->sector_id;
2140 id_block.cmpCrc64 = id_block.crc64;
2141 }
2142 else
2143 {
2144 buffer = malloc((size_t)id_block.length * 2); // Allocate double size for compression
2145 if(buffer == NULL)
2146 {
2147 TRACE("Failed to allocate memory for DVD sector ID compression");
2148 return;
2149 }
2150
2151 size_t dst_size;
2152
2153 if(ctx->use_zstd)
2154 {
2155 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)id_block.length * 2, ctx->sector_id, id_block.length,
2156 ctx->zstd_level, ctx->num_threads);
2157 if(dst_size == 0) dst_size = id_block.length;
2158 }
2159 else
2160 {
2161 dst_size = (size_t)id_block.length * 2 * 2;
2162 size_t props_size = LZMA_PROPERTIES_LENGTH;
2163 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_id, id_block.length, lzma_properties, &props_size,
2164 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2165 }
2166
2167 id_block.cmpLength = (uint32_t)dst_size;
2168
2169 if(id_block.cmpLength >= id_block.length)
2170 {
2171 id_block.compression = kCompressionNone;
2172 free(buffer);
2173 buffer = ctx->sector_id;
2174 }
2175 }
2176
2177 if(id_block.compression == kCompressionNone)
2178 {
2179 id_block.cmpLength = id_block.length;
2180 id_block.cmpCrc64 = id_block.crc64;
2181 }
2182 else
2183 {
2184 id_block.cmpCrc64 = aaruf_crc64_data(buffer, id_block.cmpLength);
2185 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2186 }
2187
2188 size_t length_to_write = id_block.cmpLength;
2190
2191 // Write header
2192 if(fwrite(&id_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2193 {
2194 if(id_block.compression == kCompressionLzma)
2195 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2196
2197 // Write data
2198 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2199 if(written_bytes == 1)
2200 {
2201 TRACE("Successfully wrote DVD sector ID block (%" PRIu64 " bytes)", id_block.cmpLength);
2202
2203 // Remove any previously existing index entries of the same type before adding
2204 TRACE("Removing any previously existing DVD sector ID block index entries");
2205 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2206 {
2207 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2208 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorId)
2209 {
2210 TRACE("Found existing DVD sector ID block index entry at position %d, removing", k);
2211 utarray_erase(ctx->index_entries, k, 1);
2212 }
2213 }
2214
2215 // Add ID block to index
2216 TRACE("Adding DVD sector ID block to index");
2217 IndexEntry id_index_entry;
2218 id_index_entry.blockType = DataBlock;
2219 id_index_entry.dataType = kDataTypeDvdSectorId;
2220 id_index_entry.offset = id_position;
2221 utarray_push_back(ctx->index_entries, &id_index_entry);
2222 ctx->dirty_index_block = true;
2223 TRACE("Added DVD sector ID block index entry at offset %" PRIu64, id_position);
2224 }
2225 }
2226
2227 if(id_block.compression != kCompressionNone) free(buffer);
2228
2229 // Write DVD sector IED block
2230 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2231 aaru_off_t ied_position = aaruf_ftell(ctx->imageStream);
2232 if(ied_position & alignment_mask)
2233 {
2234 const uint64_t aligned_position = ied_position + alignment_mask & ~alignment_mask;
2235 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2236 ied_position = aligned_position;
2237 }
2238 TRACE("Writing DVD sector IED block at position %ld", ied_position);
2239 BlockHeader ied_block = {0};
2240 ied_block.identifier = DataBlock;
2241 ied_block.type = kDataTypeDvdSectorIed;
2242 ied_block.compression =
2244 ied_block.length = (uint32_t)total_sectors * 2;
2245 // Calculate CRC64
2246 ied_block.crc64 = aaruf_crc64_data(ctx->sector_ied, ied_block.length);
2247
2248 buffer = NULL;
2249
2250 if(ied_block.compression == kCompressionNone)
2251 {
2252 buffer = ctx->sector_ied;
2253 ied_block.cmpCrc64 = ied_block.crc64;
2254 }
2255 else
2256 {
2257 buffer = malloc((size_t)ied_block.length * 2); // Allocate double size for compression
2258 if(buffer == NULL)
2259 {
2260 TRACE("Failed to allocate memory for DVD sector IED compression");
2261 return;
2262 }
2263
2264 size_t dst_size;
2265
2266 if(ctx->use_zstd)
2267 {
2268 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ied_block.length * 2, ctx->sector_ied,
2269 ied_block.length, ctx->zstd_level, ctx->num_threads);
2270 if(dst_size == 0) dst_size = ied_block.length;
2271 }
2272 else
2273 {
2274 dst_size = (size_t)ied_block.length * 2 * 2;
2275 size_t props_size = LZMA_PROPERTIES_LENGTH;
2276 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_ied, ied_block.length, lzma_properties, &props_size,
2277 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2278 }
2279
2280 ied_block.cmpLength = (uint32_t)dst_size;
2281
2282 if(ied_block.cmpLength >= ied_block.length)
2283 {
2284 ied_block.compression = kCompressionNone;
2285 free(buffer);
2286 buffer = ctx->sector_ied;
2287 }
2288 }
2289
2290 if(ied_block.compression == kCompressionNone)
2291 {
2292 ied_block.cmpLength = ied_block.length;
2293 ied_block.cmpCrc64 = ied_block.crc64;
2294 }
2295 else
2296 {
2297 ied_block.cmpCrc64 = aaruf_crc64_data(buffer, ied_block.cmpLength);
2298 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2299 }
2300
2301 length_to_write = ied_block.cmpLength;
2302 if(ied_block.compression == kCompressionLzma) ied_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2303
2304 // Write header
2305 if(fwrite(&ied_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2306 {
2307 if(ied_block.compression == kCompressionLzma)
2308 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2309
2310 // Write data
2311 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2312 if(written_bytes == 1)
2313 {
2314 TRACE("Successfully wrote DVD sector IED block (%" PRIu64 " bytes)", ied_block.cmpLength);
2315
2316 // Remove any previously existing index entries of the same type before adding
2317 TRACE("Removing any previously existing DVD sector IED block index entries");
2318 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2319 {
2320 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2321 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorIed)
2322 {
2323 TRACE("Found existing DVD sector IED block index entry at position %d, removing", k);
2324 utarray_erase(ctx->index_entries, k, 1);
2325 }
2326 }
2327
2328 // Add IED block to index
2329 TRACE("Adding DVD sector IED block to index");
2330 IndexEntry ied_index_entry;
2331 ied_index_entry.blockType = DataBlock;
2332 ied_index_entry.dataType = kDataTypeDvdSectorIed;
2333 ied_index_entry.offset = ied_position;
2334 utarray_push_back(ctx->index_entries, &ied_index_entry);
2335 ctx->dirty_index_block = true;
2336 TRACE("Added DVD sector IED block index entry at offset %" PRIu64, ied_position);
2337 }
2338 }
2339
2340 if(ied_block.compression != kCompressionNone) free(buffer);
2341
2342 // Write DVD sector CPR/MAI block
2343 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2344 aaru_off_t cpr_mai_position = aaruf_ftell(ctx->imageStream);
2345 if(cpr_mai_position & alignment_mask)
2346 {
2347 const uint64_t aligned_position = cpr_mai_position + alignment_mask & ~alignment_mask;
2348 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2349 cpr_mai_position = aligned_position;
2350 }
2351 TRACE("Writing DVD sector CPR/MAI block at position %ld", cpr_mai_position);
2352 BlockHeader cpr_mai_block = {0};
2353 cpr_mai_block.identifier = DataBlock;
2354 cpr_mai_block.type = kDataTypeDvdSectorCprMai;
2355 cpr_mai_block.compression =
2357 cpr_mai_block.length = (uint32_t)total_sectors * 6;
2358 // Calculate CRC64
2359 cpr_mai_block.crc64 = aaruf_crc64_data(ctx->sector_cpr_mai, cpr_mai_block.length);
2360
2361 buffer = NULL;
2362
2363 if(cpr_mai_block.compression == kCompressionNone)
2364 {
2365 buffer = ctx->sector_cpr_mai;
2366 cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
2367 }
2368 else
2369 {
2370 buffer = malloc((size_t)cpr_mai_block.length * 2); // Allocate double size for compression
2371 if(buffer == NULL)
2372 {
2373 TRACE("Failed to allocate memory for DVD sector CPR/MAI compression");
2374 return;
2375 }
2376
2377 size_t dst_size;
2378
2379 if(ctx->use_zstd)
2380 {
2381 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)cpr_mai_block.length * 2, ctx->sector_cpr_mai,
2382 cpr_mai_block.length, ctx->zstd_level, ctx->num_threads);
2383 if(dst_size == 0) dst_size = cpr_mai_block.length;
2384 }
2385 else
2386 {
2387 dst_size = (size_t)cpr_mai_block.length * 2 * 2;
2388 size_t props_size = LZMA_PROPERTIES_LENGTH;
2389 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_cpr_mai, cpr_mai_block.length, lzma_properties,
2390 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2391 }
2392
2393 cpr_mai_block.cmpLength = (uint32_t)dst_size;
2394
2395 if(cpr_mai_block.cmpLength >= cpr_mai_block.length)
2396 {
2397 cpr_mai_block.compression = kCompressionNone;
2398 free(buffer);
2399 buffer = ctx->sector_cpr_mai;
2400 }
2401 }
2402
2403 if(cpr_mai_block.compression == kCompressionNone)
2404 {
2405 cpr_mai_block.cmpLength = cpr_mai_block.length;
2406 cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
2407 }
2408 else
2409 {
2410 cpr_mai_block.cmpCrc64 = aaruf_crc64_data(buffer, cpr_mai_block.cmpLength);
2411 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2412 }
2413
2414 length_to_write = cpr_mai_block.cmpLength;
2415 if(cpr_mai_block.compression == kCompressionLzma) cpr_mai_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2416
2417 // Write header
2418 if(fwrite(&cpr_mai_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2419 {
2420 if(cpr_mai_block.compression == kCompressionLzma)
2421 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2422
2423 // Write data
2424 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2425 if(written_bytes == 1)
2426 {
2427 TRACE("Successfully wrote DVD sector CPR/MAI block (%" PRIu64 " bytes)", cpr_mai_block.cmpLength);
2428
2429 // Remove any previously existing index entries of the same type before adding
2430 TRACE("Removing any previously existing DVD sector CPR/MAI block index entries");
2431 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2432 {
2433 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2434 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorCprMai)
2435 {
2436 TRACE("Found existing DVD sector CPR/MAI block index entry at position %d, removing", k);
2437 utarray_erase(ctx->index_entries, k, 1);
2438 }
2439 }
2440
2441 // Add CPR/MAI block to index
2442 TRACE("Adding DVD sector CPR/MAI block to index");
2443 IndexEntry cpr_mai_index_entry;
2444 cpr_mai_index_entry.blockType = DataBlock;
2445 cpr_mai_index_entry.dataType = kDataTypeDvdSectorCprMai;
2446 cpr_mai_index_entry.offset = cpr_mai_position;
2447 utarray_push_back(ctx->index_entries, &cpr_mai_index_entry);
2448 ctx->dirty_index_block = true;
2449 TRACE("Added DVD sector CPR/MAI block index entry at offset %" PRIu64, cpr_mai_position);
2450 }
2451 }
2452
2453 if(cpr_mai_block.compression != kCompressionNone) free(buffer);
2454
2455 // Write DVD sector EDC block
2456 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2457 aaru_off_t edc_position = aaruf_ftell(ctx->imageStream);
2458 if(edc_position & alignment_mask)
2459 {
2460 const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
2461 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2462 edc_position = aligned_position;
2463 }
2464 TRACE("Writing DVD sector EDC block at position %ld", edc_position);
2465 BlockHeader edc_block = {0};
2466 edc_block.identifier = DataBlock;
2467 edc_block.type = kDataTypeDvdSectorEdc;
2468 edc_block.compression =
2470 edc_block.length = (uint32_t)total_sectors * 4;
2471 // Calculate CRC64
2472 edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
2473
2474 buffer = NULL;
2475
2476 if(edc_block.compression == kCompressionNone)
2477 {
2478 buffer = ctx->sector_edc;
2479 edc_block.cmpCrc64 = edc_block.crc64;
2480 }
2481 else
2482 {
2483 buffer = malloc((size_t)edc_block.length * 2); // Allocate double size for compression
2484 if(buffer == NULL)
2485 {
2486 TRACE("Failed to allocate memory for DVD sector EDC compression");
2487 return;
2488 }
2489
2490 size_t dst_size;
2491
2492 if(ctx->use_zstd)
2493 {
2494 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)edc_block.length * 2, ctx->sector_edc,
2495 edc_block.length, ctx->zstd_level, ctx->num_threads);
2496 if(dst_size == 0) dst_size = edc_block.length;
2497 }
2498 else
2499 {
2500 dst_size = (size_t)edc_block.length * 2 * 2;
2501 size_t props_size = LZMA_PROPERTIES_LENGTH;
2502 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_edc, edc_block.length, lzma_properties,
2503 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2504 }
2505
2506 edc_block.cmpLength = (uint32_t)dst_size;
2507
2508 if(edc_block.cmpLength >= edc_block.length)
2509 {
2510 edc_block.compression = kCompressionNone;
2511 free(buffer);
2512 buffer = ctx->sector_edc;
2513 }
2514 }
2515
2516 if(edc_block.compression == kCompressionNone)
2517 {
2518 edc_block.cmpLength = edc_block.length;
2519 edc_block.cmpCrc64 = edc_block.crc64;
2520 }
2521 else
2522 {
2523 edc_block.cmpCrc64 = aaruf_crc64_data(buffer, edc_block.cmpLength);
2524 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2525 }
2526
2527 length_to_write = edc_block.cmpLength;
2528 if(edc_block.compression == kCompressionLzma) edc_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2529
2530 // Write header
2531 if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2532 {
2533 if(edc_block.compression == kCompressionLzma)
2534 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2535
2536 // Write data
2537 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2538 if(written_bytes == 1)
2539 {
2540 TRACE("Successfully wrote DVD sector EDC block (%" PRIu64 " bytes)", edc_block.cmpLength);
2541
2542 // Remove any previously existing index entries of the same type before adding
2543 TRACE("Removing any previously existing DVD sector EDC block index entries");
2544 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2545 {
2546 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2547 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorEdc)
2548 {
2549 TRACE("Found existing DVD sector EDC block index entry at position %d, removing", k);
2550 utarray_erase(ctx->index_entries, k, 1);
2551 }
2552 }
2553
2554 // Add EDC block to index
2555 TRACE("Adding DVD sector EDC block to index");
2556 IndexEntry edc_index_entry;
2557 edc_index_entry.blockType = DataBlock;
2558 edc_index_entry.dataType = kDataTypeDvdSectorEdc;
2559 edc_index_entry.offset = edc_position;
2560 utarray_push_back(ctx->index_entries, &edc_index_entry);
2561 ctx->dirty_index_block = true;
2562 TRACE("Added DVD sector EDC block index entry at offset %" PRIu64, edc_position);
2563 }
2564 }
2565
2566 if(edc_block.compression != kCompressionNone) free(buffer);
2567}
2568
2667{
2668 if(ctx->sector_decrypted_title_key == NULL) return;
2669
2670 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2671 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
2672 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2673 if(block_position & alignment_mask)
2674 {
2675 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
2676 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2677 block_position = aligned_position;
2678 }
2679 TRACE("Writing DVD decrypted title key block at position %ld", block_position);
2680 BlockHeader decrypted_title_key_block = {0};
2681 decrypted_title_key_block.identifier = DataBlock;
2682 decrypted_title_key_block.type = kDataTypeDvdTitleKeyDecrypted;
2683 decrypted_title_key_block.compression =
2685 decrypted_title_key_block.length =
2687 5;
2688 // Calculate CRC64
2689 decrypted_title_key_block.crc64 =
2690 aaruf_crc64_data(ctx->sector_decrypted_title_key, decrypted_title_key_block.length);
2691
2692 uint8_t *buffer = NULL;
2693 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2694
2695 if(decrypted_title_key_block.compression == kCompressionNone)
2696 {
2697 buffer = ctx->sector_decrypted_title_key;
2698 decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
2699 }
2700 else
2701 {
2702 buffer = malloc((size_t)decrypted_title_key_block.length * 2); // Allocate double size for compression
2703 if(buffer == NULL)
2704 {
2705 TRACE("Failed to allocate memory for DVD decrypted title key compression");
2706 return;
2707 }
2708
2709 size_t dst_size;
2710
2711 if(ctx->use_zstd)
2712 {
2713 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)decrypted_title_key_block.length * 2,
2714 ctx->sector_decrypted_title_key, decrypted_title_key_block.length,
2715 ctx->zstd_level, ctx->num_threads);
2716 if(dst_size == 0) dst_size = decrypted_title_key_block.length;
2717 }
2718 else
2719 {
2720 dst_size = (size_t)decrypted_title_key_block.length * 2 * 2;
2721 size_t props_size = LZMA_PROPERTIES_LENGTH;
2723 decrypted_title_key_block.length, lzma_properties, &props_size, 9,
2724 ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2725 }
2726
2727 decrypted_title_key_block.cmpLength = (uint32_t)dst_size;
2728
2729 if(decrypted_title_key_block.cmpLength >= decrypted_title_key_block.length)
2730 {
2731 decrypted_title_key_block.compression = kCompressionNone;
2732 free(buffer);
2733 buffer = ctx->sector_decrypted_title_key;
2734 }
2735 }
2736
2737 if(decrypted_title_key_block.compression == kCompressionNone)
2738 {
2739 decrypted_title_key_block.cmpLength = decrypted_title_key_block.length;
2740 decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
2741 }
2742 else
2743 {
2744 decrypted_title_key_block.cmpCrc64 = aaruf_crc64_data(buffer, decrypted_title_key_block.cmpLength);
2745 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2746 }
2747
2748 const size_t length_to_write = decrypted_title_key_block.cmpLength;
2749 if(decrypted_title_key_block.compression == kCompressionLzma)
2750 decrypted_title_key_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2751
2752 // Write header
2753 if(fwrite(&decrypted_title_key_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2754 {
2755 if(decrypted_title_key_block.compression == kCompressionLzma)
2756 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2757
2758 // Write data
2759 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2760 if(written_bytes == 1)
2761 {
2762 TRACE("Successfully wrote DVD decrypted title key block (%" PRIu64 " bytes)",
2763 decrypted_title_key_block.cmpLength);
2764
2765 // Remove any previously existing index entries of the same type before adding
2766 TRACE("Removing any previously existing DVD decrypted title key block index entries");
2767 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2768 {
2769 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2770 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdTitleKeyDecrypted)
2771 {
2772 TRACE("Found existing DVD decrypted title key block index entry at position %d, removing", k);
2773 utarray_erase(ctx->index_entries, k, 1);
2774 }
2775 }
2776
2777 // Add decrypted title key block to index
2778 TRACE("Adding DVD decrypted title key block to index");
2779 IndexEntry decrypted_title_key_index_entry;
2780 decrypted_title_key_index_entry.blockType = DataBlock;
2781 decrypted_title_key_index_entry.dataType = kDataTypeDvdTitleKeyDecrypted;
2782 decrypted_title_key_index_entry.offset = block_position;
2783 utarray_push_back(ctx->index_entries, &decrypted_title_key_index_entry);
2784 ctx->dirty_index_block = true;
2785 TRACE("Added DVD decrypted title key block index entry at offset %" PRIu64, block_position);
2786 }
2787 }
2788
2789 if(decrypted_title_key_block.compression != kCompressionNone) free(buffer);
2790}
2791
2864{
2865 if(ctx->mediaTags == NULL) return;
2866
2867 mediaTagEntry *media_tag = NULL;
2868 mediaTagEntry *tmp_media_tag = NULL;
2869
2870 HASH_ITER(hh, ctx->mediaTags, media_tag, tmp_media_tag)
2871 {
2872 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2873 aaru_off_t tag_position = aaruf_ftell(ctx->imageStream);
2874 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2875 if(tag_position & alignment_mask)
2876 {
2877 const uint64_t aligned_position = tag_position + alignment_mask & ~alignment_mask;
2878 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2879 tag_position = aligned_position;
2880 }
2881
2882 TRACE("Writing media tag block type %d at position %ld", aaruf_get_datatype_for_media_tag_type(media_tag->type),
2883 tag_position);
2884 BlockHeader tag_block = {0};
2885 tag_block.identifier = DataBlock;
2886 tag_block.type = (uint16_t)aaruf_get_datatype_for_media_tag_type(media_tag->type);
2887 tag_block.compression =
2889 tag_block.length = media_tag->length;
2890
2891 // Calculate CRC64
2892 tag_block.crc64 = aaruf_crc64_data(media_tag->data, tag_block.length);
2893
2894 uint8_t *buffer = NULL;
2895 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2896
2897 if(tag_block.compression == kCompressionNone)
2898 {
2899 buffer = media_tag->data;
2900 tag_block.cmpCrc64 = tag_block.crc64;
2901 }
2902 else
2903 {
2904 buffer = malloc((size_t)tag_block.length * 2); // Allocate double size for compression
2905 if(buffer == NULL)
2906 {
2907 TRACE("Failed to allocate memory for media tag compression");
2908 return;
2909 }
2910
2911 size_t dst_size;
2912
2913 if(ctx->use_zstd)
2914 {
2915 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)tag_block.length * 2, media_tag->data,
2916 tag_block.length, ctx->zstd_level, ctx->num_threads);
2917 if(dst_size == 0) dst_size = tag_block.length;
2918 }
2919 else
2920 {
2921 dst_size = (size_t)tag_block.length * 2 * 2;
2922 size_t props_size = LZMA_PROPERTIES_LENGTH;
2923 aaruf_lzma_encode_buffer(buffer, &dst_size, media_tag->data, tag_block.length, lzma_properties,
2924 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2925 }
2926
2927 tag_block.cmpLength = (uint32_t)dst_size;
2928
2929 if(tag_block.cmpLength >= tag_block.length)
2930 {
2931 tag_block.compression = kCompressionNone;
2932 free(buffer);
2933 buffer = media_tag->data;
2934 }
2935 }
2936
2937 if(tag_block.compression == kCompressionNone)
2938 {
2939 tag_block.cmpLength = tag_block.length;
2940 tag_block.cmpCrc64 = tag_block.crc64;
2941 }
2942 else
2943 {
2944 tag_block.cmpCrc64 = aaruf_crc64_data(buffer, tag_block.cmpLength);
2945 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2946 }
2947
2948 const size_t length_to_write = tag_block.cmpLength;
2949 if(tag_block.compression == kCompressionLzma) tag_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2950
2951 // Write header
2952 if(fwrite(&tag_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2953 {
2954 if(tag_block.compression == kCompressionLzma)
2955 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream); // Write data
2956
2957 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2958 if(written_bytes == 1)
2959 {
2960 TRACE("Successfully wrote media tag block type %d (%" PRIu64 " bytes)", tag_block.type,
2961 tag_block.cmpLength);
2962
2963 // Remove any previously existing index entries of the same type before adding
2964 TRACE("Removing any previously existing media tag type %d block index entries", tag_block.type);
2965 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2966 {
2967 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2968 if(entry && entry->blockType == DataBlock && entry->dataType == tag_block.type)
2969 {
2970 TRACE("Found existing media tag type %d block index entry at position %d, removing",
2971 tag_block.type, k);
2972 utarray_erase(ctx->index_entries, k, 1);
2973 }
2974 }
2975
2976 // Add media tag block to index
2977 TRACE("Adding media tag type %d block to index", tag_block.type);
2978 IndexEntry tag_index_entry;
2979 tag_index_entry.blockType = DataBlock;
2980 tag_index_entry.dataType = tag_block.type;
2981 tag_index_entry.offset = tag_position;
2982 utarray_push_back(ctx->index_entries, &tag_index_entry);
2983 ctx->dirty_index_block = true;
2984 TRACE("Added media tag block type %d index entry at offset %" PRIu64, tag_block.type, tag_position);
2985 }
2986 }
2987
2988 if(tag_block.compression != kCompressionNone) free(buffer);
2989 }
2990}
2991
3155{
3156 if(ctx->tape_files == NULL) return;
3157
3158 // Iterate the uthash and count how many entries do we have
3159 const tapeFileHashEntry *tape_file = NULL;
3160 const tapeFileHashEntry *tmp_tape_file = NULL;
3161 size_t tape_file_count = 0;
3162 HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file) tape_file_count++;
3163
3164 // Create a memory buffer to copy all the file entries
3165 const size_t buffer_size = tape_file_count * sizeof(TapeFileEntry);
3166 TapeFileEntry *buffer = malloc(buffer_size);
3167 if(buffer == NULL)
3168 {
3169 TRACE("Failed to allocate memory for tape file entries");
3170 return;
3171 }
3172 memset(buffer, 0, buffer_size);
3173 size_t index = 0;
3174 HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file)
3175 {
3176 if(index >= tape_file_count) break;
3177 memcpy(&buffer[index], &tape_file->fileEntry, sizeof(TapeFileEntry));
3178 index++;
3179 }
3180
3181 // Create the tape file block in memory
3182 TapeFileHeader tape_file_block = {0};
3183 tape_file_block.identifier = TapeFileBlock;
3184 tape_file_block.length = (uint32_t)buffer_size;
3185 tape_file_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_file_block.length);
3186
3187 // Write tape file block to file, block aligned
3188 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3189 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3190 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3191 if(block_position & alignment_mask)
3192 {
3193 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3194 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3195 block_position = aligned_position;
3196 }
3197 TRACE("Writing tape file block at position %ld", block_position);
3198 if(fwrite(&tape_file_block, sizeof(TapeFileHeader), 1, ctx->imageStream) == 1)
3199 {
3200 const size_t written_bytes = fwrite(buffer, tape_file_block.length, 1, ctx->imageStream);
3201 if(written_bytes == 1)
3202 {
3203 TRACE("Successfully wrote tape file block (%" PRIu64 " bytes)", tape_file_block.length);
3204
3205 // Remove any previously existing index entries of the same type before adding
3206 TRACE("Removing any previously existing tape file block index entries");
3207 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3208 {
3209 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3210 if(entry && entry->blockType == TapeFileBlock && entry->dataType == 0)
3211 {
3212 TRACE("Found existing tape file block index entry at position %d, removing", k);
3213 utarray_erase(ctx->index_entries, k, 1);
3214 }
3215 }
3216
3217 // Add tape file block to index
3218 TRACE("Adding tape file block to index");
3219 IndexEntry index_entry;
3220 index_entry.blockType = TapeFileBlock;
3221 index_entry.dataType = 0;
3222 index_entry.offset = block_position;
3223 utarray_push_back(ctx->index_entries, &index_entry);
3224 ctx->dirty_index_block = true;
3225 TRACE("Added tape file block index entry at offset %" PRIu64, block_position);
3226 }
3227 }
3228
3229 free(buffer);
3230}
3231
3401{
3402 if(ctx->tape_partitions == NULL) return;
3403
3404 // Iterate the uthash and count how many entries do we have
3405 const TapePartitionHashEntry *tape_partition = NULL;
3406 const TapePartitionHashEntry *tmp_tape_partition = NULL;
3407 size_t tape_partition_count = 0;
3408 HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition) tape_partition_count++;
3409
3410 // Create a memory buffer to copy all the partition entries
3411 const size_t buffer_size = tape_partition_count * sizeof(TapePartitionEntry);
3412 TapePartitionEntry *buffer = malloc(buffer_size);
3413 if(buffer == NULL)
3414 {
3415 TRACE("Failed to allocate memory for tape partition entries");
3416 return;
3417 }
3418 memset(buffer, 0, buffer_size);
3419 size_t index = 0;
3420 HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition)
3421 {
3422 if(index >= tape_partition_count) break;
3423 memcpy(&buffer[index], &tape_partition->partitionEntry, sizeof(TapePartitionEntry));
3424 index++;
3425 }
3426
3427 // Create the tape partition block in memory
3428 TapePartitionHeader tape_partition_block = {0};
3429 tape_partition_block.identifier = TapePartitionBlock;
3430 tape_partition_block.length = (uint32_t)buffer_size;
3431 tape_partition_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_partition_block.length);
3432
3433 // Write tape partition block to partition, block aligned
3434 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3435 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3436 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3437 if(block_position & alignment_mask)
3438 {
3439 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3440 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3441 block_position = aligned_position;
3442 }
3443 TRACE("Writing tape partition block at position %ld", block_position);
3444 if(fwrite(&tape_partition_block, sizeof(TapePartitionHeader), 1, ctx->imageStream) == 1)
3445 {
3446 const size_t written_bytes = fwrite(buffer, tape_partition_block.length, 1, ctx->imageStream);
3447 if(written_bytes == 1)
3448 {
3449 TRACE("Successfully wrote tape partition block (%" PRIu64 " bytes)", tape_partition_block.length);
3450
3451 // Remove any previously existing index entries of the same type before adding
3452 TRACE("Removing any previously existing tape partition block index entries");
3453 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3454 {
3455 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3456 if(entry && entry->blockType == TapePartitionBlock && entry->dataType == 0)
3457 {
3458 TRACE("Found existing tape partition block index entry at position %d, removing", k);
3459 utarray_erase(ctx->index_entries, k, 1);
3460 }
3461 }
3462
3463 // Add tape partition block to index
3464 TRACE("Adding tape partition block to index");
3465 IndexEntry index_entry;
3466 index_entry.blockType = TapePartitionBlock;
3467 index_entry.dataType = 0;
3468 index_entry.offset = block_position;
3469 utarray_push_back(ctx->index_entries, &index_entry);
3470 ctx->dirty_index_block = true;
3471 TRACE("Added tape partition block index entry at offset %" PRIu64, block_position);
3472 }
3473 }
3474
3475 free(buffer);
3476}
3477
3540{
3541 if(ctx->geometry_block.identifier != GeometryBlock) return;
3542
3543 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3544 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3545 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3546 if(block_position & alignment_mask)
3547 {
3548 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3549 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3550 block_position = aligned_position;
3551 }
3552
3553 TRACE("Writing geometry block at position %ld", block_position);
3554
3555 // Write header
3556 if(fwrite(&ctx->geometry_block, sizeof(GeometryBlockHeader), 1, ctx->imageStream) == 1)
3557 {
3558 TRACE("Successfully wrote geometry block");
3559
3560 // Remove any previously existing index entries of the same type before adding
3561 TRACE("Removing any previously existing geometry block index entries");
3562 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3563 {
3564 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3565 if(entry && entry->blockType == GeometryBlock && entry->dataType == 0)
3566 {
3567 TRACE("Found existing geometry block index entry at position %d, removing", k);
3568 utarray_erase(ctx->index_entries, k, 1);
3569 }
3570 }
3571
3572 // Add geometry block to index
3573 TRACE("Adding geometry block to index");
3574 IndexEntry index_entry;
3575 index_entry.blockType = GeometryBlock;
3576 index_entry.dataType = 0;
3577 index_entry.offset = block_position;
3578 utarray_push_back(ctx->index_entries, &index_entry);
3579 ctx->dirty_index_block = true;
3580 TRACE("Added geometry block index entry at offset %" PRIu64, block_position);
3581 }
3582}
3583
3689{
3691 ctx->metadata_block_header.lastMediaSequence == 0 && ctx->creator == NULL && ctx->comments == NULL &&
3692 ctx->media_title == NULL && ctx->media_manufacturer == NULL && ctx->media_model == NULL &&
3693 ctx->media_serial_number == NULL && ctx->media_barcode == NULL && ctx->media_part_number == NULL &&
3694 ctx->drive_manufacturer == NULL && ctx->drive_model == NULL && ctx->drive_serial_number == NULL &&
3695 ctx->drive_firmware_revision == NULL)
3696 return;
3697
3706
3708
3709 int pos = sizeof(MetadataBlockHeader);
3710
3711 uint8_t *buffer = calloc(1, ctx->metadata_block_header.blockSize);
3712 if(buffer == NULL) return;
3713
3714 if(ctx->creator != NULL && ctx->metadata_block_header.creatorLength > 0)
3715 {
3716 memcpy(buffer + pos, ctx->creator, ctx->metadata_block_header.creatorLength);
3719 }
3720
3721 if(ctx->comments != NULL && ctx->metadata_block_header.commentsLength > 0)
3722 {
3723 memcpy(buffer + pos, ctx->comments, ctx->metadata_block_header.commentsLength);
3726 }
3727
3728 if(ctx->media_title != NULL && ctx->metadata_block_header.mediaTitleLength > 0)
3729 {
3730 memcpy(buffer + pos, ctx->media_title, ctx->metadata_block_header.mediaTitleLength);
3733 }
3734
3736 {
3737 memcpy(buffer + pos, ctx->media_manufacturer, ctx->metadata_block_header.mediaManufacturerLength);
3740 }
3741
3742 if(ctx->media_model != NULL && ctx->metadata_block_header.mediaModelLength > 0)
3743 {
3744 memcpy(buffer + pos, ctx->media_model, ctx->metadata_block_header.mediaModelLength);
3747 }
3748
3750 {
3751 memcpy(buffer + pos, ctx->media_serial_number, ctx->metadata_block_header.mediaSerialNumberLength);
3754 }
3755
3756 if(ctx->media_barcode != NULL && ctx->metadata_block_header.mediaBarcodeLength > 0)
3757 {
3758 memcpy(buffer + pos, ctx->media_barcode, ctx->metadata_block_header.mediaBarcodeLength);
3761 }
3762
3764 {
3765 memcpy(buffer + pos, ctx->media_part_number, ctx->metadata_block_header.mediaPartNumberLength);
3768 }
3769
3771 {
3772 memcpy(buffer + pos, ctx->drive_manufacturer, ctx->metadata_block_header.driveManufacturerLength);
3775 }
3776
3777 if(ctx->drive_model != NULL && ctx->metadata_block_header.driveModelLength > 0)
3778 {
3779 memcpy(buffer + pos, ctx->drive_model, ctx->metadata_block_header.driveModelLength);
3782 }
3783
3785 {
3786 memcpy(buffer + pos, ctx->drive_serial_number, ctx->metadata_block_header.driveSerialNumberLength);
3789 }
3790
3792 {
3795 }
3796
3797 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3798 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3799 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3800 if(block_position & alignment_mask)
3801 {
3802 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3803 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3804 block_position = aligned_position;
3805 }
3806
3807 memcpy(buffer, &ctx->metadata_block_header, sizeof(MetadataBlockHeader));
3808
3809 TRACE("Writing metadata block at position %ld", block_position);
3810
3811 if(fwrite(buffer, ctx->metadata_block_header.blockSize, 1, ctx->imageStream) == 1)
3812 {
3813 TRACE("Successfully wrote metadata block");
3814
3815 // Remove any previously existing index entries of the same type before adding
3816 TRACE("Removing any previously existing metadata block index entries");
3817 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3818 {
3819 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3820 if(entry && entry->blockType == MetadataBlock && entry->dataType == 0)
3821 {
3822 TRACE("Found existing metadata block index entry at position %d, removing", k);
3823 utarray_erase(ctx->index_entries, k, 1);
3824 }
3825 }
3826
3827 // Add metadata block to index
3828 TRACE("Adding metadata block to index");
3829 IndexEntry index_entry;
3830 index_entry.blockType = MetadataBlock;
3831 index_entry.dataType = 0;
3832 index_entry.offset = block_position;
3833 utarray_push_back(ctx->index_entries, &index_entry);
3834 ctx->dirty_index_block = true;
3835 TRACE("Added metadata block index entry at offset %" PRIu64, block_position);
3836 }
3837
3838 free(buffer);
3839}
3840
3987{
3988
3989 if(ctx->dump_hardware_entries_with_data == NULL || ctx->dump_hardware_header.entries == 0 ||
3991 return;
3992
3993 const size_t required_length = sizeof(DumpHardwareHeader) + ctx->dump_hardware_header.length;
3994
3995 uint8_t *buffer = calloc(1, required_length);
3996
3997 if(buffer == NULL) return;
3998
3999 // Start to iterate and copy the data
4000 size_t offset = sizeof(DumpHardwareHeader);
4001 for(int i = 0; i < ctx->dump_hardware_header.entries; i++)
4002 {
4003 size_t entry_size = sizeof(DumpHardwareEntry) +
4013
4014 if(offset + entry_size > required_length)
4015 {
4016 FATAL("Calculated size exceeds provided buffer length");
4017 free(buffer);
4018 return;
4019 }
4020
4021 memcpy(buffer + offset, &ctx->dump_hardware_entries_with_data[i].entry, sizeof(DumpHardwareEntry));
4022 offset += sizeof(DumpHardwareEntry);
4025 {
4026 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].manufacturer,
4029 }
4031 ctx->dump_hardware_entries_with_data[i].model != NULL)
4032 {
4033 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].model,
4036 }
4039 {
4040 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].revision,
4043 }
4046 {
4047 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].firmware,
4050 }
4053 {
4054 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].serial,
4057 }
4060 {
4061 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareName,
4064 }
4067 {
4068 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareVersion,
4071 }
4074 {
4075 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem,
4078 }
4081 {
4082 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].extents,
4084 offset += ctx->dump_hardware_entries_with_data[i].entry.extents * sizeof(DumpExtent);
4085 }
4086 }
4087
4088 // Calculate CRC64
4091
4092 // Copy header
4093 memcpy(buffer, &ctx->dump_hardware_header, sizeof(DumpHardwareHeader));
4094
4095 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4096 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4097 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4098 if(block_position & alignment_mask)
4099 {
4100 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4101 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4102 block_position = aligned_position;
4103 }
4104 TRACE("Writing dump hardware block at position %ld", block_position);
4105 if(fwrite(buffer, required_length, 1, ctx->imageStream) == 1)
4106 {
4107 TRACE("Successfully wrote dump hardware block");
4108
4109 // Remove any previously existing index entries of the same type before adding
4110 TRACE("Removing any previously existing dump hardware block index entries");
4111 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4112 {
4113 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4114 if(entry && entry->blockType == DumpHardwareBlock && entry->dataType == 0)
4115 {
4116 TRACE("Found existing dump hardware block index entry at position %d, removing", k);
4117 utarray_erase(ctx->index_entries, k, 1);
4118 }
4119 }
4120
4121 // Add dump hardware block to index
4122 TRACE("Adding dump hardware block to index");
4123 IndexEntry index_entry;
4124 index_entry.blockType = DumpHardwareBlock;
4125 index_entry.dataType = 0;
4126 index_entry.offset = block_position;
4127 utarray_push_back(ctx->index_entries, &index_entry);
4128 ctx->dirty_index_block = true;
4129 TRACE("Added dump hardware block index entry at offset %" PRIu64, block_position);
4130 }
4131
4132 free(buffer);
4133}
4134
4230{
4231 if(ctx->cicm_block == NULL || ctx->cicm_block_header.length == 0 || ctx->cicm_block_header.identifier != CicmBlock)
4232 return;
4233
4234 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4235 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4236 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4237
4238 if(block_position & alignment_mask)
4239 {
4240 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4241 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4242 block_position = aligned_position;
4243 }
4244
4245 TRACE("Writing CICM XML block at position %ld", block_position);
4246 if(fwrite(&ctx->cicm_block_header, sizeof(CicmMetadataBlock), 1, ctx->imageStream) == 1 &&
4247 fwrite(ctx->cicm_block, ctx->cicm_block_header.length, 1, ctx->imageStream) == 1)
4248 {
4249 TRACE("Successfully wrote CICM XML block");
4250
4251 // Remove any previously existing index entries of the same type before adding
4252 TRACE("Removing any previously existing CICM XML block index entries");
4253 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4254 {
4255 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4256 if(entry && entry->blockType == CicmBlock && entry->dataType == 0)
4257 {
4258 TRACE("Found existing CICM XML block index entry at position %d, removing", k);
4259 utarray_erase(ctx->index_entries, k, 1);
4260 }
4261 }
4262
4263 // Add CICM block to index
4264 TRACE("Adding CICM XML block to index");
4265 IndexEntry index_entry;
4266 index_entry.blockType = CicmBlock;
4267 index_entry.dataType = 0;
4268 index_entry.offset = block_position;
4269 utarray_push_back(ctx->index_entries, &index_entry);
4270 ctx->dirty_index_block = true;
4271 TRACE("Added CICM XML block index entry at offset %" PRIu64, block_position);
4272 }
4273}
4274
4380{
4381 if(ctx->json_block == NULL || ctx->json_block_header.length == 0 ||
4383 return;
4384
4385 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4386 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4387 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4388
4389 if(block_position & alignment_mask)
4390 {
4391 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4392 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4393 block_position = aligned_position;
4394 }
4395
4396 TRACE("Writing Aaru metadata JSON block at position %ld", block_position);
4397 if(fwrite(&ctx->json_block_header, sizeof(AaruMetadataJsonBlockHeader), 1, ctx->imageStream) == 1 &&
4398 fwrite(ctx->json_block, ctx->json_block_header.length, 1, ctx->imageStream) == 1)
4399 {
4400 TRACE("Successfully wrote Aaru metadata JSON block");
4401
4402 // Remove any previously existing index entries of the same type before adding
4403 TRACE("Removing any previously existing Aaru metadata JSON block index entries");
4404 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4405 {
4406 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4407 if(entry && entry->blockType == AaruMetadataJsonBlock && entry->dataType == 0)
4408 {
4409 TRACE("Found existing Aaru metadata JSON block index entry at position %d, removing", k);
4410 utarray_erase(ctx->index_entries, k, 1);
4411 }
4412 }
4413
4414 // Add Aaru metadata JSON block to index
4415 TRACE("Adding Aaru metadata JSON block to index");
4416 IndexEntry index_entry;
4417 index_entry.blockType = AaruMetadataJsonBlock;
4418 index_entry.dataType = 0;
4419 index_entry.offset = block_position;
4420 utarray_push_back(ctx->index_entries, &index_entry);
4421 ctx->dirty_index_block = true;
4422 TRACE("Added Aaru metadata JSON block index entry at offset %" PRIu64, block_position);
4423 }
4424}
4425
4610{
4611 uint64_t data_length = record->data_length;
4612 uint64_t index_length = record->index_length;
4613 uint64_t raw_length = data_length + index_length;
4614
4615 if(raw_length > UINT32_MAX)
4616 {
4617 FATAL("Flux capture raw length exceeds 32-bit limit (%" PRIu64 ")", raw_length);
4619 }
4620
4621 uint8_t *raw_buffer = NULL;
4622 if(raw_length != 0)
4623 {
4624 raw_buffer = malloc(raw_length);
4625 if(raw_buffer == NULL)
4626 {
4627 FATAL("Could not allocate %" PRIu64 " bytes for flux serialization", raw_length);
4629 }
4630
4631 if(data_length != 0 && record->data_buffer != NULL) memcpy(raw_buffer, record->data_buffer, data_length);
4632 if(index_length != 0 && record->index_buffer != NULL)
4633 memcpy(raw_buffer + data_length, record->index_buffer, index_length);
4634 }
4635
4636 uint64_t raw_crc = raw_length != 0 && raw_buffer != NULL ? aaruf_crc64_data(raw_buffer, raw_length) : 0;
4637
4638 CompressionType compression =
4640
4641 uint8_t *compressed_buffer = NULL;
4642 uint32_t cmp_length = 0;
4643 uint64_t cmp_crc = 0;
4644
4645 if(compression == kCompressionLzma)
4646 {
4647 size_t cmp_capacity = raw_length ? raw_length * 2 + 65536 : LZMA_PROPERTIES_LENGTH + 16;
4648 if(cmp_capacity < raw_length + LZMA_PROPERTIES_LENGTH) cmp_capacity = raw_length + LZMA_PROPERTIES_LENGTH;
4649
4650 uint8_t *cmp_stream = malloc(cmp_capacity);
4651 if(cmp_stream == NULL)
4652 {
4653 free(raw_buffer);
4654 FATAL("Could not allocate %zu bytes for LZMA flux compression", cmp_capacity);
4656 }
4657
4658 size_t dst_size = cmp_capacity;
4659 uint8_t lzma_props[LZMA_PROPERTIES_LENGTH] = {0};
4660 size_t props_size = LZMA_PROPERTIES_LENGTH;
4661 int32_t error_no = aaruf_lzma_encode_buffer(cmp_stream, &dst_size, raw_buffer, raw_length, lzma_props,
4662 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
4663
4664 if(error_no != 0 || props_size != LZMA_PROPERTIES_LENGTH || dst_size >= raw_length)
4665 {
4666 TRACE("Flux capture compression fell back to uncompressed (err=%d, dst=%zu, raw=%" PRIu64 ")", error_no,
4667 dst_size, raw_length);
4668 compression = kCompressionNone;
4669 free(cmp_stream);
4670 }
4671 else
4672 {
4673 cmp_length = (uint32_t)(dst_size + LZMA_PROPERTIES_LENGTH);
4674 compressed_buffer = malloc(cmp_length);
4675 if(compressed_buffer == NULL)
4676 {
4677 free(cmp_stream);
4678 free(raw_buffer);
4679 FATAL("Could not allocate %u bytes for flux compressed payload", cmp_length);
4681 }
4682
4683 memcpy(compressed_buffer, lzma_props, LZMA_PROPERTIES_LENGTH);
4684 memcpy(compressed_buffer + LZMA_PROPERTIES_LENGTH, cmp_stream, dst_size);
4685 cmp_crc = aaruf_crc64_data(compressed_buffer, cmp_length);
4686 free(cmp_stream);
4687 free(raw_buffer);
4688 raw_buffer = NULL;
4689 }
4690 }
4691 else if(compression == kCompressionZstd)
4692 {
4693 size_t cmp_capacity = raw_length ? raw_length * 2 + 65536 : 16;
4694
4695 uint8_t *cmp_stream = malloc(cmp_capacity);
4696 if(cmp_stream == NULL)
4697 {
4698 free(raw_buffer);
4699 FATAL("Could not allocate %zu bytes for zstd flux compression", cmp_capacity);
4701 }
4702
4703 size_t dst_size =
4704 aaruf_zstd_encode_buffer(cmp_stream, cmp_capacity, raw_buffer, raw_length, ctx->zstd_level, ctx->num_threads);
4705
4706 if(dst_size == 0 || dst_size >= raw_length)
4707 {
4708 TRACE("Flux capture zstd compression fell back to uncompressed (dst=%zu, raw=%" PRIu64 ")", dst_size,
4709 raw_length);
4710 compression = kCompressionNone;
4711 free(cmp_stream);
4712 }
4713 else
4714 {
4715 cmp_length = (uint32_t)dst_size;
4716 compressed_buffer = cmp_stream;
4717 cmp_crc = aaruf_crc64_data(compressed_buffer, cmp_length);
4718 ctx->has_zstd_blocks = true;
4719 free(raw_buffer);
4720 raw_buffer = NULL;
4721 }
4722 }
4723
4724 if(compression == kCompressionNone)
4725 {
4726 cmp_length = (uint32_t)raw_length;
4727 cmp_crc = raw_crc;
4728 compressed_buffer = raw_buffer;
4729 raw_buffer = NULL;
4730 }
4731
4732 // Align stream position to block boundary
4733 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4734 aaru_off_t payload_position = aaruf_ftell(ctx->imageStream);
4735 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4736 if(payload_position & alignment_mask)
4737 {
4738 const uint64_t aligned_position = payload_position + alignment_mask & ~alignment_mask;
4739 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4740 payload_position = aligned_position;
4741 }
4742
4743 DataStreamPayloadHeader payload_header = {0};
4744 payload_header.identifier = DataStreamPayloadBlock;
4745 payload_header.dataType = kDataTypeFluxData;
4746 payload_header.compression = (uint16_t)compression;
4747 payload_header.cmpLength = cmp_length;
4748 payload_header.length = (uint32_t)raw_length;
4749 payload_header.cmpCrc64 = cmp_crc;
4750 payload_header.crc64 = raw_crc;
4751
4752 if(fwrite(&payload_header, sizeof(DataStreamPayloadHeader), 1, ctx->imageStream) != 1)
4753 {
4754 free(compressed_buffer);
4755 free(raw_buffer);
4756 FATAL("Could not write flux payload header");
4758 }
4759
4760 if(cmp_length != 0 && compressed_buffer != NULL && fwrite(compressed_buffer, cmp_length, 1, ctx->imageStream) != 1)
4761 {
4762 free(compressed_buffer);
4763 free(raw_buffer);
4764 FATAL("Could not write flux payload data");
4766 }
4767
4768 IndexEntry payload_entry;
4769 payload_entry.blockType = DataStreamPayloadBlock;
4770 payload_entry.dataType = kDataTypeFluxData;
4771 payload_entry.offset = payload_position;
4772 utarray_push_back(ctx->index_entries, &payload_entry);
4773
4774 entry->head = record->entry.head;
4775 entry->track = record->entry.track;
4776 entry->subtrack = record->entry.subtrack;
4777 entry->captureIndex = record->entry.captureIndex;
4778 entry->dataResolution = record->entry.dataResolution;
4779 entry->indexResolution = record->entry.indexResolution;
4780 entry->indexOffset = record->data_length;
4781 entry->payloadOffset = payload_position >> ctx->user_data_ddt_header.blockAlignmentShift;
4782
4783 record->entry = *entry;
4784
4785 free(compressed_buffer);
4786 free(raw_buffer);
4787
4788 return AARUF_STATUS_OK;
4789}
4790
5025{
5026 TRACE("Entering write_flux_blocks(%p)", ctx);
5027
5028 if(ctx == NULL || ctx->imageStream == NULL)
5029 {
5030 FATAL("Invalid context when writing flux blocks");
5032 }
5033
5034 if(ctx->flux_captures == NULL || utarray_len(ctx->flux_captures) == 0)
5035 {
5036 TRACE("No flux captures enqueued, skipping flux block serialization");
5037 return AARUF_STATUS_OK;
5038 }
5039
5040 size_t capture_count = utarray_len(ctx->flux_captures);
5041 if(capture_count > UINT16_MAX)
5042 {
5043 FATAL("Flux capture count exceeds header capacity (%zu > %u)", capture_count, UINT16_MAX);
5045 }
5046
5047 FluxEntry *previous_entries = ctx->flux_entries;
5048 ctx->flux_entries = NULL;
5049
5050 FluxEntry *entries = malloc(capture_count * sizeof(FluxEntry));
5051 if(entries == NULL)
5052 {
5053 ctx->flux_entries = previous_entries;
5054 FATAL("Could not allocate %zu bytes for flux entries", capture_count * sizeof(FluxEntry));
5056 }
5057
5058 size_t idx = 0;
5059 for(FluxCaptureRecord *record = (FluxCaptureRecord *)utarray_front(ctx->flux_captures); record != NULL;
5060 record = (FluxCaptureRecord *)utarray_next(ctx->flux_captures, record), ++idx)
5061 {
5062 int32_t res = write_flux_capture_payload(ctx, record, &entries[idx]);
5063 if(res != AARUF_STATUS_OK)
5064 {
5065 free(entries);
5066 ctx->flux_entries = previous_entries;
5067 return res;
5068 }
5069 }
5070
5071 FluxHeader header = {0};
5072 header.identifier = FluxDataBlock;
5073 header.entries = (uint16_t)capture_count;
5075 header.crc64 =
5076 capture_count == 0 ? 0 : aaruf_crc64_data((const uint8_t *)entries, capture_count * sizeof(FluxEntry));
5077
5078 // Align stream position to block boundary
5079 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
5080 aaru_off_t metadata_position = aaruf_ftell(ctx->imageStream);
5081 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
5082 if(metadata_position & alignment_mask)
5083 {
5084 const uint64_t aligned_position = metadata_position + alignment_mask & ~alignment_mask;
5085 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
5086 metadata_position = aligned_position;
5087 }
5088
5089 if(fwrite(&header, sizeof(FluxHeader), 1, ctx->imageStream) != 1)
5090 {
5091 free(entries);
5092 ctx->flux_entries = previous_entries;
5093 FATAL("Could not write flux metadata header");
5095 }
5096
5097 if(capture_count != 0)
5098 {
5099 size_t written_entries = fwrite(entries, sizeof(FluxEntry), capture_count, ctx->imageStream);
5100 if(written_entries != capture_count)
5101 {
5102 free(entries);
5103 ctx->flux_entries = previous_entries;
5104 FATAL("Could not write %zu flux entries (wrote %zu)", capture_count, written_entries);
5106 }
5107 }
5108
5109 IndexEntry metadata_entry;
5110 metadata_entry.blockType = FluxDataBlock;
5111 metadata_entry.dataType = 0;
5112 metadata_entry.offset = metadata_position;
5113 utarray_push_back(ctx->index_entries, &metadata_entry);
5114
5115 if(previous_entries != NULL) free(previous_entries);
5116 ctx->flux_entries = entries;
5117 ctx->flux_data_header = header;
5118
5119 utarray_free(ctx->flux_captures);
5120 ctx->flux_captures = NULL;
5121
5122 int32_t map_result = flux_map_rebuild_from_entries(ctx);
5123 if(map_result != AARUF_STATUS_OK) return map_result;
5124
5125 TRACE("Wrote %zu flux captures", capture_count);
5126 return AARUF_STATUS_OK;
5127}
5128
5146{
5147 // Write the complete index at the end of the file
5148 TRACE("Writing index at the end of the file");
5149 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
5150 aaru_off_t index_position = aaruf_ftell(ctx->imageStream);
5151
5152 // Align index position to block boundary if needed
5153 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
5154 if(index_position & alignment_mask)
5155 {
5156 uint64_t aligned_position = index_position + alignment_mask & ~alignment_mask;
5157 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
5158 index_position = aligned_position;
5159 TRACE("Aligned index position to %" PRIu64, aligned_position);
5160 }
5161
5162 // Prepare index header
5163 IndexHeader3 index_header;
5164 index_header.identifier = IndexBlock3;
5165 index_header.entries = utarray_len(ctx->index_entries);
5166 index_header.previous = 0; // No previous index for now
5167
5168 TRACE("Writing index with %" PRIu64 " entries at position %ld", index_header.entries, index_position);
5169
5170 // Calculate CRC64 of index entries
5171 crc64_ctx *index_crc64_context = aaruf_crc64_init();
5172 if(index_crc64_context != NULL && index_header.entries > 0)
5173 {
5174 size_t index_data_size = index_header.entries * sizeof(IndexEntry);
5175 aaruf_crc64_update(index_crc64_context, utarray_front(ctx->index_entries), index_data_size);
5176 aaruf_crc64_final(index_crc64_context, &index_header.crc64);
5177 TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64);
5178 }
5179 else
5180 index_header.crc64 = 0;
5181
5182 // Free CRC64 context
5183 aaruf_crc64_free(index_crc64_context);
5184
5185 // Write index header
5186 if(fwrite(&index_header, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
5187 {
5188 TRACE("Successfully wrote index header");
5189
5190 // Write index entries
5191 if(index_header.entries > 0)
5192 {
5193 size_t entries_written = 0;
5194 IndexEntry *entry = NULL;
5195
5196 for(entry = (IndexEntry *)utarray_front(ctx->index_entries); entry != NULL;
5197 entry = (IndexEntry *)utarray_next(ctx->index_entries, entry))
5198 if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1)
5199 {
5200 entries_written++;
5201 TRACE("Wrote index entry: blockType=0x%08X dataType=%u offset=%" PRIu64, entry->blockType,
5202 entry->dataType, entry->offset);
5203 }
5204 else
5205 {
5206 TRACE("Failed to write index entry %zu", entries_written);
5207 break;
5208 }
5209
5210 if(entries_written == index_header.entries)
5211 {
5212 TRACE("Successfully wrote all %zu index entries", entries_written);
5213
5214 // Update header with index offset and rewrite it
5215 ctx->header.indexOffset = index_position;
5216 TRACE("Updating header with index offset: %" PRIu64, ctx->header.indexOffset);
5217
5218 // Seek back to beginning and rewrite header
5219 aaruf_fseek(ctx->imageStream, 0, SEEK_SET);
5220 if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) == 1)
5221 TRACE("Successfully updated header with index offset");
5222 else
5223 {
5224 TRACE("Failed to update header with index offset");
5226 }
5227 }
5228 else
5229 {
5230 TRACE("Failed to write all index entries (wrote %zu of %" PRIu64 ")", entries_written,
5231 index_header.entries);
5233 }
5234 }
5235 }
5236 else
5237 {
5238 TRACE("Failed to write index header");
5240 }
5241
5242 return AARUF_STATUS_OK;
5243}
5244
5258{
5259 TRACE("File is writing");
5260
5262
5263 TRACE("Seeking to start of image");
5264 // Write the header at the beginning of the file
5265 aaruf_fseek(ctx->imageStream, 0, SEEK_SET);
5266
5267 TRACE("Writing header at position 0");
5268 if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1)
5269 {
5270 fclose(ctx->imageStream);
5271 ctx->imageStream = NULL;
5273 return -1;
5274 }
5275
5276 // Close current block first
5277 TRACE("Closing current block if any");
5278 if(ctx->writing_buffer != NULL)
5279 {
5280 int error = aaruf_close_current_block(ctx);
5281
5282 if(error != AARUF_STATUS_OK) return error;
5283 }
5284
5285 int32_t res;
5286 if(ctx->is_tape)
5287 {
5288 // Write tape DDT
5289 if(ctx->dirty_tape_ddt)
5290 {
5291 res = write_tape_ddt(ctx);
5292 if(res != AARUF_STATUS_OK) return res;
5293 }
5294 }
5295 else
5296 {
5297 // Write cached secondary DDT table if any
5298 if(ctx->dirty_secondary_ddt)
5299 {
5300 res = write_cached_secondary_ddt(ctx);
5301 if(res != AARUF_STATUS_OK) return res;
5302 }
5303
5304 // Write primary DDT table (multi-level) if applicable
5305 if(ctx->dirty_primary_ddt)
5306 {
5307 res = write_primary_ddt(ctx);
5308 if(res != AARUF_STATUS_OK) return res;
5309 }
5310
5311 // Write single-level DDT table if applicable
5312 if(ctx->dirty_single_level_ddt)
5313 {
5314 res = write_single_level_ddt(ctx);
5315 if(res != AARUF_STATUS_OK) return res;
5316 }
5317 }
5318
5319 // Finalize checksums and write checksum block
5321
5322 // Write tracks block
5324
5325 // Write flux capture blocks
5326 if(ctx->dirty_flux_block)
5327 {
5328 res = write_flux_blocks(ctx);
5329 if(res != AARUF_STATUS_OK) return res;
5330 }
5331
5332 // Write MODE 2 subheader data block
5334
5335 // Write CD sector prefix data block
5337
5338 // Write sector prefix DDT (statuses + optional indexes)
5340
5341 // Write CD sector suffix data block (EDC/ECC captures)
5343
5344 // Write sector prefix DDT (EDC/ECC captures)
5346
5347 // Write sector subchannel data block
5349
5350 // Write DVD long sector data blocks
5352
5353 // Write DVD decrypted title keys
5355
5356 // Write media tags data blocks
5357 if(ctx->dirty_media_tags) write_media_tags(ctx);
5358
5359 // Write tape files
5361
5362 // Write tape partitions
5364
5365 // Write geometry block if any
5367
5368 // Write metadata block
5370
5371 // Write dump hardware block if any
5373
5374 // Write CICM XML block if any
5375 if(ctx->dirty_cicm_block) write_cicm_block(ctx);
5376
5377 // Write Aaru metadata JSON block if any
5379
5380 // Write the complete index at the end of the file
5381 if(ctx->dirty_index_block)
5382 {
5383 res = write_index_block(ctx);
5384 if(res != AARUF_STATUS_OK) return res;
5385 }
5386
5387 // Write erasure coding parity, ECMB, and recovery footer
5388 ec_finalize(ctx);
5389
5390 if(ctx->deduplicate && ctx->sector_hash_map != NULL)
5391 {
5392 TRACE("Clearing sector hash map");
5393 // Clear sector hash map
5395 ctx->sector_hash_map = NULL;
5396 }
5397
5398 return AARUF_STATUS_OK;
5399}
static int32_t write_flux_capture_payload(aaruformat_context *ctx, FluxCaptureRecord *record, FluxEntry *entry)
Serialize a single flux capture payload block to the image file.
static int32_t write_primary_ddt(aaruformat_context *ctx)
Write (flush) the multi-level primary DDT table header and data back to its file offset.
static void write_cicm_block(aaruformat_context *ctx)
Serialize the CICM XML metadata block to the image file.
static void write_sector_subchannel(aaruformat_context *ctx)
Serialize the per-sector subchannel or tag data block.
static int32_t write_index_block(aaruformat_context *ctx)
Serialize the accumulated index entries at the end of the image and back-patch the header.
static void write_media_tags(aaruformat_context *ctx)
Serialize all accumulated media tags to the image file.
static void write_dvd_title_key_decrypted_block(aaruformat_context *ctx)
Serialize the DVD decrypted title key data block to the image file.
static int32_t write_single_level_ddt(aaruformat_context *ctx)
Serialize a single-level DDT (tableShift == 0) directly after its header.
static void write_tape_file_block(aaruformat_context *ctx)
Serialize the tape file metadata block to the image file.
int32_t aaruf_finalize_write(aaruformat_context *ctx)
Finalize a write-mode image: flush all pending blocks, DDTs, and the index.
static void write_dumphw_block(aaruformat_context *ctx)
Serialize the dump hardware block containing acquisition environment information.
static void write_dvd_long_sector_blocks(aaruformat_context *ctx)
Serialize DVD long sector auxiliary data blocks to the image file.
static void write_checksum_block(aaruformat_context *ctx)
Finalize any active checksum calculations and append a checksum block.
static void write_sector_suffix(aaruformat_context *ctx)
Serialize the optional CD sector suffix block (EDC/ECC region capture).
static void write_sector_suffix_ddt(aaruformat_context *ctx)
Serialize the per-sector CD suffix status / index DeDuplication Table (DDT v2, suffix variant).
static void write_tracks_block(aaruformat_context *ctx)
Serialize the tracks metadata block and add it to the index.
static void write_aaru_json_block(aaruformat_context *ctx)
Serialize the Aaru metadata JSON block to the image file.
static void write_geometry_block(aaruformat_context *ctx)
Serialize the geometry metadata block to the image file.
static void write_mode2_subheaders_block(aaruformat_context *ctx)
Serialize a MODE 2 (XA) subheaders data block.
static void write_tape_partition_block(aaruformat_context *ctx)
Serialize the tape partition metadata block to the image file.
static void write_sector_prefix_ddt(aaruformat_context *ctx)
Serialize the per-sector CD prefix status / index DeDuplication Table (DDT v2, prefix variant).
static int32_t write_tape_ddt(aaruformat_context *ctx)
Converts tape DDT hash table to array format and writes it as a single-level DDT.
static int32_t write_cached_secondary_ddt(aaruformat_context *ctx)
Flush a cached secondary (child) DeDuplication Table (DDT) to the image.
Definition close_write.c:70
static void write_metadata_block(aaruformat_context *ctx)
Serialize the metadata block containing image and media descriptive information.
static void write_sector_prefix(aaruformat_context *ctx)
Serialize the optional CD sector prefix block.
static int32_t write_flux_blocks(aaruformat_context *ctx)
Serialize all accumulated flux capture blocks to the image file.
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
#define MD5_DIGEST_LENGTH
Definition context.h:72
struct TapeFileHashEntry tapeFileHashEntry
void aaruf_sha1_final(sha1_ctx *ctx, unsigned char *result)
Definition sha1.c:124
int32_t aaruf_cst_transform(const uint8_t *interleaved, uint8_t *sequential, size_t length)
Transforms interleaved subchannel data to sequential format.
Definition cst.c:35
int32_t aaruf_lzma_encode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, uint8_t *out_props, size_t *out_props_size, int32_t level, uint32_t dict_size, int32_t lc, int32_t lp, int32_t pb, int32_t fb, int32_t num_threads)
Encodes a buffer using LZMA compression.
Definition lzma.c:65
uint64_t aaruf_crc64_data(const uint8_t *data, uint32_t len)
Definition crc64.c:160
int aaruf_crc64_update(crc64_ctx *ctx, const uint8_t *data, uint32_t len)
Updates the CRC64 context with new data.
Definition crc64.c:55
size_t aaruf_zstd_encode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, size_t src_size, int level, int num_threads)
Encodes a buffer using Zstandard compression.
Definition zstd.c:59
void aaruf_crc64_free(crc64_ctx *ctx)
Frees a CRC64 context.
Definition crc64.c:155
void aaruf_sha256_final(sha256_ctx *ctx, unsigned char *result)
Definition sha256.c:115
crc64_ctx * aaruf_crc64_init()
Initializes a CRC64 context.
Definition crc64.c:32
void aaruf_md5_final(md5_ctx *ctx, unsigned char *result)
Definition md5.c:493
void aaruf_spamsum_free(spamsum_ctx *ctx)
Frees a spamsum (fuzzy hash) context.
Definition spamsum.c:87
int32_t aaruf_get_datatype_for_media_tag_type(int32_t tag_type)
Converts an Aaru media tag type to an image data type.
Definition helpers.c:223
int aaruf_spamsum_final(spamsum_ctx *ctx, uint8_t *result)
Definition spamsum.c:203
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
@ IndexBlock3
Block containing the index v3.
Definition enums.h:171
@ ChecksumBlock
Block containing contents checksums.
Definition enums.h:176
@ DataBlock
Block containing data.
Definition enums.h:164
@ TapePartitionBlock
Block containing list of partitions for a tape image.
Definition enums.h:182
@ GeometryBlock
Block containing logical geometry.
Definition enums.h:172
@ DeDuplicationTableSecondary
Block containing a secondary deduplication table (v2).
Definition enums.h:168
@ AaruMetadataJsonBlock
Block containing JSON version of Aaru Metadata.
Definition enums.h:183
@ CicmBlock
Block containing CICM XML metadata.
Definition enums.h:175
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:166
@ TapeFileBlock
Block containing list of files for a tape image.
Definition enums.h:181
@ DeDuplicationTableSAlpha
Block containing a secondary deduplication table (v2) (mistake).
Definition enums.h:167
@ DumpHardwareBlock
Block containing an array of hardware used to create the image.
Definition enums.h:180
@ MetadataBlock
Block containing metadata.
Definition enums.h:173
@ TracksBlock
Block containing optical disc tracks.
Definition enums.h:174
@ FluxDataBlock
Block containing flux data metadata.
Definition enums.h:184
@ DataStreamPayloadBlock
Block containing compressed data stream payload (e.g., flux data, bitstreams).
Definition enums.h:185
@ AARU_FEATURE_INCOMPAT_ZSTD
Image contains Zstandard-compressed blocks.
Definition enums.h:307
@ OpticalDisc
Purely optical discs.
Definition enums.h:246
@ BlockMedia
Media that is physically block-based or abstracted like that.
Definition enums.h:247
@ Blake3
BLAKE3 hash.
Definition enums.h:201
@ Sha1
SHA-1 hash.
Definition enums.h:198
@ Md5
MD5 hash.
Definition enums.h:197
@ Sha256
SHA-256 hash.
Definition enums.h:199
@ SpamSum
SpamSum (context-triggered piecewise hash).
Definition enums.h:200
@ kDataTypeDvdTitleKeyDecrypted
Decrypted DVD Title Key.
Definition enums.h:129
@ kDataTypeDvdSectorId
DVD Identification Data (ID).
Definition enums.h:130
@ kDataTypeAppleSonyTag
Apple Sony (12‑byte) tag.
Definition enums.h:120
@ kDataTypeCdSectorSuffix
Compact Disc sector suffix (EDC, ECC P, ECC Q).
Definition enums.h:117
@ kDataTypeFluxData
Flux data.
Definition enums.h:136
@ kDataTypeCdSubchannel
Compact Disc subchannel data.
Definition enums.h:118
@ kDataTypeDvdSectorIed
DVD ID Error Detection Code (IED).
Definition enums.h:131
@ kDataTypeDvdSectorCprMai
DVD Copyright Management Information (CPR_MAI).
Definition enums.h:128
@ kDataTypePriamDataTowerTag
Priam Data Tower (24‑byte) tag.
Definition enums.h:121
@ kDataTypeUserData
User (main) data.
Definition enums.h:48
@ kDataTypeDvdSectorEdc
DVD Error Detection Code (EDC).
Definition enums.h:132
@ kDataTypeAppleProfileTag
Apple Profile (20‑byte) tag.
Definition enums.h:119
@ kDataTypeCdSubHeader
Compact Disc MODE 2 subheader.
Definition enums.h:125
@ kDataTypeCdSectorPrefix
Compact Disc sector prefix (sync, header).
Definition enums.h:116
@ AARU_FEATURE_RW_BLAKE3
BLAKE3 checksum is present (read/write support for BLAKE3 hashes).
Definition enums.h:294
@ AARUF_STATUS_INVALID_CONTEXT
Provided context/handle is invalid.
Definition enums.h:237
CompressionType
List of known compression types.
Definition enums.h:32
@ kCompressionLzmaCst
LZMA applied to Claunia Subchannel Transform processed data.
Definition enums.h:36
@ kCompressionLzma
LZMA compression.
Definition enums.h:34
@ kCompressionNone
Not compressed.
Definition enums.h:33
@ kCompressionZstd
Zstandard compression.
Definition enums.h:37
@ kCompressionZstdCst
Zstandard applied to Claunia Subchannel Transform processed data.
Definition enums.h:38
void ec_finalize(aaruformat_context *ctx)
Flush all partial data stripes and write parity for all groups + ECMB + recovery footer.
Definition erasure.c:690
#define AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA
Failure writing block payload.
Definition errors.h:63
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:81
#define AARUF_ERROR_NOT_ENOUGH_MEMORY
Memory allocation failure (critical).
Definition errors.h:48
#define AARUF_ERROR_INCORRECT_DATA_SIZE
Data size does not match expected size.
Definition errors.h:65
#define AARUF_ERROR_NOT_AARUFORMAT
Input file/stream failed magic or structural validation.
Definition errors.h:40
#define AARUF_ERROR_CANNOT_WRITE_HEADER
Failure writing container header.
Definition errors.h:60
#define AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER
Failure writing block header.
Definition errors.h:62
@ AppleProfile
Definition aaru.h:710
@ AppleSonySS
3.5", SS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
Definition aaru.h:252
@ PriamDataTower
Definition aaru.h:713
@ AppleFileWare
5.25", DS, ?D, ?? tracks, ?? spt, 512 bytes/sector, GCR, opposite side heads, aka Twiggy
Definition aaru.h:254
@ AppleSonyDS
3.5", DS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
Definition aaru.h:253
void free_map(hash_map_t *map)
Frees all memory associated with a hash map.
Definition hash_map.c:78
int32_t aaruf_close_current_block(aaruformat_context *ctx)
Finalizes and writes the current data block to the AaruFormat image file.
Definition write.c:1536
static int aaruf_fseek(FILE *stream, aaru_off_t offset, int origin)
Definition internal.h:46
static aaru_off_t aaruf_ftell(FILE *stream)
Definition internal.h:52
#define LZMA_THREADS(ctx)
Clamp num_threads to LZMA's valid range [1, 2].
Definition internal.h:23
int32_t flux_map_rebuild_from_entries(aaruformat_context *ctx)
Rebuild the flux capture lookup map from the flux_entries array.
Definition flux.c:172
int64_t aaru_off_t
Definition internal.h:42
#define FATAL(fmt,...)
Definition log.h:40
#define TRACE(fmt,...)
Definition log.h:25
#define SHA1_DIGEST_LENGTH
Definition sha1.h:39
#define SHA256_DIGEST_LENGTH
Definition sha256.h:38
#define FUZZY_MAX_RESULT
Definition spamsum.h:30
Version 2 container header with GUID, alignment shifts, and feature negotiation bitmaps.
Definition header.h:107
uint64_t featureIncompatible
Feature bits: any unimplemented -> abort (cannot open safely).
Definition header.h:123
uint64_t featureCompatible
Feature bits: unimplemented bits are ignorable (still R/W safe).
Definition header.h:121
uint64_t indexOffset
Absolute byte offset to primary index block (MUST be > 0; 0 => corrupt/unreadable).
Definition header.h:115
Header for an Aaru metadata JSON block (identifier == BlockType::AaruMetadataJsonBlock).
Definition metadata.h:120
uint32_t identifier
Block identifier, must be BlockType::AaruMetadataJsonBlock.
Definition metadata.h:121
uint32_t length
Length in bytes of the Aaru metadata JSON payload that follows.
Definition metadata.h:122
Header preceding the compressed data payload of a data block (BlockType::DataBlock).
Definition data.h:71
uint32_t cmpLength
Size in bytes of the compressed payload immediately following this header.
Definition data.h:76
uint32_t length
Size in bytes of the uncompressed payload resulting after decompression.
Definition data.h:77
uint32_t identifier
Block identifier, must be BlockType::DataBlock.
Definition data.h:72
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload (cmpLength bytes).
Definition data.h:78
uint64_t crc64
CRC64-ECMA of the uncompressed payload (length bytes).
Definition data.h:79
uint16_t type
Logical data classification (value from DataType).
Definition data.h:73
uint16_t compression
Compression algorithm used (value from CompressionType).
Definition data.h:74
Per-checksum metadata immediately followed by the digest / signature bytes.
Definition checksum.h:91
uint32_t length
Length in bytes of the digest that immediately follows this structure.
Definition checksum.h:93
uint8_t type
Algorithm used (value from ChecksumAlgorithm).
Definition checksum.h:92
Header that precedes the sequence of checksum entries for a checksum block.
Definition checksum.h:74
uint32_t identifier
Block identifier, must be BlockType::ChecksumBlock.
Definition checksum.h:75
uint32_t length
Length in bytes of the payload (all entries + their digest data, excluding this header).
Definition checksum.h:76
uint8_t entries
Number of checksum entries that follow in the payload.
Definition checksum.h:77
uint8_t * spamsum
SpamSum fuzzy hash (ASCII), allocated length+1 with trailing 0.
Definition context.h:113
bool hasSha256
True if sha256[] buffer populated.
Definition context.h:106
uint8_t sha1[20]
SHA-1 digest (20 bytes).
Definition context.h:110
uint8_t sha256[32]
SHA-256 digest (32 bytes).
Definition context.h:111
uint8_t md5[16]
MD5 digest (16 bytes).
Definition context.h:109
bool hasSpamSum
True if spamsum pointer allocated and signature read.
Definition context.h:108
bool hasSha1
True if sha1[] buffer populated.
Definition context.h:105
uint8_t blake3[BLAKE3_OUT_LEN]
BLAKE3 digest (32 bytes).
Definition context.h:112
bool hasMd5
True if md5[] buffer populated.
Definition context.h:104
bool hasBlake3
True if blake3[] buffer populated.
Definition context.h:107
Header for a CICM XML metadata block (identifier == BlockType::CicmBlock).
Definition metadata.h:108
uint32_t length
Length in bytes of the CICM metadata payload that follows.
Definition metadata.h:110
uint32_t identifier
Block identifier, must be BlockType::CicmBlock.
Definition metadata.h:109
Header structure for a DataStreamPayloadBlock containing data stream payload.
Definition flux.h:243
uint16_t dataType
Data type classification (value from DataType), e.g., FluxData or BitstreamData.
Definition flux.h:245
uint32_t cmpLength
Compressed length in bytes (includes LZMA properties if compression = Lzma).
Definition flux.h:247
uint64_t cmpCrc64
CRC64-ECMA checksum of the compressed payload (or same as crc64 if uncompressed).
Definition flux.h:249
uint32_t identifier
Block identifier, must be BlockType::DataStreamPayloadBlock (0x4C505344, "DSPL").
Definition flux.h:244
uint16_t compression
Compression type: 0 = None, 1 = Lzma.
Definition flux.h:246
uint32_t length
Uncompressed length in bytes.
Definition flux.h:248
uint64_t crc64
CRC64-ECMA checksum of the uncompressed payload.
Definition flux.h:250
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
uint64_t cmpCrc64
CRC64-ECMA of compressed table payload.
Definition ddt.h:161
uint16_t type
Data classification (DataType) for sectors referenced by this table.
Definition ddt.h:144
uint64_t start
Base internal index covered by this table (used for secondary tables; currently informational).
Definition ddt.h:153
uint64_t crc64
CRC64-ECMA of uncompressed table payload.
Definition ddt.h:162
uint64_t entries
Number of entries contained in (uncompressed) table payload.
Definition ddt.h:158
uint8_t levels
Total number of hierarchy levels (root depth); > 0.
Definition ddt.h:146
uint64_t length
Uncompressed payload size in bytes.
Definition ddt.h:160
uint32_t identifier
Block identifier, must be BlockType::DeDuplicationTable2.
Definition ddt.h:143
uint8_t tableShift
2^tableShift = number of logical sectors per primary entry (multi-level only; 0 for single-level or s...
Definition ddt.h:156
uint64_t blocks
Total internal span (negative + usable + overflow) in logical sectors.
Definition ddt.h:150
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
uint32_t overflow
Trailing dumped sectors beyond user area (overflow range), still mapped with entries.
Definition ddt.h:151
uint32_t negative
Leading negative LBA count; added to external L to build internal index.
Definition ddt.h:149
uint8_t tableLevel
Zero-based level index of this table (0 = root, increases downward).
Definition ddt.h:147
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
uint8_t dataShift
2^dataShift = sectors represented per increment in blockIndex field.
Definition ddt.h:155
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
uint64_t previousLevelOffset
Absolute byte offset of the parent (previous) level table; 0 if root.
Definition ddt.h:148
Inclusive [start,end] logical sector range contributed by a single hardware environment.
Definition context.h:431
uint8_t * firmware
Firmware version string or NULL.
Definition context.h:418
uint8_t * revision
Hardware revision string or NULL.
Definition context.h:417
uint8_t * model
Model string or NULL.
Definition context.h:416
uint8_t * softwareName
Dump software name or NULL.
Definition context.h:420
struct DumpExtent * extents
Array of extents (entry.extents elements) or NULL.
Definition context.h:414
uint8_t * manufacturer
Manufacturer string (UTF-8) or NULL.
Definition context.h:415
uint8_t * softwareVersion
Dump software version or NULL.
Definition context.h:421
uint8_t * serial
Serial number string or NULL.
Definition context.h:419
DumpHardwareEntry entry
Fixed-size header with lengths & counts.
Definition context.h:413
uint8_t * softwareOperatingSystem
Host operating system string or NULL.
Definition context.h:422
Per-environment length table describing subsequent UTF-8 strings and optional extent array.
Definition dump.h:113
uint32_t softwareNameLength
Length in bytes of dumping software name string.
Definition dump.h:119
uint32_t manufacturerLength
Length in bytes of manufacturer UTF-8 string.
Definition dump.h:114
uint32_t softwareVersionLength
Length in bytes of dumping software version string.
Definition dump.h:120
uint32_t firmwareLength
Length in bytes of firmware version string.
Definition dump.h:117
uint32_t extents
Number of DumpExtent records following the strings (0 = none).
Definition dump.h:122
uint32_t modelLength
Length in bytes of model UTF-8 string.
Definition dump.h:115
uint32_t serialLength
Length in bytes of device serial number string.
Definition dump.h:118
uint32_t revisionLength
Length in bytes of revision / hardware revision string.
Definition dump.h:116
uint32_t softwareOperatingSystemLength
Length in bytes of host operating system string.
Definition dump.h:121
Header that precedes a sequence of dump hardware entries and their variable-length payload.
Definition dump.h:91
uint64_t crc64
CRC64-ECMA of the payload (byte-swapped for legacy v1 images, handled automatically).
Definition dump.h:95
uint32_t identifier
Block identifier, must be BlockType::DumpHardwareBlock.
Definition dump.h:92
uint32_t length
Total payload bytes after this header (sum of entries, strings, and extents arrays).
Definition dump.h:94
uint16_t entries
Number of DumpHardwareEntry records that follow.
Definition dump.h:93
Internal structure for storing flux capture data during write mode.
Definition flux.h:271
FluxEntry entry
Flux entry metadata describing this capture.
Definition flux.h:272
uint32_t data_length
Length of the data buffer in bytes.
Definition flux.h:274
uint32_t index_length
Length of the index buffer in bytes.
Definition flux.h:276
uint8_t * index_buffer
Pointer to the flux index buffer. Owned by the utarray, freed automatically.
Definition flux.h:275
uint8_t * data_buffer
Pointer to the flux data buffer. Owned by the utarray, freed automatically.
Definition flux.h:273
Metadata entry describing a single flux capture in the FluxDataBlock.
Definition flux.h:156
uint64_t indexResolution
Resolution in picoseconds at which the index stream was sampled.
Definition flux.h:161
uint64_t dataResolution
Resolution in picoseconds at which the data stream was sampled.
Definition flux.h:162
uint32_t head
Head number the flux capture corresponds to. Typically 0 or 1 for double-sided media.
Definition flux.h:157
uint64_t payloadOffset
Block-aligned file offset where the DataStreamPayloadBlock containing this capture's data is stored,...
Definition flux.h:164
uint16_t track
Track number the flux capture corresponds to. Track numbering is format-dependent.
Definition flux.h:158
uint64_t indexOffset
Byte offset within the payload where the index buffer starts (equals data_length).
Definition flux.h:163
uint8_t subtrack
Subtrack number, allowing sub-stepping within a track. Used for fine positioning.
Definition flux.h:159
uint32_t captureIndex
Capture index, allowing multiple captures for the same location (e.g., multiple revolutions).
Definition flux.h:160
Header structure for a FluxDataBlock containing flux capture metadata.
Definition flux.h:117
uint32_t identifier
Block identifier, must be BlockType::FluxDataBlock (0x58554C46, "FLUX").
Definition flux.h:118
uint16_t entries
Number of FluxEntry records following this header. Maximum value: 65535.
Definition flux.h:119
uint64_t crc64
CRC64-ECMA checksum of the FluxEntry array (header excluded).
Definition flux.h:121
uint8_t blockAlignmentShift
Block alignment shift: 2^blockAlignmentShift = block alignment boundary in bytes.
Definition flux.h:120
Legacy CHS style logical geometry metadata (BlockType::GeometryBlock).
Definition data.h:91
uint32_t identifier
Block identifier, must be BlockType::GeometryBlock.
Definition data.h:92
uint32_t MediaType
Media type identifier (see MediaType enum; 0=Unknown).
Definition aaru.h:945
uint8_t MetadataMediaType
Media type for sidecar generation (internal archival use).
Definition aaru.h:946
uint64_t Sectors
Total count of addressable logical sectors/blocks.
Definition aaru.h:938
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
uint16_t dataType
Data classification (value from DataType) or unused for untyped blocks.
Definition index.h:111
Index header (version 3) adding hierarchical chaining (identifier == IndexBlock3).
Definition index.h:93
uint64_t previous
File offset of a previous IndexBlock3 header (0 if none / root segment).
Definition index.h:97
uint32_t identifier
Block identifier (must be BlockType::IndexBlock3).
Definition index.h:94
uint64_t crc64
CRC64-ECMA of the local entries array (does NOT cover subindexes or previous chains).
Definition index.h:96
uint64_t entries
Number of IndexEntry records that follow in this (sub)index block.
Definition index.h:95
Header for a metadata block containing offsets and lengths to UTF-16LE descriptive strings.
Definition metadata.h:69
uint32_t commentsLength
Length in bytes (including null) of comments string.
Definition metadata.h:78
int32_t mediaSequence
Sequence number within a multi-disc / multi-volume set (0-based or 1-based as producer defines).
Definition metadata.h:72
uint32_t identifier
Block identifier, must be BlockType::MetadataBlock.
Definition metadata.h:70
uint32_t mediaTitleOffset
Offset to UTF-16LE media title string.
Definition metadata.h:79
uint32_t driveModelLength
Length in bytes (including null) of drive model string.
Definition metadata.h:94
uint32_t driveManufacturerLength
Length in bytes (including null) of drive manufacturer string.
Definition metadata.h:92
uint32_t blockSize
Total size in bytes of the entire metadata block (header + strings).
Definition metadata.h:71
uint32_t driveModelOffset
Offset to UTF-16LE drive model string.
Definition metadata.h:93
uint32_t mediaModelOffset
Offset to UTF-16LE media model string.
Definition metadata.h:83
uint32_t creatorOffset
Offset to UTF-16LE creator string (or undefined if creatorLength==0).
Definition metadata.h:75
uint32_t mediaTitleLength
Length in bytes (including null) of media title string.
Definition metadata.h:80
uint32_t mediaManufacturerOffset
Offset to UTF-16LE media manufacturer string.
Definition metadata.h:81
uint32_t driveSerialNumberLength
Length in bytes (including null) of drive serial number string.
Definition metadata.h:96
uint32_t driveSerialNumberOffset
Offset to UTF-16LE drive serial number string.
Definition metadata.h:95
uint32_t mediaManufacturerLength
Length in bytes (including null) of media manufacturer string.
Definition metadata.h:82
uint32_t mediaModelLength
Length in bytes (including null) of media model string.
Definition metadata.h:84
uint32_t driveFirmwareRevisionOffset
Offset to UTF-16LE drive firmware revision string.
Definition metadata.h:97
int32_t lastMediaSequence
Total number of media in the set; 0 or 1 if single item.
Definition metadata.h:74
uint32_t commentsOffset
Offset to UTF-16LE comments string.
Definition metadata.h:77
uint32_t driveManufacturerOffset
Offset to UTF-16LE drive manufacturer string.
Definition metadata.h:91
uint32_t mediaSerialNumberOffset
Offset to UTF-16LE media serial number string.
Definition metadata.h:85
uint32_t mediaSerialNumberLength
Length in bytes (including null) of media serial number string.
Definition metadata.h:86
uint32_t mediaPartNumberOffset
Offset to UTF-16LE media part number string.
Definition metadata.h:89
uint32_t mediaPartNumberLength
Length in bytes (including null) of media part number string.
Definition metadata.h:90
uint32_t mediaBarcodeLength
Length in bytes (including null) of media barcode string.
Definition metadata.h:88
uint32_t creatorLength
Length in bytes (including null) of creator string (0 if absent).
Definition metadata.h:76
uint32_t driveFirmwareRevisionLength
Length in bytes (including null) of drive firmware revision string.
Definition metadata.h:98
uint32_t mediaBarcodeOffset
Offset to UTF-16LE media barcode string.
Definition metadata.h:87
uint64_t key
Key: sector address.
Definition context.h:145
uint64_t value
Value: DDT entry.
Definition context.h:146
Describes a single logical file on a tape medium.
Definition tape.h:134
TapeFileEntry fileEntry
The actual tape file data.
Definition context.h:132
Header for a tape file metadata block containing file layout information.
Definition tape.h:238
uint32_t identifier
Block type identifier.
Definition tape.h:239
uint64_t crc64
CRC64-ECMA checksum of the entry data.
Definition tape.h:245
uint64_t length
Size of entry data in bytes (excluding this header).
Definition tape.h:243
Describes a single physical partition on a tape medium.
Definition tape.h:320
TapePartitionEntry partitionEntry
The actual tape partition data.
Definition context.h:139
Header for a tape partition metadata block containing partition layout information.
Definition tape.h:441
uint64_t crc64
CRC64-ECMA checksum of the entry data.
Definition tape.h:448
uint64_t length
Size of entry data in bytes (excluding this header).
Definition tape.h:446
uint32_t identifier
Block type identifier.
Definition tape.h:442
Single optical disc track descriptor (sequence, type, LBAs, session, ISRC, flags).
Definition optical.h:72
Header for an optical tracks block listing track entries.
Definition optical.h:62
uint16_t entries
Number of TrackEntry records following this header.
Definition optical.h:64
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
uint8_t * media_barcode
Barcode of the media represented by the image.
Definition context.h:225
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:192
Checksums checksums
Whole-image checksums discovered.
Definition context.h:272
uint8_t * creator
Who (person) created the image?
Definition context.h:219
bool dirty_checksum_block
True if checksum block should be written during close.
Definition context.h:325
bool deduplicate
Storage deduplication active (duplicates coalesce).
Definition context.h:303
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:304
uint8_t * cicm_block
CICM XML payload.
Definition context.h:217
uint8_t * sector_cpr_mai
DVD sector CPR_MAI (6 bytes) if present.
Definition context.h:210
bool dirty_media_tags
True if media tags should be written during close.
Definition context.h:335
hash_map_t * sector_hash_map
Deduplication hash map (fingerprint->entry mapping).
Definition context.h:256
FluxHeader flux_data_header
Flux data header (if present).
Definition context.h:316
UT_array * flux_captures
Pending flux capture payloads (write path).
Definition context.h:318
sha256_ctx sha256_context
Opaque SHA-256 context for streaming updates.
Definition context.h:275
bool calculating_sha256
True if whole-image SHA-256 being calculated on-the-fly.
Definition context.h:278
bool dirty_primary_ddt
True if primary DDT table should be written during close.
Definition context.h:323
uint8_t * drive_firmware_revision
Firmware revision of the drive used to read the media represented by the image.
Definition context.h:231
uint8_t * media_serial_number
Serial number of the media represented by the image.
Definition context.h:224
uint8_t * sector_ied
DVD sector IED (2 bytes) if present.
Definition context.h:209
md5_ctx md5_context
Opaque MD5 context for streaming updates.
Definition context.h:273
bool dirty_sector_suffix_block
True if sector suffix block should be written during close.
Definition context.h:330
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:190
MetadataBlockHeader metadata_block_header
Metadata block header.
Definition context.h:233
uint8_t * sector_prefix
Raw per-sector prefix (e.g., sync+header) uncorrected.
Definition context.h:202
bool dirty_dvd_title_key_decrypted_block
True if decrypted title key block should be written during close.
Definition context.h:334
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:189
tapeFileHashEntry * tape_files
Hash table root for tape files.
Definition context.h:311
bool dirty_mode2_subheaders_block
True if MODE2 subheader block should be written during close.
Definition context.h:327
bool dirty_tape_partition_block
True if tape partition block should be written during close.
Definition context.h:338
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:193
bool dirty_cicm_block
True if CICM metadata block should be written during close.
Definition context.h:342
bool is_tape
True if the image is a tape image.
Definition context.h:313
uint8_t * sector_edc
DVD sector EDC (4 bytes) if present.
Definition context.h:211
bool calculating_sha1
True if whole-image SHA-1 being calculated on-the-fly.
Definition context.h:277
uint8_t * media_model
Model of the media represented by the image.
Definition context.h:223
bool dirty_tracks_block
True if tracks block should be written during close.
Definition context.h:326
uint8_t * drive_serial_number
Serial number of the drive used to read the media represented by the image.
Definition context.h:229
bool dirty_sector_prefix_block
True if sector prefix block should be written during close.
Definition context.h:328
uint8_t * drive_manufacturer
Manufacturer of the drive used to read the media represented by the image.
Definition context.h:227
bool dirty_index_block
True if index block should be written during close.
Definition context.h:345
uint8_t * sector_suffix
Raw per-sector suffix (EDC/ECC) uncorrected.
Definition context.h:204
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:178
FluxEntry * flux_entries
Array of flux entries (flux_data_header.entries elements).
Definition context.h:317
bool dirty_json_block
True if JSON metadata block should be written during close.
Definition context.h:343
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:185
spamsum_ctx * spamsum_context
Opaque SpamSum context for streaming updates.
Definition context.h:270
CicmMetadataBlock cicm_block_header
CICM metadata header (if present).
Definition context.h:234
size_t sector_prefix_offset
Current position in sector_prefix.
Definition context.h:289
uint8_t * drive_model
Model of the drive used to read the media represented by the image.
Definition context.h:228
bool dirty_single_level_ddt
True if single-level DDT should be written during close.
Definition context.h:324
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:283
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:188
bool calculating_spamsum
True if whole-image SpamSum being calculated on-the-fly.
Definition context.h:279
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:195
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:267
blake3_hasher * blake3_context
Opaque BLAKE3 context for streaming updates.
Definition context.h:271
bool calculating_blake3
True if whole-image BLAKE3 being calculated on-the-fly.
Definition context.h:280
struct DumpHardwareEntriesWithData * dump_hardware_entries_with_data
Array of dump hardware entries + strings.
Definition context.h:215
bool calculating_md5
True if whole-image MD5 being calculated on-the-fly.
Definition context.h:276
bool dirty_sector_suffix_ddt
True if sector suffix DDT should be written during close.
Definition context.h:331
GeometryBlockHeader geometry_block
Logical geometry block (if present).
Definition context.h:232
size_t sector_suffix_offset
Current position in sector_suffix.
Definition context.h:290
bool has_zstd_blocks
True if any block was actually written with Zstandard compression.
Definition context.h:306
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:191
uint8_t * json_block
JSON metadata block payload (UTF-8).
Definition context.h:218
int zstd_level
Zstandard compression level (writing path, default 19).
Definition context.h:307
uint8_t * media_part_number
Part number of the media represented by the image.
Definition context.h:226
uint8_t * sector_decrypted_title_key
DVD decrypted title key (5 bytes) if present.
Definition context.h:212
bool dirty_dumphw_block
True if dump hardware block should be written during close.
Definition context.h:341
AaruMetadataJsonBlockHeader json_block_header
JSON metadata block header (if present).
Definition context.h:236
bool dirty_metadata_block
True if metadata block should be written during close.
Definition context.h:340
uint8_t * sector_subchannel
Raw 96-byte subchannel (if captured).
Definition context.h:206
uint8_t * comments
Image comments.
Definition context.h:221
bool dirty_sector_subchannel_block
True if subchannel block should be written during close.
Definition context.h:332
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:179
bool dirty_dvd_long_sector_blocks
True if DVD long sector blocks should be written during close.
Definition context.h:333
bool dirty_sector_prefix_ddt
True if sector prefix DDT should be written during close.
Definition context.h:329
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:255
int num_threads
Compression worker threads (1 = single-threaded, default).
Definition context.h:308
bool dirty_tape_file_block
True if tape file block should be written during close.
Definition context.h:337
uint8_t * mode2_subheaders
MODE2 Form1/Form2 8-byte subheaders (concatenated).
Definition context.h:207
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:263
bool dirty_flux_block
True if flux block should be written during close.
Definition context.h:344
uint8_t * sector_id
DVD sector ID (4 bytes) if present.
Definition context.h:208
DumpHardwareHeader dump_hardware_header
Dump hardware header.
Definition context.h:235
bool use_zstd
Use Zstandard instead of LZMA for data blocks.
Definition context.h:305
sha1_ctx sha1_context
Opaque SHA-1 context for streaming updates.
Definition context.h:274
bool dirty_secondary_ddt
True if secondary DDT tables should be written during close.
Definition context.h:322
bool dirty_tape_ddt
True if tape DDT should be written during close.
Definition context.h:336
TapePartitionHashEntry * tape_partitions
Hash table root for tape partitions.
Definition context.h:312
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:302
TrackEntry * track_entries
Full track list (tracksHeader.entries elements).
Definition context.h:245
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:194
uint8_t * media_title
Title of the media represented by the image.
Definition context.h:220
bool dirty_geometry_block
True if geometry block should be written during close.
Definition context.h:339
uint8_t * media_manufacturer
Manufacturer of the media represented by the image.
Definition context.h:222
TracksHeader tracks_header
Tracks header (optical) if present.
Definition context.h:247
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56
Hash table entry for an arbitrary media tag (e.g., proprietary drive/medium descriptor).
Definition context.h:122
uint8_t * data
Tag data blob (opaque to library core); length bytes long.
Definition context.h:123
int32_t type
Numeric type identifier.
Definition context.h:124
uint32_t length
Length in bytes of data.
Definition context.h:125