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 // Flux-only images carry no DDT.
73 if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
74
75 // Write cached secondary table to file end and update primary table entry with its position
76 // Check if we have a cached table that needs to be written (either it has an offset or exists in memory)
77 bool has_cached_secondary_ddt =
78 ctx->user_data_ddt_header.tableShift > 0 && (ctx->cached_ddt_offset != 0 || ctx->cached_secondary_ddt2 != NULL);
79
80 if(!has_cached_secondary_ddt) return AARUF_STATUS_OK;
81
82 TRACE("Writing cached secondary DDT table to file");
83
84 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
85 aaru_off_t end_of_file = aaruf_ftell(ctx->imageStream);
86
87 // Align the position according to block alignment shift
88 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
89 if(end_of_file & alignment_mask)
90 {
91 // Calculate the next aligned position
92 uint64_t aligned_position = end_of_file + alignment_mask & ~alignment_mask;
93
94 // Seek to the aligned position and pad with zeros if necessary
95 aaruf_fseek(ctx->imageStream, (aaru_off_t)aligned_position, SEEK_SET);
96 end_of_file = aligned_position;
97
98 TRACE("Aligned DDT write position from %ld to %" PRIu64 " (alignment shift: %d)",
99 aaruf_ftell(ctx->imageStream) - (aligned_position - end_of_file), aligned_position,
101 }
102
103 // Prepare DDT header for the cached table
104 DdtHeader2 ddt_header = {0};
106 ddt_header.type = kDataTypeUserData;
107 ddt_header.compression =
109 ddt_header.levels = ctx->user_data_ddt_header.levels;
110 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
111 ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
112 ddt_header.negative = ctx->user_data_ddt_header.negative;
113 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
115 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
116 ddt_header.tableShift = 0; // Secondary tables are single level
117
118 uint64_t items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
119 ddt_header.blocks = items_per_ddt_entry;
120 ddt_header.entries = items_per_ddt_entry;
121 ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry;
122
123 // Calculate data size
124 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
125
126 // Calculate CRC64 of the data
127 crc64_ctx *crc64_context = aaruf_crc64_init();
128 if(crc64_context != NULL)
129 {
130 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
131
132 uint64_t crc64;
133 aaruf_crc64_final(crc64_context, &crc64);
134 ddt_header.crc64 = crc64;
135 }
136
137 // Free CRC64 context
138 aaruf_crc64_free(crc64_context);
139
140 uint8_t *buffer = NULL;
141 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
142
143 if(ddt_header.compression == kCompressionNone)
144 {
145 buffer = (uint8_t *)ctx->cached_secondary_ddt2;
146 ddt_header.cmpCrc64 = ddt_header.crc64;
147 }
148 else
149 {
150 buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
151 if(buffer == NULL)
152 {
153 TRACE("Failed to allocate memory for secondary DDT v2 compression");
155 }
156
157 size_t dst_size;
158
159 if(ctx->use_zstd)
160 {
161 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ddt_header.length * 2,
162 (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
163 ctx->zstd_level, ctx->num_threads);
164 if(dst_size == 0) dst_size = ddt_header.length; // compression failed, fall through to None
165 }
166 else
167 {
168 dst_size = (size_t)ddt_header.length * 2 * 2;
169 size_t props_size = LZMA_PROPERTIES_LENGTH;
170 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
171 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273,
172 LZMA_THREADS(ctx));
173 }
174
175 ddt_header.cmpLength = (uint32_t)dst_size;
176
177 if(ddt_header.cmpLength >= ddt_header.length)
178 {
179 ddt_header.compression = kCompressionNone;
180 free(buffer);
181 buffer = (uint8_t *)ctx->cached_secondary_ddt2;
182 }
183 }
184
185 if(ddt_header.compression == kCompressionNone)
186 {
187 ddt_header.cmpLength = ddt_header.length;
188 ddt_header.cmpCrc64 = ddt_header.crc64;
189 }
190 else
191 {
192 ddt_header.cmpCrc64 = aaruf_crc64_data(buffer, ddt_header.cmpLength);
193 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
194 }
195
196 if(ddt_header.compression == kCompressionLzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
197
198 // Write header
199 if(fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
200 {
201 if(ddt_header.compression == kCompressionLzma)
202 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
203
204 // Write data
205 if(fwrite(buffer, ddt_header.cmpLength, 1, ctx->imageStream) == 1)
206 {
207 // Update primary table entry to point to new location
208 const uint64_t new_secondary_table_block_offset =
209 end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
210
211 ctx->user_data_ddt2[ctx->cached_ddt_position] = (uint64_t)new_secondary_table_block_offset;
212
213 // Update index: remove old entry for cached DDT and add new one
214 TRACE("Updating index for cached secondary DDT");
215
216 // Remove old index entry for the cached DDT
217 if(ctx->cached_ddt_offset != 0)
218 {
219 TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
220 const IndexEntry *entry = NULL;
221
222 // Find and remove the old index entry
223 for(unsigned int k = 0; k < utarray_len(ctx->index_entries); k++)
224 {
225 entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
226 if(entry && entry->offset == ctx->cached_ddt_offset &&
229 {
230 TRACE("Found old DDT index entry at position %u, removing", k);
231 utarray_erase(ctx->index_entries, k, 1);
232 break;
233 }
234 }
235 }
236
237 // Add new index entry for the newly written secondary DDT
238 IndexEntry new_ddt_entry;
240 new_ddt_entry.dataType = kDataTypeUserData;
241 new_ddt_entry.offset = end_of_file;
242
243 utarray_push_back(ctx->index_entries, &new_ddt_entry);
244 ctx->dirty_index_block = true;
245 TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
246
247 // Write the updated primary table back to its original position in the file
248 aaru_off_t saved_pos = aaruf_ftell(ctx->imageStream);
249 aaruf_fseek(ctx->imageStream, (aaru_off_t)(ctx->primary_ddt_offset + sizeof(DdtHeader2)), SEEK_SET);
250
251 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
252
253 size_t primary_written_bytes = 0;
254 primary_written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
255
256 if(primary_written_bytes != 1)
257 {
258 TRACE("Could not flush primary DDT table to file.");
260 }
261
262 aaruf_fseek(ctx->imageStream, saved_pos, SEEK_SET);
263 }
264 else
265 TRACE("Failed to write cached secondary DDT data");
266 }
267 else
268 TRACE("Failed to write cached secondary DDT header");
269
270 // Free the cached table
271 free(ctx->cached_secondary_ddt2);
272 ctx->cached_secondary_ddt2 = NULL;
273 ctx->cached_ddt_offset = 0;
274
275 // Set position
276 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
277
278 if(ddt_header.compression != kCompressionNone) free(buffer);
279
280 return AARUF_STATUS_OK;
281}
282
301{
302 // Flux-only images carry no DDT.
303 if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
304
305 // Write the cached primary DDT table back to its position in the file
306 if(ctx->user_data_ddt_header.tableShift <= 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
307
308 TRACE("Writing cached primary DDT table back to file");
309
310 // Calculate CRC64 of the primary DDT table data first
311 crc64_ctx *crc64_context = aaruf_crc64_init();
312 if(crc64_context != NULL)
313 {
314 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
315
316 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->user_data_ddt2, primary_table_size);
317
318 uint64_t crc64;
319 aaruf_crc64_final(crc64_context, &crc64);
320
321 // Properly populate all header fields for multi-level DDT primary table
325 // levels, tableLevel, previousLevelOffset, negative, overflow, blockAlignmentShift,
326 // dataShift, tableShift, sizeType, entries, blocks, start are already set during creation
327 ctx->user_data_ddt_header.crc64 = crc64;
328 ctx->user_data_ddt_header.cmpCrc64 = crc64;
329 ctx->user_data_ddt_header.length = primary_table_size;
330 ctx->user_data_ddt_header.cmpLength = primary_table_size;
331
332 TRACE("Calculated CRC64 for primary DDT: 0x%16lX", crc64);
333 }
334
335 // Free CRC64 context
336 aaruf_crc64_free(crc64_context);
337
338 // First write the DDT header
340
341 size_t headerWritten = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
342 if(headerWritten != 1)
343 {
344 TRACE("Failed to write primary DDT header to file");
346 }
347
348 // Then write the table data (position is already after the header)
349 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
350
351 // Write the primary table data
352 size_t written_bytes = 0;
353 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
354
355 if(written_bytes == 1)
356 {
357 TRACE("Successfully wrote primary DDT header and table to file (%" PRIu64 " entries, %zu bytes)",
358 ctx->user_data_ddt_header.entries, primary_table_size);
359
360 // Remove any previously existing index entries of the same type before adding
361 TRACE("Removing any previously existing primary DDT index entries");
362 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
363 {
364 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
365 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeUserData)
366 {
367 TRACE("Found existing primary DDT index entry at position %d, removing", k);
368 utarray_erase(ctx->index_entries, k, 1);
369 }
370 }
371
372 // Add primary DDT to index
373 TRACE("Adding primary DDT to index");
374 IndexEntry primary_ddt_entry;
375 primary_ddt_entry.blockType = DeDuplicationTable2;
376 primary_ddt_entry.dataType = kDataTypeUserData;
377 primary_ddt_entry.offset = ctx->primary_ddt_offset;
378
379 utarray_push_back(ctx->index_entries, &primary_ddt_entry);
380 ctx->dirty_index_block = true;
381 TRACE("Added primary DDT index entry at offset %" PRIu64, ctx->primary_ddt_offset);
382 }
383 else
384 TRACE("Failed to write primary DDT table to file");
385
386 return AARUF_STATUS_OK;
387}
388
406{
407 // Flux-only images carry no DDT.
408 if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
409
410 // Write the single level DDT table block aligned just after the header
411 if(ctx->user_data_ddt_header.tableShift != 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
412
413 TRACE("Writing single-level DDT table to file");
414
415 // Calculate CRC64 of the primary DDT table data
416 const size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
417
418 // Properly populate all header fields
423 ctx->user_data_ddt_header.levels = 1; // Single level
424 ctx->user_data_ddt_header.tableLevel = 0; // Top level
425 ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
426 // negative and overflow are already set during creation
427 // blockAlignmentShift, dataShift, tableShift, sizeType, entries, blocks, start are already set
428 ctx->user_data_ddt_header.length = primary_table_size;
429 ctx->user_data_ddt_header.cmpLength = primary_table_size;
430
431 ctx->user_data_ddt_header.crc64 = aaruf_crc64_data((uint8_t *)ctx->user_data_ddt2, primary_table_size);
432
433 TRACE("Calculated CRC64 for single-level DDT: 0x%16lX", ctx->user_data_ddt_header.crc64);
434
435 uint8_t *cmp_buffer = NULL;
436 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
437
439 {
440
441 cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
443 }
444 else
445 {
446 cmp_buffer = malloc((size_t)ctx->user_data_ddt_header.length * 2); // Allocate double size for compression
447 if(cmp_buffer == NULL)
448 {
449 TRACE("Failed to allocate memory for secondary DDT v2 compression");
451 }
452
453 size_t dst_size;
454
455 if(ctx->use_zstd)
456 {
457 dst_size = aaruf_zstd_encode_buffer(cmp_buffer, (size_t)ctx->user_data_ddt_header.length * 2,
458 (uint8_t *)ctx->user_data_ddt2, ctx->user_data_ddt_header.length,
459 ctx->zstd_level, ctx->num_threads);
460 if(dst_size == 0) dst_size = ctx->user_data_ddt_header.length; // fall through to None
461 }
462 else
463 {
464 dst_size = (size_t)ctx->user_data_ddt_header.length * 2 * 2;
465 size_t props_size = LZMA_PROPERTIES_LENGTH;
466 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->user_data_ddt2,
467 ctx->user_data_ddt_header.length, lzma_properties, &props_size, 9,
468 ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
469 }
470
471 ctx->user_data_ddt_header.cmpLength = (uint32_t)dst_size;
472
474 {
476 free(cmp_buffer);
477
478 cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
479 }
480 }
481
483 {
486 }
487 else
488 {
490 aaruf_crc64_data(cmp_buffer, (uint32_t)ctx->user_data_ddt_header.cmpLength);
491 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
492 }
493
496
497 // Write the DDT header first
498 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
499 aaru_off_t ddt_position = aaruf_ftell(ctx->imageStream);
500 // Align index position to block boundary if needed
501 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
502 if(ddt_position & alignment_mask)
503 {
504 const uint64_t aligned_position = ddt_position + alignment_mask & ~alignment_mask;
505 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
506 ddt_position = aligned_position;
507 }
508
509 const size_t header_written = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
510 if(header_written != 1)
511 {
512 TRACE("Failed to write single-level DDT header to file");
514 }
515
516 // Write the primary table data
517 size_t written_bytes = 0;
519 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
520
521 written_bytes = fwrite(cmp_buffer, ctx->user_data_ddt_header.cmpLength, 1, ctx->imageStream);
522
523 if(written_bytes == 1)
524 {
525 TRACE("Successfully wrote single-level DDT header and table to file (%" PRIu64
526 " entries, %zu bytes, %zu compressed bytes)",
528
529 // Remove any previously existing index entries of the same type before adding
530 TRACE("Removing any previously existing single-level DDT index entries");
531 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
532 {
533 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
534 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeUserData)
535 {
536 TRACE("Found existing single-level DDT index entry at position %d, removing", k);
537 utarray_erase(ctx->index_entries, k, 1);
538 }
539 }
540
541 // Add single-level DDT to index
542 TRACE("Adding single-level DDT to index");
543 IndexEntry single_ddt_entry;
544 single_ddt_entry.blockType = DeDuplicationTable2;
545 single_ddt_entry.dataType = kDataTypeUserData;
546 single_ddt_entry.offset = ddt_position;
547
548 utarray_push_back(ctx->index_entries, &single_ddt_entry);
549 ctx->dirty_index_block = true;
550 TRACE("Added single-level DDT index entry at offset %" PRIu64, ddt_position);
551 }
552 else
553 TRACE("Failed to write single-level DDT table data to file");
554
555 // Free compression buffer if it was allocated
556 if(ctx->user_data_ddt_header.compression != kCompressionNone && cmp_buffer != (uint8_t *)ctx->user_data_ddt2)
557 free(cmp_buffer);
558
559 return AARUF_STATUS_OK;
560}
561
670{
671 if(!ctx->is_tape) return AARUF_STATUS_INVALID_CONTEXT;
672
673 // Flux-only images carry no DDT (defensive; tape images aren't flux-only today).
674 if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
675
676 // Traverse the tape DDT uthash and find the biggest key
677 uint64_t max_key = 0;
678 TapeDdtHashEntry *entry, *tmp;
679 HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
680 if(entry->key > max_key) max_key = entry->key;
681
682 // Initialize context user data DDT header
687 ctx->user_data_ddt_header.levels = 1; // Single level
688 ctx->user_data_ddt_header.tableLevel = 0; // Top level
689 ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
692 ctx->user_data_ddt_header.tableShift = 0; // Single level
693 ctx->user_data_ddt_header.entries = max_key + 1;
694 ctx->user_data_ddt_header.blocks = max_key + 1;
696 ctx->user_data_ddt_header.length = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
698
699 // Initialize memory for user data DDT
700 ctx->user_data_ddt2 = calloc(ctx->user_data_ddt_header.entries, sizeof(uint64_t));
701 if(ctx->user_data_ddt2 == NULL)
702 {
703 TRACE("Failed to allocate memory for tape DDT table");
705 }
706
707 // Populate user data DDT from tape DDT uthash
708 HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
709 if(entry->key < ctx->user_data_ddt_header.blocks) ctx->user_data_ddt2[entry->key] = entry->value;
710
711 // Do not repeat code
712 return write_single_level_ddt(ctx);
713}
714
732{
733 uint64_t alignment_mask;
734 uint64_t aligned_position;
735
736 // Finalize pending checksums
737 if(ctx->calculating_md5)
738 {
739 ctx->checksums.hasMd5 = true;
741 }
742 if(ctx->calculating_sha1)
743 {
744 ctx->checksums.hasSha1 = true;
746 }
747 if(ctx->calculating_sha256)
748 {
749 ctx->checksums.hasSha256 = true;
751 }
752 if(ctx->calculating_spamsum)
753 {
754 ctx->checksums.hasSpamSum = true;
755 ctx->checksums.spamsum = calloc(1, FUZZY_MAX_RESULT);
758 }
759 if(ctx->calculating_blake3)
760 {
761 ctx->checksums.hasBlake3 = true;
762 blake3_hasher_finalize(ctx->blake3_context, ctx->checksums.blake3, BLAKE3_OUT_LEN);
763 free(ctx->blake3_context);
764 }
765
766 // Write the checksums block
767 bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 ||
769
770 if(!has_checksums) return;
771
772 ChecksumHeader checksum_header = {0};
773 checksum_header.identifier = ChecksumBlock;
774
775 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
776 aaru_off_t checksum_position = aaruf_ftell(ctx->imageStream);
777 // Align index position to block boundary if needed
778 alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
779 if(checksum_position & alignment_mask)
780 {
781 aligned_position = checksum_position + alignment_mask & ~alignment_mask;
782 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
783 checksum_position = aligned_position;
784 }
785
786 // Skip checksum_header
787 aaruf_fseek(ctx->imageStream, sizeof(checksum_header), SEEK_CUR);
788
789 if(ctx->checksums.hasMd5)
790 {
791 TRACE("Writing MD5 checksum entry");
792 ChecksumEntry md5_entry = {0};
793 md5_entry.length = MD5_DIGEST_LENGTH;
794 md5_entry.type = Md5;
795 fwrite(&md5_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
796 fwrite(&ctx->checksums.md5, MD5_DIGEST_LENGTH, 1, ctx->imageStream);
797 checksum_header.length += sizeof(ChecksumEntry) + MD5_DIGEST_LENGTH;
798 checksum_header.entries++;
799 }
800
801 if(ctx->checksums.hasSha1)
802 {
803 TRACE("Writing SHA1 checksum entry");
804 ChecksumEntry sha1_entry = {0};
805 sha1_entry.length = SHA1_DIGEST_LENGTH;
806 sha1_entry.type = Sha1;
807 fwrite(&sha1_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
808 fwrite(&ctx->checksums.sha1, SHA1_DIGEST_LENGTH, 1, ctx->imageStream);
809 checksum_header.length += sizeof(ChecksumEntry) + SHA1_DIGEST_LENGTH;
810 checksum_header.entries++;
811 }
812
813 if(ctx->checksums.hasSha256)
814 {
815 TRACE("Writing SHA256 checksum entry");
816 ChecksumEntry sha256_entry = {0};
817 sha256_entry.length = SHA256_DIGEST_LENGTH;
818 sha256_entry.type = Sha256;
819 fwrite(&sha256_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
820 fwrite(&ctx->checksums.sha256, SHA256_DIGEST_LENGTH, 1, ctx->imageStream);
821 checksum_header.length += sizeof(ChecksumEntry) + SHA256_DIGEST_LENGTH;
822 checksum_header.entries++;
823 }
824
825 if(ctx->checksums.hasSpamSum)
826 {
827 TRACE("Writing SpamSum checksum entry");
828 ChecksumEntry spamsum_entry = {0};
829 spamsum_entry.length = strlen((const char *)ctx->checksums.spamsum);
830 spamsum_entry.type = SpamSum;
831 fwrite(&spamsum_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
832 fwrite(ctx->checksums.spamsum, spamsum_entry.length, 1, ctx->imageStream);
833 checksum_header.length += sizeof(ChecksumEntry) + spamsum_entry.length;
834 checksum_header.entries++;
835 }
836
837 if(ctx->checksums.hasBlake3)
838 {
839 TRACE("Writing BLAKE3 checksum entry");
840 ChecksumEntry blake3_entry = {0};
841 blake3_entry.length = BLAKE3_OUT_LEN;
842 blake3_entry.type = Blake3;
843 fwrite(&blake3_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
844 fwrite(&ctx->checksums.blake3, BLAKE3_OUT_LEN, 1, ctx->imageStream);
845 checksum_header.length += sizeof(ChecksumEntry) + BLAKE3_OUT_LEN;
846 checksum_header.entries++;
848 }
849
850 aaruf_fseek(ctx->imageStream, checksum_position, SEEK_SET);
851 TRACE("Writing checksum header");
852 fwrite(&checksum_header, sizeof(ChecksumHeader), 1, ctx->imageStream);
853
854 // Remove any previously existing index entries of the same type before adding
855 TRACE("Removing any previously existing checksum block index entries");
856 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
857 {
858 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
859 if(entry && entry->blockType == ChecksumBlock && entry->dataType == 0)
860 {
861 TRACE("Found existing checksum block index entry at position %d, removing", k);
862 utarray_erase(ctx->index_entries, k, 1);
863 }
864 }
865
866 // Add checksum block to index
867 TRACE("Adding checksum block to index");
868 IndexEntry checksum_index_entry;
869 checksum_index_entry.blockType = ChecksumBlock;
870 checksum_index_entry.dataType = 0;
871 checksum_index_entry.offset = checksum_position;
872
873 utarray_push_back(ctx->index_entries, &checksum_index_entry);
874 ctx->dirty_index_block = true;
875 TRACE("Added checksum block index entry at offset %" PRIu64, checksum_position);
876}
877
889{
890 // Write tracks block
891 if(ctx->tracks_header.entries <= 0 || ctx->track_entries == NULL) return;
892
893 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
894 aaru_off_t tracks_position = aaruf_ftell(ctx->imageStream);
895 // Align index position to block boundary if needed
896 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
897 if(tracks_position & alignment_mask)
898 {
899 uint64_t aligned_position = tracks_position + alignment_mask & ~alignment_mask;
900 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
901 tracks_position = aligned_position;
902 }
903
904 TRACE("Writing tracks block at position %ld", tracks_position);
905 // Write header
906 if(fwrite(&ctx->tracks_header, sizeof(TracksHeader), 1, ctx->imageStream) == 1)
907 {
908 // Write entries
909 size_t written_entries =
910 fwrite(ctx->track_entries, sizeof(TrackEntry), ctx->tracks_header.entries, ctx->imageStream);
911
912 if(written_entries == ctx->tracks_header.entries)
913 {
914 TRACE("Successfully wrote tracks block with %u entries", ctx->tracks_header.entries);
915
916 // Remove any previously existing index entries of the same type before adding
917 TRACE("Removing any previously existing tracks block index entries");
918 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
919 {
920 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
921 if(entry && entry->blockType == TracksBlock && entry->dataType == 0)
922 {
923 TRACE("Found existing tracks block index entry at position %d, removing", k);
924 utarray_erase(ctx->index_entries, k, 1);
925 }
926 }
927
928 // Add tracks block to index
929 TRACE("Adding tracks block to index");
930
931 IndexEntry tracks_index_entry;
932 tracks_index_entry.blockType = TracksBlock;
933 tracks_index_entry.dataType = 0;
934 tracks_index_entry.offset = tracks_position;
935 utarray_push_back(ctx->index_entries, &tracks_index_entry);
936 ctx->dirty_index_block = true;
937 TRACE("Added tracks block index entry at offset %" PRIu64, tracks_position);
938 }
939 }
940}
941
955{
956 // Write MODE 2 subheader data block
957 if(ctx->mode2_subheaders == NULL) return;
958
959 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
960 aaru_off_t mode2_subheaders_position = aaruf_ftell(ctx->imageStream);
961 // Align index position to block boundary if needed
962 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
963 if(mode2_subheaders_position & alignment_mask)
964 {
965 uint64_t aligned_position = mode2_subheaders_position + alignment_mask & ~alignment_mask;
966 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
967 mode2_subheaders_position = aligned_position;
968 }
969
970 TRACE("Writing MODE 2 subheaders block at position %ld", mode2_subheaders_position);
971 BlockHeader subheaders_block = {0};
972 subheaders_block.identifier = DataBlock;
973 subheaders_block.type = kDataTypeCdSubHeader;
974 subheaders_block.compression =
976 subheaders_block.length =
978 8;
979
980 // Calculate CRC64
981 subheaders_block.crc64 = aaruf_crc64_data(ctx->mode2_subheaders, subheaders_block.length);
982
983 uint8_t *buffer = NULL;
984 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
985
986 if(subheaders_block.compression == kCompressionNone)
987 {
988 buffer = ctx->mode2_subheaders;
989 subheaders_block.cmpCrc64 = subheaders_block.crc64;
990 }
991 else
992 {
993 buffer = malloc((size_t)subheaders_block.length * 2); // Allocate double size for compression
994 if(buffer == NULL)
995 {
996 TRACE("Failed to allocate memory for MODE 2 subheaders compression");
997 return;
998 }
999
1000 size_t dst_size;
1001
1002 if(ctx->use_zstd)
1003 {
1004 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)subheaders_block.length * 2, ctx->mode2_subheaders,
1005 subheaders_block.length, ctx->zstd_level, ctx->num_threads);
1006 if(dst_size == 0) dst_size = subheaders_block.length;
1007 }
1008 else
1009 {
1010 dst_size = (size_t)subheaders_block.length * 2 * 2;
1011 size_t props_size = LZMA_PROPERTIES_LENGTH;
1012 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->mode2_subheaders, subheaders_block.length, lzma_properties,
1013 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1014 }
1015
1016 subheaders_block.cmpLength = (uint32_t)dst_size;
1017
1018 if(subheaders_block.cmpLength >= subheaders_block.length)
1019 {
1020 subheaders_block.compression = kCompressionNone;
1021 free(buffer);
1022 buffer = ctx->mode2_subheaders;
1023 }
1024 }
1025
1026 if(subheaders_block.compression == kCompressionNone)
1027 {
1028 subheaders_block.cmpLength = subheaders_block.length;
1029 subheaders_block.cmpCrc64 = subheaders_block.crc64;
1030 }
1031 else
1032 {
1033 subheaders_block.cmpCrc64 = aaruf_crc64_data(buffer, subheaders_block.cmpLength);
1034 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1035 }
1036
1037 const size_t length_to_write = subheaders_block.cmpLength;
1038 if(subheaders_block.compression == kCompressionLzma) subheaders_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1039
1040 // Write header
1041 if(fwrite(&subheaders_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1042 {
1043 if(subheaders_block.compression == kCompressionLzma)
1044 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1045
1046 // Write data
1047 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1048 if(written_bytes == 1)
1049 {
1050 TRACE("Successfully wrote MODE 2 subheaders block (%" PRIu64 " bytes)", subheaders_block.cmpLength);
1051
1052 // Remove any previously existing index entries of the same type before adding
1053 TRACE("Removing any previously existing MODE 2 subheaders block index entries");
1054 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1055 {
1056 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1057 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeCdSubHeader)
1058 {
1059 TRACE("Found existing MODE 2 subheaders block index entry at position %d, removing", k);
1060 utarray_erase(ctx->index_entries, k, 1);
1061 }
1062 }
1063
1064 // Add MODE 2 subheaders block to index
1065 TRACE("Adding MODE 2 subheaders block to index");
1066 IndexEntry mode2_subheaders_index_entry;
1067 mode2_subheaders_index_entry.blockType = DataBlock;
1068 mode2_subheaders_index_entry.dataType = kDataTypeCdSubHeader;
1069 mode2_subheaders_index_entry.offset = mode2_subheaders_position;
1070 utarray_push_back(ctx->index_entries, &mode2_subheaders_index_entry);
1071 ctx->dirty_index_block = true;
1072 TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, mode2_subheaders_position);
1073 }
1074 }
1075
1076 if(subheaders_block.compression != kCompressionNone) free(buffer);
1077}
1078
1102{
1103 if(ctx->sector_prefix == NULL) return;
1104
1105 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1106 aaru_off_t prefix_position = aaruf_ftell(ctx->imageStream);
1107 // Align index position to block boundary if needed
1108 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1109 if(prefix_position & alignment_mask)
1110 {
1111 uint64_t aligned_position = prefix_position + alignment_mask & ~alignment_mask;
1112 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1113 prefix_position = aligned_position;
1114 }
1115
1116 TRACE("Writing sector prefix block at position %ld", prefix_position);
1117 BlockHeader prefix_block = {0};
1118 prefix_block.identifier = DataBlock;
1119 prefix_block.type = kDataTypeCdSectorPrefix;
1120 prefix_block.compression =
1122 prefix_block.length = (uint32_t)ctx->sector_prefix_offset;
1123
1124 // Calculate CRC64
1125 prefix_block.crc64 = aaruf_crc64_data(ctx->sector_prefix, prefix_block.length);
1126
1127 uint8_t *buffer = NULL;
1128 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1129
1130 if(prefix_block.compression == kCompressionNone)
1131 {
1132 buffer = ctx->sector_prefix;
1133 prefix_block.cmpCrc64 = prefix_block.crc64;
1134 }
1135 else
1136 {
1137 buffer = malloc((size_t)prefix_block.length * 2); // Allocate double size for compression
1138 if(buffer == NULL)
1139 {
1140 TRACE("Failed to allocate memory for CD sector prefix compression");
1141 return;
1142 }
1143
1144 size_t dst_size;
1145
1146 if(ctx->use_zstd)
1147 {
1148 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)prefix_block.length * 2, ctx->sector_prefix,
1149 prefix_block.length, ctx->zstd_level, ctx->num_threads);
1150 if(dst_size == 0) dst_size = prefix_block.length;
1151 }
1152 else
1153 {
1154 dst_size = (size_t)prefix_block.length * 2 * 2;
1155 size_t props_size = LZMA_PROPERTIES_LENGTH;
1156 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_prefix, prefix_block.length, lzma_properties,
1157 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1158 }
1159
1160 prefix_block.cmpLength = (uint32_t)dst_size;
1161
1162 if(prefix_block.cmpLength >= prefix_block.length)
1163 {
1164 prefix_block.compression = kCompressionNone;
1165 free(buffer);
1166 buffer = ctx->sector_prefix;
1167 }
1168 }
1169
1170 if(prefix_block.compression == kCompressionNone)
1171 {
1172 prefix_block.cmpLength = prefix_block.length;
1173 prefix_block.cmpCrc64 = prefix_block.crc64;
1174 }
1175 else
1176 {
1177 prefix_block.cmpCrc64 = aaruf_crc64_data(buffer, prefix_block.cmpLength);
1178 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1179 }
1180
1181 const size_t length_to_write = prefix_block.cmpLength;
1182 if(prefix_block.compression == kCompressionLzma) prefix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1183
1184 // Write header
1185 if(fwrite(&prefix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1186 {
1187 if(prefix_block.compression == kCompressionLzma)
1188 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1189
1190 // Write data
1191 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1192 if(written_bytes == 1)
1193 {
1194 TRACE("Successfully wrote CD sector prefix block (%" PRIu64 " bytes)", prefix_block.cmpLength);
1195
1196 // Remove any previously existing index entries of the same type before adding
1197 TRACE("Removing any previously existing CD sector prefix block index entries");
1198 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1199 {
1200 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1201 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeCdSectorPrefix)
1202 {
1203 TRACE("Found existing CD sector prefix block index entry at position %d, removing", k);
1204 utarray_erase(ctx->index_entries, k, 1);
1205 }
1206 }
1207
1208 // Add prefix block to index
1209 TRACE("Adding CD sector prefix block to index");
1210 IndexEntry prefix_index_entry;
1211 prefix_index_entry.blockType = DataBlock;
1212 prefix_index_entry.dataType = kDataTypeCdSectorPrefix;
1213 prefix_index_entry.offset = prefix_position;
1214 utarray_push_back(ctx->index_entries, &prefix_index_entry);
1215 ctx->dirty_index_block = true;
1216 TRACE("Added CD sector prefix block index entry at offset %" PRIu64, prefix_position);
1217 }
1218 }
1219
1220 if(prefix_block.compression != kCompressionNone) free(buffer);
1221}
1222
1255{
1256 if(ctx->sector_suffix == NULL) return;
1257
1258 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1259 aaru_off_t suffix_position = aaruf_ftell(ctx->imageStream);
1260 // Align index position to block boundary if needed
1261 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1262 if(suffix_position & alignment_mask)
1263 {
1264 const uint64_t aligned_position = suffix_position + alignment_mask & ~alignment_mask;
1265 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1266 suffix_position = aligned_position;
1267 }
1268
1269 TRACE("Writing sector suffix block at position %ld", suffix_position);
1270 BlockHeader suffix_block = {0};
1271 suffix_block.identifier = DataBlock;
1272 suffix_block.type = kDataTypeCdSectorSuffix;
1273 suffix_block.compression =
1275 suffix_block.length = (uint32_t)ctx->sector_suffix_offset;
1276
1277 // Calculate CRC64
1278 suffix_block.crc64 = aaruf_crc64_data(ctx->sector_suffix, suffix_block.length);
1279
1280 uint8_t *buffer = NULL;
1281 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1282
1283 if(suffix_block.compression == kCompressionNone)
1284 {
1285 buffer = ctx->sector_suffix;
1286 suffix_block.cmpCrc64 = suffix_block.crc64;
1287 }
1288 else
1289 {
1290 buffer = malloc((size_t)suffix_block.length * 2); // Allocate double size for compression
1291 if(buffer == NULL)
1292 {
1293 TRACE("Failed to allocate memory for CD sector suffix compression");
1294 return;
1295 }
1296
1297 size_t dst_size;
1298
1299 if(ctx->use_zstd)
1300 {
1301 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)suffix_block.length * 2, ctx->sector_suffix,
1302 suffix_block.length, ctx->zstd_level, ctx->num_threads);
1303 if(dst_size == 0) dst_size = suffix_block.length;
1304 }
1305 else
1306 {
1307 dst_size = (size_t)suffix_block.length * 2 * 2;
1308 size_t props_size = LZMA_PROPERTIES_LENGTH;
1309 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_suffix, suffix_block.length, lzma_properties,
1310 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1311 }
1312
1313 suffix_block.cmpLength = (uint32_t)dst_size;
1314
1315 if(suffix_block.cmpLength >= suffix_block.length)
1316 {
1317 suffix_block.compression = kCompressionNone;
1318 free(buffer);
1319 buffer = ctx->sector_suffix;
1320 }
1321 }
1322
1323 if(suffix_block.compression == kCompressionNone)
1324 {
1325 suffix_block.cmpLength = suffix_block.length;
1326 suffix_block.cmpCrc64 = suffix_block.crc64;
1327 }
1328 else
1329 {
1330 suffix_block.cmpCrc64 = aaruf_crc64_data(buffer, suffix_block.cmpLength);
1331 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1332 }
1333
1334 const size_t length_to_write = suffix_block.cmpLength;
1335 if(suffix_block.compression == kCompressionLzma) suffix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1336
1337 // Write header
1338 if(fwrite(&suffix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1339 {
1340 if(suffix_block.compression == kCompressionLzma)
1341 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1342
1343 // Write data
1344 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1345 if(written_bytes == 1)
1346 {
1347 TRACE("Successfully wrote CD sector suffix block (%" PRIu64 " bytes)", suffix_block.cmpLength);
1348
1349 // Remove any previously existing index entries of the same type before adding
1350 TRACE("Removing any previously existing CD sector suffix block index entries");
1351 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1352 {
1353 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1354 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeCdSectorSuffix)
1355 {
1356 TRACE("Found existing CD sector suffix block index entry at position %d, removing", k);
1357 utarray_erase(ctx->index_entries, k, 1);
1358 }
1359 }
1360
1361 // Add suffix block to index
1362 TRACE("Adding CD sector suffix block to index");
1363 IndexEntry suffix_index_entry;
1364 suffix_index_entry.blockType = DataBlock;
1365 suffix_index_entry.dataType = kDataTypeCdSectorSuffix;
1366 suffix_index_entry.offset = suffix_position;
1367 utarray_push_back(ctx->index_entries, &suffix_index_entry);
1368 ctx->dirty_index_block = true;
1369 TRACE("Added CD sector suffix block index entry at offset %" PRIu64, suffix_position);
1370 }
1371 }
1372
1373 if(suffix_block.compression != kCompressionNone) free(buffer);
1374}
1375
1404{
1405 if(ctx->sector_prefix_ddt2 == NULL) return;
1406
1407 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1408 aaru_off_t prefix_ddt_position = aaruf_ftell(ctx->imageStream);
1409 // Align index position to block boundary if needed
1410 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1411 if(prefix_ddt_position & alignment_mask)
1412 {
1413 const uint64_t aligned_position = prefix_ddt_position + alignment_mask & ~alignment_mask;
1414 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1415 prefix_ddt_position = aligned_position;
1416 }
1417
1418 TRACE("Writing sector prefix DDT v2 at position %ld", prefix_ddt_position);
1419 DdtHeader2 ddt_header2 = {0};
1420 ddt_header2.identifier = DeDuplicationTable2;
1421 ddt_header2.type = kDataTypeCdSectorPrefix;
1422 ddt_header2.compression =
1424 ddt_header2.levels = 1;
1425 ddt_header2.tableLevel = 0;
1426 ddt_header2.negative = ctx->user_data_ddt_header.negative;
1427 ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
1429 ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
1430 ddt_header2.tableShift = 0; // Single-level DDT
1431 ddt_header2.entries =
1433 ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
1434 ddt_header2.start = 0;
1435 ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
1436 // Calculate CRC64
1437 ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_prefix_ddt2, (uint32_t)ddt_header2.length);
1438
1439 uint8_t *buffer = NULL;
1440 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1441
1442 if(ddt_header2.compression == kCompressionNone)
1443 {
1444 buffer = (uint8_t *)ctx->sector_prefix_ddt2;
1445 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1446 }
1447 else
1448 {
1449 buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
1450 if(buffer == NULL)
1451 {
1452 TRACE("Failed to allocate memory for sector prefix DDT v2 compression");
1453 return;
1454 }
1455
1456 size_t dst_size;
1457
1458 if(ctx->use_zstd)
1459 {
1460 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ddt_header2.length * 2,
1461 (uint8_t *)ctx->sector_prefix_ddt2, ddt_header2.length,
1462 ctx->zstd_level, ctx->num_threads);
1463 if(dst_size == 0) dst_size = ddt_header2.length;
1464 }
1465 else
1466 {
1467 dst_size = (size_t)ddt_header2.length * 2 * 2;
1468 size_t props_size = LZMA_PROPERTIES_LENGTH;
1469 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_prefix_ddt2, ddt_header2.length,
1470 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273,
1471 LZMA_THREADS(ctx));
1472 }
1473
1474 ddt_header2.cmpLength = (uint32_t)dst_size;
1475
1476 if(ddt_header2.cmpLength >= ddt_header2.length)
1477 {
1478 ddt_header2.compression = kCompressionNone;
1479 free(buffer);
1480 buffer = (uint8_t *)ctx->sector_prefix_ddt2;
1481 }
1482 }
1483
1484 if(ddt_header2.compression == kCompressionNone)
1485 {
1486 ddt_header2.cmpLength = ddt_header2.length;
1487 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1488 }
1489 else
1490 {
1491 ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
1492 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1493 }
1494
1495 const size_t length_to_write = ddt_header2.cmpLength;
1496 if(ddt_header2.compression == kCompressionLzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
1497
1498 // Write header
1499 if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
1500 {
1501 if(ddt_header2.compression == kCompressionLzma)
1502 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1503
1504 // Write data
1505 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1506 if(written_bytes == 1)
1507 {
1508 TRACE("Successfully wrote sector prefix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
1509
1510 // Remove any previously existing index entries of the same type before adding
1511 TRACE("Removing any previously existing sector prefix DDT v2 index entries");
1512 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1513 {
1514 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1515 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeCdSectorPrefix)
1516 {
1517 TRACE("Found existing sector prefix DDT v2 index entry at position %d, removing", k);
1518 utarray_erase(ctx->index_entries, k, 1);
1519 }
1520 }
1521
1522 // Add prefix block to index
1523 TRACE("Adding sector prefix DDT v2 to index");
1524 IndexEntry prefix_ddt_index_entry;
1525 prefix_ddt_index_entry.blockType = DeDuplicationTable2;
1526 prefix_ddt_index_entry.dataType = kDataTypeCdSectorPrefix;
1527 prefix_ddt_index_entry.offset = prefix_ddt_position;
1528 utarray_push_back(ctx->index_entries, &prefix_ddt_index_entry);
1529 ctx->dirty_index_block = true;
1530 TRACE("Added sector prefix DDT v2 index entry at offset %" PRIu64, prefix_ddt_position);
1531 }
1532 }
1533
1534 if(ddt_header2.compression != kCompressionNone) free(buffer);
1535}
1536
1581{
1582 if(ctx->sector_suffix_ddt2 == NULL) return;
1583
1584 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1585 aaru_off_t suffix_ddt_position = aaruf_ftell(ctx->imageStream);
1586 // Align index position to block boundary if needed
1587 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1588 if(suffix_ddt_position & alignment_mask)
1589 {
1590 const uint64_t aligned_position = suffix_ddt_position + alignment_mask & ~alignment_mask;
1591 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1592 suffix_ddt_position = aligned_position;
1593 }
1594
1595 TRACE("Writing sector suffix DDT v2 at position %ld", suffix_ddt_position);
1596 DdtHeader2 ddt_header2 = {0};
1597 ddt_header2.identifier = DeDuplicationTable2;
1598 ddt_header2.type = kDataTypeCdSectorSuffix;
1599 ddt_header2.compression =
1601 ddt_header2.levels = 1;
1602 ddt_header2.tableLevel = 0;
1603 ddt_header2.negative = ctx->user_data_ddt_header.negative;
1604 ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
1606 ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
1607 ddt_header2.tableShift = 0; // Single-level DDT
1608 ddt_header2.entries =
1610 ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
1611 ddt_header2.start = 0;
1612 ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
1613 // Calculate CRC64
1614 ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_suffix_ddt2, (uint32_t)ddt_header2.length);
1615
1616 uint8_t *buffer = NULL;
1617 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1618
1619 if(ddt_header2.compression == kCompressionNone)
1620 {
1621 buffer = (uint8_t *)ctx->sector_suffix_ddt2;
1622 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1623 }
1624 else
1625 {
1626 buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
1627 if(buffer == NULL)
1628 {
1629 TRACE("Failed to allocate memory for sector suffix DDT v2 compression");
1630 return;
1631 }
1632
1633 size_t dst_size;
1634
1635 if(ctx->use_zstd)
1636 {
1637 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ddt_header2.length * 2,
1638 (uint8_t *)ctx->sector_suffix_ddt2, ddt_header2.length,
1639 ctx->zstd_level, ctx->num_threads);
1640 if(dst_size == 0) dst_size = ddt_header2.length;
1641 }
1642 else
1643 {
1644 dst_size = (size_t)ddt_header2.length * 2 * 2;
1645 size_t props_size = LZMA_PROPERTIES_LENGTH;
1646 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_suffix_ddt2, ddt_header2.length,
1647 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273,
1648 LZMA_THREADS(ctx));
1649 }
1650
1651 ddt_header2.cmpLength = (uint32_t)dst_size;
1652
1653 if(ddt_header2.cmpLength >= ddt_header2.length)
1654 {
1655 ddt_header2.compression = kCompressionNone;
1656 free(buffer);
1657 buffer = (uint8_t *)ctx->sector_suffix_ddt2;
1658 }
1659 }
1660
1661 if(ddt_header2.compression == kCompressionNone)
1662 {
1663 ddt_header2.cmpLength = ddt_header2.length;
1664 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1665 }
1666 else
1667 {
1668 ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
1669 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1670 }
1671
1672 const size_t length_to_write = ddt_header2.cmpLength;
1673 if(ddt_header2.compression == kCompressionLzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
1674
1675 // Write header
1676 if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
1677 {
1678 if(ddt_header2.compression == kCompressionLzma)
1679 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1680
1681 // Write data
1682 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1683 if(written_bytes == 1)
1684 {
1685 TRACE("Successfully wrote sector suffix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
1686
1687 // Remove any previously existing index entries of the same type before adding
1688 TRACE("Removing any previously existing sector suffix DDT v2 block index entries");
1689 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1690 {
1691 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1692 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == kDataTypeCdSectorSuffix)
1693 {
1694 TRACE("Found existing sector suffix DDT v2 index entry at position %d, removing", k);
1695 utarray_erase(ctx->index_entries, k, 1);
1696 }
1697 }
1698
1699 // Add suffix block to index
1700 TRACE("Adding sector suffix DDT v2 to index");
1701 IndexEntry suffix_ddt_index_entry;
1702 suffix_ddt_index_entry.blockType = DeDuplicationTable2;
1703 suffix_ddt_index_entry.dataType = kDataTypeCdSectorSuffix;
1704 suffix_ddt_index_entry.offset = suffix_ddt_position;
1705 utarray_push_back(ctx->index_entries, &suffix_ddt_index_entry);
1706 ctx->dirty_index_block = true;
1707 TRACE("Added sector suffix DDT v2 index entry at offset %" PRIu64, suffix_ddt_position);
1708 }
1709 }
1710
1711 if(ddt_header2.compression != kCompressionNone) free(buffer);
1712}
1713
1772{
1773 if(ctx->sector_subchannel == NULL) return;
1774
1775 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1776 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
1777 // Align index position to block boundary if needed
1778 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1779 if(block_position & alignment_mask)
1780 {
1781 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
1782 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
1783 block_position = aligned_position;
1784 }
1785
1786 TRACE("Writing sector subchannel block at position %ld", block_position);
1787 BlockHeader subchannel_block = {0};
1788 subchannel_block.identifier = DataBlock;
1789 subchannel_block.compression = kCompressionNone;
1790
1791 uint8_t *buffer = ctx->sector_subchannel;
1792 bool owns_buffer = false;
1793 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1794
1796 {
1797 subchannel_block.type = kDataTypeCdSubchannel;
1798 subchannel_block.length = (uint32_t)(ctx->user_data_ddt_header.negative + ctx->image_info.Sectors +
1800 96;
1801 subchannel_block.cmpLength = subchannel_block.length;
1802
1803 if(ctx->compression_enabled)
1804 {
1805 uint8_t *cst_buffer = malloc(subchannel_block.length);
1806
1807 if(cst_buffer == NULL)
1808 {
1809 TRACE("Failed to allocate memory for Claunia Subchannel Transform output");
1810 return;
1811 }
1812
1813 uint8_t *dst_buffer = malloc(subchannel_block.length);
1814
1815 if(dst_buffer == NULL)
1816 {
1817 TRACE("Failed to allocate memory for compressed output");
1818 free(cst_buffer);
1819 return;
1820 }
1821
1822 aaruf_cst_transform(ctx->sector_subchannel, cst_buffer, subchannel_block.length);
1823
1824 size_t dst_size;
1825
1826 if(ctx->use_zstd)
1827 {
1828 dst_size = aaruf_zstd_encode_buffer(dst_buffer, subchannel_block.length, cst_buffer,
1829 subchannel_block.length, ctx->zstd_level, ctx->num_threads);
1830 if(dst_size == 0) dst_size = subchannel_block.length; /* compression failed, fall through to none */
1831 }
1832 else
1833 {
1834 dst_size = subchannel_block.length;
1835 size_t props_size = LZMA_PROPERTIES_LENGTH;
1836
1837 aaruf_lzma_encode_buffer(dst_buffer, &dst_size, cst_buffer, subchannel_block.length, lzma_properties,
1838 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1839 }
1840
1841 free(cst_buffer);
1842
1843 if(dst_size < subchannel_block.length)
1844 {
1845 subchannel_block.compression = ctx->use_zstd ? kCompressionZstdCst : kCompressionLzmaCst;
1846 subchannel_block.cmpLength = (uint32_t)dst_size;
1847 buffer = dst_buffer;
1848 owns_buffer = true;
1849 }
1850 else
1851 {
1852 subchannel_block.compression = kCompressionNone;
1853 free(dst_buffer);
1854 subchannel_block.cmpLength = subchannel_block.length;
1855 }
1856 }
1857 }
1858 else if(ctx->image_info.MetadataMediaType == BlockMedia)
1859 {
1860 switch(ctx->image_info.MediaType)
1861 {
1862 case AppleProfile:
1863 case AppleFileWare:
1864 subchannel_block.type = kDataTypeAppleProfileTag;
1865 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 20;
1866 break;
1867 case AppleSonyDS:
1868 case AppleSonySS:
1869 subchannel_block.type = kDataTypeAppleSonyTag;
1870 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 12;
1871 break;
1872 case PriamDataTower:
1873 subchannel_block.type = kDataTypePriamDataTowerTag;
1874 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 24;
1875 break;
1876 default:
1877 TRACE("Incorrect media type, not writing sector subchannel block");
1878 return; // Incorrect media type
1879 }
1880 subchannel_block.cmpLength = subchannel_block.length;
1881
1882 uint8_t *dst_buffer = malloc(subchannel_block.length);
1883
1884 if(dst_buffer == NULL)
1885 {
1886 TRACE("Failed to allocate memory for compressed output");
1887 return;
1888 }
1889
1890 size_t dst_size;
1891
1892 if(ctx->use_zstd)
1893 {
1894 dst_size = aaruf_zstd_encode_buffer(dst_buffer, subchannel_block.length, ctx->sector_subchannel,
1895 subchannel_block.length, ctx->zstd_level, ctx->num_threads);
1896 if(dst_size == 0) dst_size = subchannel_block.length; /* compression failed, fall through to none */
1897 }
1898 else
1899 {
1900 dst_size = subchannel_block.length;
1901 size_t props_size = LZMA_PROPERTIES_LENGTH;
1902
1903 aaruf_lzma_encode_buffer(dst_buffer, &dst_size, ctx->sector_subchannel, subchannel_block.length,
1904 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
1905 }
1906
1907 if(dst_size < subchannel_block.length)
1908 {
1909 subchannel_block.compression = ctx->use_zstd ? kCompressionZstd : kCompressionLzma;
1910 subchannel_block.cmpLength = (uint32_t)dst_size;
1911 buffer = dst_buffer;
1912 owns_buffer = true;
1913 }
1914 else
1915 {
1916 subchannel_block.compression = kCompressionNone;
1917 free(dst_buffer);
1918 subchannel_block.cmpLength = subchannel_block.length;
1919 }
1920 }
1921 else
1922 {
1923 TRACE("Incorrect media type, not writing sector subchannel block");
1924 return; // Incorrect media type
1925 }
1926
1927 // Calculate CRC64 for raw subchannel data and compressed payload when present
1928 subchannel_block.crc64 = aaruf_crc64_data(ctx->sector_subchannel, subchannel_block.length);
1929 if(subchannel_block.compression == kCompressionNone)
1930 subchannel_block.cmpCrc64 = subchannel_block.crc64;
1931 else
1932 {
1933 subchannel_block.cmpCrc64 = aaruf_crc64_data(buffer, subchannel_block.cmpLength);
1934 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
1935 }
1936
1937 const size_t length_to_write = subchannel_block.cmpLength;
1938 const bool has_lzma_props = subchannel_block.compression == kCompressionLzma ||
1939 subchannel_block.compression == kCompressionLzmaCst;
1940 if(has_lzma_props) subchannel_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1941
1942 // Write header
1943 if(fwrite(&subchannel_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1944 {
1945 if(has_lzma_props)
1946 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1947
1948 // Write data
1949 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1950 if(written_bytes == 1)
1951 {
1952 TRACE("Successfully wrote sector subchannel block (%" PRIu64 " bytes)", subchannel_block.cmpLength);
1953
1954 // Remove any previously existing index entries of the same type before adding
1955 TRACE("Removing any previously existing subchannel block index entries");
1956 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1957 {
1958 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1959 if(entry && entry->blockType == DataBlock && entry->dataType == subchannel_block.type)
1960 {
1961 TRACE("Found existing subchannel block index entry at position %d, removing", k);
1962 utarray_erase(ctx->index_entries, k, 1);
1963 }
1964 }
1965
1966 // Add subchannel block to index
1967 TRACE("Adding sector subchannel block to index");
1968 IndexEntry subchannel_index_entry;
1969 subchannel_index_entry.blockType = DataBlock;
1970 subchannel_index_entry.dataType = subchannel_block.type;
1971 subchannel_index_entry.offset = block_position;
1972 utarray_push_back(ctx->index_entries, &subchannel_index_entry);
1973 ctx->dirty_index_block = true;
1974 TRACE("Added sector subchannel block index entry at offset %" PRIu64, block_position);
1975 }
1976 }
1977
1978 if(owns_buffer) free(buffer);
1979}
1980
2118{
2119 if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL || ctx->sector_edc == NULL)
2120 return;
2121
2122 uint64_t total_sectors =
2124
2125 // Write DVD sector ID block
2126 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2127 aaru_off_t id_position = aaruf_ftell(ctx->imageStream);
2128 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2129 if(id_position & alignment_mask)
2130 {
2131 const uint64_t aligned_position = id_position + alignment_mask & ~alignment_mask;
2132 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2133 id_position = aligned_position;
2134 }
2135 TRACE("Writing DVD sector ID block at position %ld", id_position);
2136 BlockHeader id_block = {0};
2137 id_block.identifier = DataBlock;
2138 id_block.type = kDataTypeDvdSectorId;
2139 id_block.compression =
2141 id_block.length = (uint32_t)total_sectors * 4;
2142
2143 // Calculate CRC64
2144 id_block.crc64 = aaruf_crc64_data(ctx->sector_id, id_block.length);
2145
2146 uint8_t *buffer = NULL;
2147 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2148
2149 if(id_block.compression == kCompressionNone)
2150 {
2151 buffer = ctx->sector_id;
2152 id_block.cmpCrc64 = id_block.crc64;
2153 }
2154 else
2155 {
2156 buffer = malloc((size_t)id_block.length * 2); // Allocate double size for compression
2157 if(buffer == NULL)
2158 {
2159 TRACE("Failed to allocate memory for DVD sector ID compression");
2160 return;
2161 }
2162
2163 size_t dst_size;
2164
2165 if(ctx->use_zstd)
2166 {
2167 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)id_block.length * 2, ctx->sector_id, id_block.length,
2168 ctx->zstd_level, ctx->num_threads);
2169 if(dst_size == 0) dst_size = id_block.length;
2170 }
2171 else
2172 {
2173 dst_size = (size_t)id_block.length * 2 * 2;
2174 size_t props_size = LZMA_PROPERTIES_LENGTH;
2175 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_id, id_block.length, lzma_properties, &props_size,
2176 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2177 }
2178
2179 id_block.cmpLength = (uint32_t)dst_size;
2180
2181 if(id_block.cmpLength >= id_block.length)
2182 {
2183 id_block.compression = kCompressionNone;
2184 free(buffer);
2185 buffer = ctx->sector_id;
2186 }
2187 }
2188
2189 if(id_block.compression == kCompressionNone)
2190 {
2191 id_block.cmpLength = id_block.length;
2192 id_block.cmpCrc64 = id_block.crc64;
2193 }
2194 else
2195 {
2196 id_block.cmpCrc64 = aaruf_crc64_data(buffer, id_block.cmpLength);
2197 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2198 }
2199
2200 size_t length_to_write = id_block.cmpLength;
2202
2203 // Write header
2204 if(fwrite(&id_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2205 {
2206 if(id_block.compression == kCompressionLzma)
2207 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2208
2209 // Write data
2210 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2211 if(written_bytes == 1)
2212 {
2213 TRACE("Successfully wrote DVD sector ID block (%" PRIu64 " bytes)", id_block.cmpLength);
2214
2215 // Remove any previously existing index entries of the same type before adding
2216 TRACE("Removing any previously existing DVD sector ID block index entries");
2217 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2218 {
2219 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2220 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorId)
2221 {
2222 TRACE("Found existing DVD sector ID block index entry at position %d, removing", k);
2223 utarray_erase(ctx->index_entries, k, 1);
2224 }
2225 }
2226
2227 // Add ID block to index
2228 TRACE("Adding DVD sector ID block to index");
2229 IndexEntry id_index_entry;
2230 id_index_entry.blockType = DataBlock;
2231 id_index_entry.dataType = kDataTypeDvdSectorId;
2232 id_index_entry.offset = id_position;
2233 utarray_push_back(ctx->index_entries, &id_index_entry);
2234 ctx->dirty_index_block = true;
2235 TRACE("Added DVD sector ID block index entry at offset %" PRIu64, id_position);
2236 }
2237 }
2238
2239 if(id_block.compression != kCompressionNone) free(buffer);
2240
2241 // Write DVD sector IED block
2242 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2243 aaru_off_t ied_position = aaruf_ftell(ctx->imageStream);
2244 if(ied_position & alignment_mask)
2245 {
2246 const uint64_t aligned_position = ied_position + alignment_mask & ~alignment_mask;
2247 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2248 ied_position = aligned_position;
2249 }
2250 TRACE("Writing DVD sector IED block at position %ld", ied_position);
2251 BlockHeader ied_block = {0};
2252 ied_block.identifier = DataBlock;
2253 ied_block.type = kDataTypeDvdSectorIed;
2254 ied_block.compression =
2256 ied_block.length = (uint32_t)total_sectors * 2;
2257 // Calculate CRC64
2258 ied_block.crc64 = aaruf_crc64_data(ctx->sector_ied, ied_block.length);
2259
2260 buffer = NULL;
2261
2262 if(ied_block.compression == kCompressionNone)
2263 {
2264 buffer = ctx->sector_ied;
2265 ied_block.cmpCrc64 = ied_block.crc64;
2266 }
2267 else
2268 {
2269 buffer = malloc((size_t)ied_block.length * 2); // Allocate double size for compression
2270 if(buffer == NULL)
2271 {
2272 TRACE("Failed to allocate memory for DVD sector IED compression");
2273 return;
2274 }
2275
2276 size_t dst_size;
2277
2278 if(ctx->use_zstd)
2279 {
2280 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)ied_block.length * 2, ctx->sector_ied,
2281 ied_block.length, ctx->zstd_level, ctx->num_threads);
2282 if(dst_size == 0) dst_size = ied_block.length;
2283 }
2284 else
2285 {
2286 dst_size = (size_t)ied_block.length * 2 * 2;
2287 size_t props_size = LZMA_PROPERTIES_LENGTH;
2288 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_ied, ied_block.length, lzma_properties, &props_size,
2289 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2290 }
2291
2292 ied_block.cmpLength = (uint32_t)dst_size;
2293
2294 if(ied_block.cmpLength >= ied_block.length)
2295 {
2296 ied_block.compression = kCompressionNone;
2297 free(buffer);
2298 buffer = ctx->sector_ied;
2299 }
2300 }
2301
2302 if(ied_block.compression == kCompressionNone)
2303 {
2304 ied_block.cmpLength = ied_block.length;
2305 ied_block.cmpCrc64 = ied_block.crc64;
2306 }
2307 else
2308 {
2309 ied_block.cmpCrc64 = aaruf_crc64_data(buffer, ied_block.cmpLength);
2310 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2311 }
2312
2313 length_to_write = ied_block.cmpLength;
2314 if(ied_block.compression == kCompressionLzma) ied_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2315
2316 // Write header
2317 if(fwrite(&ied_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2318 {
2319 if(ied_block.compression == kCompressionLzma)
2320 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2321
2322 // Write data
2323 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2324 if(written_bytes == 1)
2325 {
2326 TRACE("Successfully wrote DVD sector IED block (%" PRIu64 " bytes)", ied_block.cmpLength);
2327
2328 // Remove any previously existing index entries of the same type before adding
2329 TRACE("Removing any previously existing DVD sector IED block index entries");
2330 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2331 {
2332 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2333 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorIed)
2334 {
2335 TRACE("Found existing DVD sector IED block index entry at position %d, removing", k);
2336 utarray_erase(ctx->index_entries, k, 1);
2337 }
2338 }
2339
2340 // Add IED block to index
2341 TRACE("Adding DVD sector IED block to index");
2342 IndexEntry ied_index_entry;
2343 ied_index_entry.blockType = DataBlock;
2344 ied_index_entry.dataType = kDataTypeDvdSectorIed;
2345 ied_index_entry.offset = ied_position;
2346 utarray_push_back(ctx->index_entries, &ied_index_entry);
2347 ctx->dirty_index_block = true;
2348 TRACE("Added DVD sector IED block index entry at offset %" PRIu64, ied_position);
2349 }
2350 }
2351
2352 if(ied_block.compression != kCompressionNone) free(buffer);
2353
2354 // Write DVD sector CPR/MAI block
2355 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2356 aaru_off_t cpr_mai_position = aaruf_ftell(ctx->imageStream);
2357 if(cpr_mai_position & alignment_mask)
2358 {
2359 const uint64_t aligned_position = cpr_mai_position + alignment_mask & ~alignment_mask;
2360 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2361 cpr_mai_position = aligned_position;
2362 }
2363 TRACE("Writing DVD sector CPR/MAI block at position %ld", cpr_mai_position);
2364 BlockHeader cpr_mai_block = {0};
2365 cpr_mai_block.identifier = DataBlock;
2366 cpr_mai_block.type = kDataTypeDvdSectorCprMai;
2367 cpr_mai_block.compression =
2369 cpr_mai_block.length = (uint32_t)total_sectors * 6;
2370 // Calculate CRC64
2371 cpr_mai_block.crc64 = aaruf_crc64_data(ctx->sector_cpr_mai, cpr_mai_block.length);
2372
2373 buffer = NULL;
2374
2375 if(cpr_mai_block.compression == kCompressionNone)
2376 {
2377 buffer = ctx->sector_cpr_mai;
2378 cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
2379 }
2380 else
2381 {
2382 buffer = malloc((size_t)cpr_mai_block.length * 2); // Allocate double size for compression
2383 if(buffer == NULL)
2384 {
2385 TRACE("Failed to allocate memory for DVD sector CPR/MAI compression");
2386 return;
2387 }
2388
2389 size_t dst_size;
2390
2391 if(ctx->use_zstd)
2392 {
2393 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)cpr_mai_block.length * 2, ctx->sector_cpr_mai,
2394 cpr_mai_block.length, ctx->zstd_level, ctx->num_threads);
2395 if(dst_size == 0) dst_size = cpr_mai_block.length;
2396 }
2397 else
2398 {
2399 dst_size = (size_t)cpr_mai_block.length * 2 * 2;
2400 size_t props_size = LZMA_PROPERTIES_LENGTH;
2401 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_cpr_mai, cpr_mai_block.length, lzma_properties,
2402 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2403 }
2404
2405 cpr_mai_block.cmpLength = (uint32_t)dst_size;
2406
2407 if(cpr_mai_block.cmpLength >= cpr_mai_block.length)
2408 {
2409 cpr_mai_block.compression = kCompressionNone;
2410 free(buffer);
2411 buffer = ctx->sector_cpr_mai;
2412 }
2413 }
2414
2415 if(cpr_mai_block.compression == kCompressionNone)
2416 {
2417 cpr_mai_block.cmpLength = cpr_mai_block.length;
2418 cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
2419 }
2420 else
2421 {
2422 cpr_mai_block.cmpCrc64 = aaruf_crc64_data(buffer, cpr_mai_block.cmpLength);
2423 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2424 }
2425
2426 length_to_write = cpr_mai_block.cmpLength;
2427 if(cpr_mai_block.compression == kCompressionLzma) cpr_mai_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2428
2429 // Write header
2430 if(fwrite(&cpr_mai_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2431 {
2432 if(cpr_mai_block.compression == kCompressionLzma)
2433 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2434
2435 // Write data
2436 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2437 if(written_bytes == 1)
2438 {
2439 TRACE("Successfully wrote DVD sector CPR/MAI block (%" PRIu64 " bytes)", cpr_mai_block.cmpLength);
2440
2441 // Remove any previously existing index entries of the same type before adding
2442 TRACE("Removing any previously existing DVD sector CPR/MAI block index entries");
2443 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2444 {
2445 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2446 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorCprMai)
2447 {
2448 TRACE("Found existing DVD sector CPR/MAI block index entry at position %d, removing", k);
2449 utarray_erase(ctx->index_entries, k, 1);
2450 }
2451 }
2452
2453 // Add CPR/MAI block to index
2454 TRACE("Adding DVD sector CPR/MAI block to index");
2455 IndexEntry cpr_mai_index_entry;
2456 cpr_mai_index_entry.blockType = DataBlock;
2457 cpr_mai_index_entry.dataType = kDataTypeDvdSectorCprMai;
2458 cpr_mai_index_entry.offset = cpr_mai_position;
2459 utarray_push_back(ctx->index_entries, &cpr_mai_index_entry);
2460 ctx->dirty_index_block = true;
2461 TRACE("Added DVD sector CPR/MAI block index entry at offset %" PRIu64, cpr_mai_position);
2462 }
2463 }
2464
2465 if(cpr_mai_block.compression != kCompressionNone) free(buffer);
2466
2467 // Write DVD sector EDC block
2468 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2469 aaru_off_t edc_position = aaruf_ftell(ctx->imageStream);
2470 if(edc_position & alignment_mask)
2471 {
2472 const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
2473 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2474 edc_position = aligned_position;
2475 }
2476 TRACE("Writing DVD sector EDC block at position %ld", edc_position);
2477 BlockHeader edc_block = {0};
2478 edc_block.identifier = DataBlock;
2479 edc_block.type = kDataTypeDvdSectorEdc;
2480 edc_block.compression =
2482 edc_block.length = (uint32_t)total_sectors * 4;
2483 // Calculate CRC64
2484 edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
2485
2486 buffer = NULL;
2487
2488 if(edc_block.compression == kCompressionNone)
2489 {
2490 buffer = ctx->sector_edc;
2491 edc_block.cmpCrc64 = edc_block.crc64;
2492 }
2493 else
2494 {
2495 buffer = malloc((size_t)edc_block.length * 2); // Allocate double size for compression
2496 if(buffer == NULL)
2497 {
2498 TRACE("Failed to allocate memory for DVD sector EDC compression");
2499 return;
2500 }
2501
2502 size_t dst_size;
2503
2504 if(ctx->use_zstd)
2505 {
2506 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)edc_block.length * 2, ctx->sector_edc,
2507 edc_block.length, ctx->zstd_level, ctx->num_threads);
2508 if(dst_size == 0) dst_size = edc_block.length;
2509 }
2510 else
2511 {
2512 dst_size = (size_t)edc_block.length * 2 * 2;
2513 size_t props_size = LZMA_PROPERTIES_LENGTH;
2514 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_edc, edc_block.length, lzma_properties,
2515 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2516 }
2517
2518 edc_block.cmpLength = (uint32_t)dst_size;
2519
2520 if(edc_block.cmpLength >= edc_block.length)
2521 {
2522 edc_block.compression = kCompressionNone;
2523 free(buffer);
2524 buffer = ctx->sector_edc;
2525 }
2526 }
2527
2528 if(edc_block.compression == kCompressionNone)
2529 {
2530 edc_block.cmpLength = edc_block.length;
2531 edc_block.cmpCrc64 = edc_block.crc64;
2532 }
2533 else
2534 {
2535 edc_block.cmpCrc64 = aaruf_crc64_data(buffer, edc_block.cmpLength);
2536 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2537 }
2538
2539 length_to_write = edc_block.cmpLength;
2540 if(edc_block.compression == kCompressionLzma) edc_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2541
2542 // Write header
2543 if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2544 {
2545 if(edc_block.compression == kCompressionLzma)
2546 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2547
2548 // Write data
2549 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2550 if(written_bytes == 1)
2551 {
2552 TRACE("Successfully wrote DVD sector EDC block (%" PRIu64 " bytes)", edc_block.cmpLength);
2553
2554 // Remove any previously existing index entries of the same type before adding
2555 TRACE("Removing any previously existing DVD sector EDC block index entries");
2556 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2557 {
2558 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2559 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdSectorEdc)
2560 {
2561 TRACE("Found existing DVD sector EDC block index entry at position %d, removing", k);
2562 utarray_erase(ctx->index_entries, k, 1);
2563 }
2564 }
2565
2566 // Add EDC block to index
2567 TRACE("Adding DVD sector EDC block to index");
2568 IndexEntry edc_index_entry;
2569 edc_index_entry.blockType = DataBlock;
2570 edc_index_entry.dataType = kDataTypeDvdSectorEdc;
2571 edc_index_entry.offset = edc_position;
2572 utarray_push_back(ctx->index_entries, &edc_index_entry);
2573 ctx->dirty_index_block = true;
2574 TRACE("Added DVD sector EDC block index entry at offset %" PRIu64, edc_position);
2575 }
2576 }
2577
2578 if(edc_block.compression != kCompressionNone) free(buffer);
2579}
2580
2678{
2679 if(ctx->sector_edc == NULL) return;
2680
2681 uint64_t total_sectors =
2683
2684 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2685
2686 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2687 aaru_off_t edc_position = aaruf_ftell(ctx->imageStream);
2688 if(edc_position & alignment_mask)
2689 {
2690 const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
2691 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2692 edc_position = aligned_position;
2693 }
2694 TRACE("Writing BD sector EDC block at position %ld", edc_position);
2695 BlockHeader edc_block = {0};
2696 edc_block.identifier = DataBlock;
2697 edc_block.type = kDataTypeBdSectorEdc;
2698 edc_block.compression =
2700 edc_block.length = (uint32_t)total_sectors * 4;
2701 edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
2702
2703 uint8_t *buffer = NULL;
2704 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2705
2706 if(edc_block.compression == kCompressionNone)
2707 {
2708 buffer = ctx->sector_edc;
2709 edc_block.cmpCrc64 = edc_block.crc64;
2710 }
2711 else
2712 {
2713 buffer = malloc((size_t)edc_block.length * 2);
2714 if(buffer == NULL)
2715 {
2716 TRACE("Failed to allocate memory for BD sector EDC compression");
2717 return;
2718 }
2719
2720 size_t dst_size;
2721
2722 if(ctx->use_zstd)
2723 {
2724 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)edc_block.length * 2, ctx->sector_edc,
2725 edc_block.length, ctx->zstd_level, ctx->num_threads);
2726 if(dst_size == 0) dst_size = edc_block.length;
2727 }
2728 else
2729 {
2730 dst_size = (size_t)edc_block.length * 2 * 2;
2731 size_t props_size = LZMA_PROPERTIES_LENGTH;
2732 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_edc, edc_block.length, lzma_properties, &props_size,
2733 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2734 }
2735
2736 edc_block.cmpLength = (uint32_t)dst_size;
2737
2738 if(edc_block.cmpLength >= edc_block.length)
2739 {
2740 edc_block.compression = kCompressionNone;
2741 free(buffer);
2742 buffer = ctx->sector_edc;
2743 }
2744 }
2745
2746 if(edc_block.compression == kCompressionNone)
2747 {
2748 edc_block.cmpLength = edc_block.length;
2749 edc_block.cmpCrc64 = edc_block.crc64;
2750 }
2751 else
2752 {
2753 edc_block.cmpCrc64 = aaruf_crc64_data(buffer, edc_block.cmpLength);
2754 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2755 }
2756
2757 size_t length_to_write = edc_block.cmpLength;
2758 if(edc_block.compression == kCompressionLzma) edc_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2759
2760 if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2761 {
2762 if(edc_block.compression == kCompressionLzma)
2763 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2764
2765 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2766 if(written_bytes == 1)
2767 {
2768 TRACE("Successfully wrote BD sector EDC block (%" PRIu64 " bytes)", edc_block.cmpLength);
2769
2770 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2771 {
2772 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2773 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeBdSectorEdc)
2774 {
2775 TRACE("Found existing BD sector EDC block index entry at position %d, removing", k);
2776 utarray_erase(ctx->index_entries, k, 1);
2777 }
2778 }
2779
2780 IndexEntry edc_index_entry;
2781 edc_index_entry.blockType = DataBlock;
2782 edc_index_entry.dataType = kDataTypeBdSectorEdc;
2783 edc_index_entry.offset = edc_position;
2784 utarray_push_back(ctx->index_entries, &edc_index_entry);
2785 ctx->dirty_index_block = true;
2786 TRACE("Added BD sector EDC block index entry at offset %" PRIu64, edc_position);
2787 }
2788 }
2789
2790 if(edc_block.compression != kCompressionNone) free(buffer);
2791}
2792
2891{
2892 if(ctx->sector_decrypted_title_key == NULL) return;
2893
2894 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
2895 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
2896 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2897 if(block_position & alignment_mask)
2898 {
2899 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
2900 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
2901 block_position = aligned_position;
2902 }
2903 TRACE("Writing DVD decrypted title key block at position %ld", block_position);
2904 BlockHeader decrypted_title_key_block = {0};
2905 decrypted_title_key_block.identifier = DataBlock;
2906 decrypted_title_key_block.type = kDataTypeDvdTitleKeyDecrypted;
2907 decrypted_title_key_block.compression =
2909 decrypted_title_key_block.length =
2911 5;
2912 // Calculate CRC64
2913 decrypted_title_key_block.crc64 =
2914 aaruf_crc64_data(ctx->sector_decrypted_title_key, decrypted_title_key_block.length);
2915
2916 uint8_t *buffer = NULL;
2917 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2918
2919 if(decrypted_title_key_block.compression == kCompressionNone)
2920 {
2921 buffer = ctx->sector_decrypted_title_key;
2922 decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
2923 }
2924 else
2925 {
2926 buffer = malloc((size_t)decrypted_title_key_block.length * 2); // Allocate double size for compression
2927 if(buffer == NULL)
2928 {
2929 TRACE("Failed to allocate memory for DVD decrypted title key compression");
2930 return;
2931 }
2932
2933 size_t dst_size;
2934
2935 if(ctx->use_zstd)
2936 {
2937 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)decrypted_title_key_block.length * 2,
2938 ctx->sector_decrypted_title_key, decrypted_title_key_block.length,
2939 ctx->zstd_level, ctx->num_threads);
2940 if(dst_size == 0) dst_size = decrypted_title_key_block.length;
2941 }
2942 else
2943 {
2944 dst_size = (size_t)decrypted_title_key_block.length * 2 * 2;
2945 size_t props_size = LZMA_PROPERTIES_LENGTH;
2947 decrypted_title_key_block.length, lzma_properties, &props_size, 9,
2948 ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
2949 }
2950
2951 decrypted_title_key_block.cmpLength = (uint32_t)dst_size;
2952
2953 if(decrypted_title_key_block.cmpLength >= decrypted_title_key_block.length)
2954 {
2955 decrypted_title_key_block.compression = kCompressionNone;
2956 free(buffer);
2957 buffer = ctx->sector_decrypted_title_key;
2958 }
2959 }
2960
2961 if(decrypted_title_key_block.compression == kCompressionNone)
2962 {
2963 decrypted_title_key_block.cmpLength = decrypted_title_key_block.length;
2964 decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
2965 }
2966 else
2967 {
2968 decrypted_title_key_block.cmpCrc64 = aaruf_crc64_data(buffer, decrypted_title_key_block.cmpLength);
2969 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
2970 }
2971
2972 const size_t length_to_write = decrypted_title_key_block.cmpLength;
2973 if(decrypted_title_key_block.compression == kCompressionLzma)
2974 decrypted_title_key_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2975
2976 // Write header
2977 if(fwrite(&decrypted_title_key_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2978 {
2979 if(decrypted_title_key_block.compression == kCompressionLzma)
2980 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2981
2982 // Write data
2983 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2984 if(written_bytes == 1)
2985 {
2986 TRACE("Successfully wrote DVD decrypted title key block (%" PRIu64 " bytes)",
2987 decrypted_title_key_block.cmpLength);
2988
2989 // Remove any previously existing index entries of the same type before adding
2990 TRACE("Removing any previously existing DVD decrypted title key block index entries");
2991 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2992 {
2993 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2994 if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeDvdTitleKeyDecrypted)
2995 {
2996 TRACE("Found existing DVD decrypted title key block index entry at position %d, removing", k);
2997 utarray_erase(ctx->index_entries, k, 1);
2998 }
2999 }
3000
3001 // Add decrypted title key block to index
3002 TRACE("Adding DVD decrypted title key block to index");
3003 IndexEntry decrypted_title_key_index_entry;
3004 decrypted_title_key_index_entry.blockType = DataBlock;
3005 decrypted_title_key_index_entry.dataType = kDataTypeDvdTitleKeyDecrypted;
3006 decrypted_title_key_index_entry.offset = block_position;
3007 utarray_push_back(ctx->index_entries, &decrypted_title_key_index_entry);
3008 ctx->dirty_index_block = true;
3009 TRACE("Added DVD decrypted title key block index entry at offset %" PRIu64, block_position);
3010 }
3011 }
3012
3013 if(decrypted_title_key_block.compression != kCompressionNone) free(buffer);
3014}
3015
3088{
3089 if(ctx->mediaTags == NULL) return;
3090
3091 mediaTagEntry *media_tag = NULL;
3092 mediaTagEntry *tmp_media_tag = NULL;
3093
3094 HASH_ITER(hh, ctx->mediaTags, media_tag, tmp_media_tag)
3095 {
3096 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3097 aaru_off_t tag_position = aaruf_ftell(ctx->imageStream);
3098 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3099 if(tag_position & alignment_mask)
3100 {
3101 const uint64_t aligned_position = tag_position + alignment_mask & ~alignment_mask;
3102 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3103 tag_position = aligned_position;
3104 }
3105
3106 TRACE("Writing media tag block type %d at position %ld", aaruf_get_datatype_for_media_tag_type(media_tag->type),
3107 tag_position);
3108 BlockHeader tag_block = {0};
3109 tag_block.identifier = DataBlock;
3110 tag_block.type = (uint16_t)aaruf_get_datatype_for_media_tag_type(media_tag->type);
3111 tag_block.compression =
3113 tag_block.length = media_tag->length;
3114
3115 // Calculate CRC64
3116 tag_block.crc64 = aaruf_crc64_data(media_tag->data, tag_block.length);
3117
3118 uint8_t *buffer = NULL;
3119 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
3120
3121 if(tag_block.compression == kCompressionNone)
3122 {
3123 buffer = media_tag->data;
3124 tag_block.cmpCrc64 = tag_block.crc64;
3125 }
3126 else
3127 {
3128 buffer = malloc((size_t)tag_block.length * 2); // Allocate double size for compression
3129 if(buffer == NULL)
3130 {
3131 TRACE("Failed to allocate memory for media tag compression");
3132 return;
3133 }
3134
3135 size_t dst_size;
3136
3137 if(ctx->use_zstd)
3138 {
3139 dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)tag_block.length * 2, media_tag->data,
3140 tag_block.length, ctx->zstd_level, ctx->num_threads);
3141 if(dst_size == 0) dst_size = tag_block.length;
3142 }
3143 else
3144 {
3145 dst_size = (size_t)tag_block.length * 2 * 2;
3146 size_t props_size = LZMA_PROPERTIES_LENGTH;
3147 aaruf_lzma_encode_buffer(buffer, &dst_size, media_tag->data, tag_block.length, lzma_properties,
3148 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
3149 }
3150
3151 tag_block.cmpLength = (uint32_t)dst_size;
3152
3153 if(tag_block.cmpLength >= tag_block.length)
3154 {
3155 tag_block.compression = kCompressionNone;
3156 free(buffer);
3157 buffer = media_tag->data;
3158 }
3159 }
3160
3161 if(tag_block.compression == kCompressionNone)
3162 {
3163 tag_block.cmpLength = tag_block.length;
3164 tag_block.cmpCrc64 = tag_block.crc64;
3165 }
3166 else
3167 {
3168 tag_block.cmpCrc64 = aaruf_crc64_data(buffer, tag_block.cmpLength);
3169 if(ctx->use_zstd) ctx->has_zstd_blocks = true;
3170 }
3171
3172 const size_t length_to_write = tag_block.cmpLength;
3173 if(tag_block.compression == kCompressionLzma) tag_block.cmpLength += LZMA_PROPERTIES_LENGTH;
3174
3175 // Write header
3176 if(fwrite(&tag_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
3177 {
3178 if(tag_block.compression == kCompressionLzma)
3179 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream); // Write data
3180
3181 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
3182 if(written_bytes == 1)
3183 {
3184 TRACE("Successfully wrote media tag block type %d (%" PRIu64 " bytes)", tag_block.type,
3185 tag_block.cmpLength);
3186
3187 // Remove any previously existing index entries of the same type before adding
3188 TRACE("Removing any previously existing media tag type %d block index entries", tag_block.type);
3189 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3190 {
3191 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3192 if(entry && entry->blockType == DataBlock && entry->dataType == tag_block.type)
3193 {
3194 TRACE("Found existing media tag type %d block index entry at position %d, removing",
3195 tag_block.type, k);
3196 utarray_erase(ctx->index_entries, k, 1);
3197 }
3198 }
3199
3200 // Add media tag block to index
3201 TRACE("Adding media tag type %d block to index", tag_block.type);
3202 IndexEntry tag_index_entry;
3203 tag_index_entry.blockType = DataBlock;
3204 tag_index_entry.dataType = tag_block.type;
3205 tag_index_entry.offset = tag_position;
3206 utarray_push_back(ctx->index_entries, &tag_index_entry);
3207 ctx->dirty_index_block = true;
3208 TRACE("Added media tag block type %d index entry at offset %" PRIu64, tag_block.type, tag_position);
3209 }
3210 }
3211
3212 if(tag_block.compression != kCompressionNone) free(buffer);
3213 }
3214}
3215
3379{
3380 if(ctx->tape_files == NULL) return;
3381
3382 // Iterate the uthash and count how many entries do we have
3383 const tapeFileHashEntry *tape_file = NULL;
3384 const tapeFileHashEntry *tmp_tape_file = NULL;
3385 size_t tape_file_count = 0;
3386 HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file) tape_file_count++;
3387
3388 // Create a memory buffer to copy all the file entries
3389 const size_t buffer_size = tape_file_count * sizeof(TapeFileEntry);
3390 TapeFileEntry *buffer = malloc(buffer_size);
3391 if(buffer == NULL)
3392 {
3393 TRACE("Failed to allocate memory for tape file entries");
3394 return;
3395 }
3396 memset(buffer, 0, buffer_size);
3397 size_t index = 0;
3398 HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file)
3399 {
3400 if(index >= tape_file_count) break;
3401 memcpy(&buffer[index], &tape_file->fileEntry, sizeof(TapeFileEntry));
3402 index++;
3403 }
3404
3405 // Create the tape file block in memory
3406 TapeFileHeader tape_file_block = {0};
3407 tape_file_block.identifier = TapeFileBlock;
3408 tape_file_block.length = (uint32_t)buffer_size;
3409 tape_file_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_file_block.length);
3410
3411 // Write tape file block to file, block aligned
3412 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3413 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3414 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3415 if(block_position & alignment_mask)
3416 {
3417 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3418 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3419 block_position = aligned_position;
3420 }
3421 TRACE("Writing tape file block at position %ld", block_position);
3422 if(fwrite(&tape_file_block, sizeof(TapeFileHeader), 1, ctx->imageStream) == 1)
3423 {
3424 const size_t written_bytes = fwrite(buffer, tape_file_block.length, 1, ctx->imageStream);
3425 if(written_bytes == 1)
3426 {
3427 TRACE("Successfully wrote tape file block (%" PRIu64 " bytes)", tape_file_block.length);
3428
3429 // Remove any previously existing index entries of the same type before adding
3430 TRACE("Removing any previously existing tape file block index entries");
3431 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3432 {
3433 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3434 if(entry && entry->blockType == TapeFileBlock && entry->dataType == 0)
3435 {
3436 TRACE("Found existing tape file block index entry at position %d, removing", k);
3437 utarray_erase(ctx->index_entries, k, 1);
3438 }
3439 }
3440
3441 // Add tape file block to index
3442 TRACE("Adding tape file block to index");
3443 IndexEntry index_entry;
3444 index_entry.blockType = TapeFileBlock;
3445 index_entry.dataType = 0;
3446 index_entry.offset = block_position;
3447 utarray_push_back(ctx->index_entries, &index_entry);
3448 ctx->dirty_index_block = true;
3449 TRACE("Added tape file block index entry at offset %" PRIu64, block_position);
3450 }
3451 }
3452
3453 free(buffer);
3454}
3455
3625{
3626 if(ctx->tape_partitions == NULL) return;
3627
3628 // Iterate the uthash and count how many entries do we have
3629 const TapePartitionHashEntry *tape_partition = NULL;
3630 const TapePartitionHashEntry *tmp_tape_partition = NULL;
3631 size_t tape_partition_count = 0;
3632 HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition) tape_partition_count++;
3633
3634 // Create a memory buffer to copy all the partition entries
3635 const size_t buffer_size = tape_partition_count * sizeof(TapePartitionEntry);
3636 TapePartitionEntry *buffer = malloc(buffer_size);
3637 if(buffer == NULL)
3638 {
3639 TRACE("Failed to allocate memory for tape partition entries");
3640 return;
3641 }
3642 memset(buffer, 0, buffer_size);
3643 size_t index = 0;
3644 HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition)
3645 {
3646 if(index >= tape_partition_count) break;
3647 memcpy(&buffer[index], &tape_partition->partitionEntry, sizeof(TapePartitionEntry));
3648 index++;
3649 }
3650
3651 // Create the tape partition block in memory
3652 TapePartitionHeader tape_partition_block = {0};
3653 tape_partition_block.identifier = TapePartitionBlock;
3654 tape_partition_block.length = (uint32_t)buffer_size;
3655 tape_partition_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_partition_block.length);
3656
3657 // Write tape partition block to partition, block aligned
3658 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3659 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3660 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3661 if(block_position & alignment_mask)
3662 {
3663 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3664 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3665 block_position = aligned_position;
3666 }
3667 TRACE("Writing tape partition block at position %ld", block_position);
3668 if(fwrite(&tape_partition_block, sizeof(TapePartitionHeader), 1, ctx->imageStream) == 1)
3669 {
3670 const size_t written_bytes = fwrite(buffer, tape_partition_block.length, 1, ctx->imageStream);
3671 if(written_bytes == 1)
3672 {
3673 TRACE("Successfully wrote tape partition block (%" PRIu64 " bytes)", tape_partition_block.length);
3674
3675 // Remove any previously existing index entries of the same type before adding
3676 TRACE("Removing any previously existing tape partition block index entries");
3677 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3678 {
3679 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3680 if(entry && entry->blockType == TapePartitionBlock && entry->dataType == 0)
3681 {
3682 TRACE("Found existing tape partition block index entry at position %d, removing", k);
3683 utarray_erase(ctx->index_entries, k, 1);
3684 }
3685 }
3686
3687 // Add tape partition block to index
3688 TRACE("Adding tape partition block to index");
3689 IndexEntry index_entry;
3690 index_entry.blockType = TapePartitionBlock;
3691 index_entry.dataType = 0;
3692 index_entry.offset = block_position;
3693 utarray_push_back(ctx->index_entries, &index_entry);
3694 ctx->dirty_index_block = true;
3695 TRACE("Added tape partition block index entry at offset %" PRIu64, block_position);
3696 }
3697 }
3698
3699 free(buffer);
3700}
3701
3764{
3765 if(ctx->geometry_block.identifier != GeometryBlock) return;
3766
3767 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
3768 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
3769 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3770 if(block_position & alignment_mask)
3771 {
3772 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3773 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
3774 block_position = aligned_position;
3775 }
3776
3777 TRACE("Writing geometry block at position %ld", block_position);
3778
3779 // Write header
3780 if(fwrite(&ctx->geometry_block, sizeof(GeometryBlockHeader), 1, ctx->imageStream) == 1)
3781 {
3782 TRACE("Successfully wrote geometry block");
3783
3784 // Remove any previously existing index entries of the same type before adding
3785 TRACE("Removing any previously existing geometry block index entries");
3786 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3787 {
3788 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3789 if(entry && entry->blockType == GeometryBlock && entry->dataType == 0)
3790 {
3791 TRACE("Found existing geometry block index entry at position %d, removing", k);
3792 utarray_erase(ctx->index_entries, k, 1);
3793 }
3794 }
3795
3796 // Add geometry block to index
3797 TRACE("Adding geometry block to index");
3798 IndexEntry index_entry;
3799 index_entry.blockType = GeometryBlock;
3800 index_entry.dataType = 0;
3801 index_entry.offset = block_position;
3802 utarray_push_back(ctx->index_entries, &index_entry);
3803 ctx->dirty_index_block = true;
3804 TRACE("Added geometry block index entry at offset %" PRIu64, block_position);
3805 }
3806}
3807
3913{
3915 ctx->metadata_block_header.lastMediaSequence == 0 && ctx->creator == NULL && ctx->comments == NULL &&
3916 ctx->media_title == NULL && ctx->media_manufacturer == NULL && ctx->media_model == NULL &&
3917 ctx->media_serial_number == NULL && ctx->media_barcode == NULL && ctx->media_part_number == NULL &&
3918 ctx->drive_manufacturer == NULL && ctx->drive_model == NULL && ctx->drive_serial_number == NULL &&
3919 ctx->drive_firmware_revision == NULL)
3920 return;
3921
3930
3932
3933 int pos = sizeof(MetadataBlockHeader);
3934
3935 uint8_t *buffer = calloc(1, ctx->metadata_block_header.blockSize);
3936 if(buffer == NULL) return;
3937
3938 if(ctx->creator != NULL && ctx->metadata_block_header.creatorLength > 0)
3939 {
3940 memcpy(buffer + pos, ctx->creator, ctx->metadata_block_header.creatorLength);
3943 }
3944
3945 if(ctx->comments != NULL && ctx->metadata_block_header.commentsLength > 0)
3946 {
3947 memcpy(buffer + pos, ctx->comments, ctx->metadata_block_header.commentsLength);
3950 }
3951
3952 if(ctx->media_title != NULL && ctx->metadata_block_header.mediaTitleLength > 0)
3953 {
3954 memcpy(buffer + pos, ctx->media_title, ctx->metadata_block_header.mediaTitleLength);
3957 }
3958
3960 {
3961 memcpy(buffer + pos, ctx->media_manufacturer, ctx->metadata_block_header.mediaManufacturerLength);
3964 }
3965
3966 if(ctx->media_model != NULL && ctx->metadata_block_header.mediaModelLength > 0)
3967 {
3968 memcpy(buffer + pos, ctx->media_model, ctx->metadata_block_header.mediaModelLength);
3971 }
3972
3974 {
3975 memcpy(buffer + pos, ctx->media_serial_number, ctx->metadata_block_header.mediaSerialNumberLength);
3978 }
3979
3980 if(ctx->media_barcode != NULL && ctx->metadata_block_header.mediaBarcodeLength > 0)
3981 {
3982 memcpy(buffer + pos, ctx->media_barcode, ctx->metadata_block_header.mediaBarcodeLength);
3985 }
3986
3988 {
3989 memcpy(buffer + pos, ctx->media_part_number, ctx->metadata_block_header.mediaPartNumberLength);
3992 }
3993
3995 {
3996 memcpy(buffer + pos, ctx->drive_manufacturer, ctx->metadata_block_header.driveManufacturerLength);
3999 }
4000
4001 if(ctx->drive_model != NULL && ctx->metadata_block_header.driveModelLength > 0)
4002 {
4003 memcpy(buffer + pos, ctx->drive_model, ctx->metadata_block_header.driveModelLength);
4006 }
4007
4009 {
4010 memcpy(buffer + pos, ctx->drive_serial_number, ctx->metadata_block_header.driveSerialNumberLength);
4013 }
4014
4016 {
4019 }
4020
4021 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4022 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4023 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4024 if(block_position & alignment_mask)
4025 {
4026 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4027 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4028 block_position = aligned_position;
4029 }
4030
4031 memcpy(buffer, &ctx->metadata_block_header, sizeof(MetadataBlockHeader));
4032
4033 TRACE("Writing metadata block at position %ld", block_position);
4034
4035 if(fwrite(buffer, ctx->metadata_block_header.blockSize, 1, ctx->imageStream) == 1)
4036 {
4037 TRACE("Successfully wrote metadata block");
4038
4039 // Remove any previously existing index entries of the same type before adding
4040 TRACE("Removing any previously existing metadata block index entries");
4041 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4042 {
4043 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4044 if(entry && entry->blockType == MetadataBlock && entry->dataType == 0)
4045 {
4046 TRACE("Found existing metadata block index entry at position %d, removing", k);
4047 utarray_erase(ctx->index_entries, k, 1);
4048 }
4049 }
4050
4051 // Add metadata block to index
4052 TRACE("Adding metadata block to index");
4053 IndexEntry index_entry;
4054 index_entry.blockType = MetadataBlock;
4055 index_entry.dataType = 0;
4056 index_entry.offset = block_position;
4057 utarray_push_back(ctx->index_entries, &index_entry);
4058 ctx->dirty_index_block = true;
4059 TRACE("Added metadata block index entry at offset %" PRIu64, block_position);
4060 }
4061
4062 free(buffer);
4063}
4064
4211{
4212
4213 if(ctx->dump_hardware_entries_with_data == NULL || ctx->dump_hardware_header.entries == 0 ||
4215 return;
4216
4217 const size_t required_length = sizeof(DumpHardwareHeader) + ctx->dump_hardware_header.length;
4218
4219 uint8_t *buffer = calloc(1, required_length);
4220
4221 if(buffer == NULL) return;
4222
4223 // Start to iterate and copy the data
4224 size_t offset = sizeof(DumpHardwareHeader);
4225 for(int i = 0; i < ctx->dump_hardware_header.entries; i++)
4226 {
4227 size_t entry_size = sizeof(DumpHardwareEntry) +
4237
4238 if(offset + entry_size > required_length)
4239 {
4240 FATAL("Calculated size exceeds provided buffer length");
4241 free(buffer);
4242 return;
4243 }
4244
4245 memcpy(buffer + offset, &ctx->dump_hardware_entries_with_data[i].entry, sizeof(DumpHardwareEntry));
4246 offset += sizeof(DumpHardwareEntry);
4249 {
4250 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].manufacturer,
4253 }
4255 ctx->dump_hardware_entries_with_data[i].model != NULL)
4256 {
4257 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].model,
4260 }
4263 {
4264 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].revision,
4267 }
4270 {
4271 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].firmware,
4274 }
4277 {
4278 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].serial,
4281 }
4284 {
4285 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareName,
4288 }
4291 {
4292 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareVersion,
4295 }
4298 {
4299 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem,
4302 }
4305 {
4306 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].extents,
4308 offset += ctx->dump_hardware_entries_with_data[i].entry.extents * sizeof(DumpExtent);
4309 }
4310 }
4311
4312 // Calculate CRC64
4315
4316 // Copy header
4317 memcpy(buffer, &ctx->dump_hardware_header, sizeof(DumpHardwareHeader));
4318
4319 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4320 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4321 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4322 if(block_position & alignment_mask)
4323 {
4324 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4325 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4326 block_position = aligned_position;
4327 }
4328 TRACE("Writing dump hardware block at position %ld", block_position);
4329 if(fwrite(buffer, required_length, 1, ctx->imageStream) == 1)
4330 {
4331 TRACE("Successfully wrote dump hardware block");
4332
4333 // Remove any previously existing index entries of the same type before adding
4334 TRACE("Removing any previously existing dump hardware block index entries");
4335 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4336 {
4337 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4338 if(entry && entry->blockType == DumpHardwareBlock && entry->dataType == 0)
4339 {
4340 TRACE("Found existing dump hardware block index entry at position %d, removing", k);
4341 utarray_erase(ctx->index_entries, k, 1);
4342 }
4343 }
4344
4345 // Add dump hardware block to index
4346 TRACE("Adding dump hardware block to index");
4347 IndexEntry index_entry;
4348 index_entry.blockType = DumpHardwareBlock;
4349 index_entry.dataType = 0;
4350 index_entry.offset = block_position;
4351 utarray_push_back(ctx->index_entries, &index_entry);
4352 ctx->dirty_index_block = true;
4353 TRACE("Added dump hardware block index entry at offset %" PRIu64, block_position);
4354 }
4355
4356 free(buffer);
4357}
4358
4454{
4455 if(ctx->cicm_block == NULL || ctx->cicm_block_header.length == 0 || ctx->cicm_block_header.identifier != CicmBlock)
4456 return;
4457
4458 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4459 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4460 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4461
4462 if(block_position & alignment_mask)
4463 {
4464 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4465 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4466 block_position = aligned_position;
4467 }
4468
4469 TRACE("Writing CICM XML block at position %ld", block_position);
4470 if(fwrite(&ctx->cicm_block_header, sizeof(CicmMetadataBlock), 1, ctx->imageStream) == 1 &&
4471 fwrite(ctx->cicm_block, ctx->cicm_block_header.length, 1, ctx->imageStream) == 1)
4472 {
4473 TRACE("Successfully wrote CICM XML block");
4474
4475 // Remove any previously existing index entries of the same type before adding
4476 TRACE("Removing any previously existing CICM XML block index entries");
4477 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4478 {
4479 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4480 if(entry && entry->blockType == CicmBlock && entry->dataType == 0)
4481 {
4482 TRACE("Found existing CICM XML block index entry at position %d, removing", k);
4483 utarray_erase(ctx->index_entries, k, 1);
4484 }
4485 }
4486
4487 // Add CICM block to index
4488 TRACE("Adding CICM XML block to index");
4489 IndexEntry index_entry;
4490 index_entry.blockType = CicmBlock;
4491 index_entry.dataType = 0;
4492 index_entry.offset = block_position;
4493 utarray_push_back(ctx->index_entries, &index_entry);
4494 ctx->dirty_index_block = true;
4495 TRACE("Added CICM XML block index entry at offset %" PRIu64, block_position);
4496 }
4497}
4498
4604{
4605 if(ctx->json_block == NULL || ctx->json_block_header.length == 0 ||
4607 return;
4608
4609 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4610 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4611 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4612
4613 if(block_position & alignment_mask)
4614 {
4615 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4616 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4617 block_position = aligned_position;
4618 }
4619
4620 TRACE("Writing Aaru metadata JSON block at position %ld", block_position);
4621 if(fwrite(&ctx->json_block_header, sizeof(AaruMetadataJsonBlockHeader), 1, ctx->imageStream) == 1 &&
4622 fwrite(ctx->json_block, ctx->json_block_header.length, 1, ctx->imageStream) == 1)
4623 {
4624 TRACE("Successfully wrote Aaru metadata JSON block");
4625
4626 // Remove any previously existing index entries of the same type before adding
4627 TRACE("Removing any previously existing Aaru metadata JSON block index entries");
4628 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4629 {
4630 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4631 if(entry && entry->blockType == AaruMetadataJsonBlock && entry->dataType == 0)
4632 {
4633 TRACE("Found existing Aaru metadata JSON block index entry at position %d, removing", k);
4634 utarray_erase(ctx->index_entries, k, 1);
4635 }
4636 }
4637
4638 // Add Aaru metadata JSON block to index
4639 TRACE("Adding Aaru metadata JSON block to index");
4640 IndexEntry index_entry;
4641 index_entry.blockType = AaruMetadataJsonBlock;
4642 index_entry.dataType = 0;
4643 index_entry.offset = block_position;
4644 utarray_push_back(ctx->index_entries, &index_entry);
4645 ctx->dirty_index_block = true;
4646 TRACE("Added Aaru metadata JSON block index entry at offset %" PRIu64, block_position);
4647 }
4648}
4649
4658{
4659 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4660 {
4661 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4662
4663 if(entry && entry->blockType == DataPositionMeasurementBlock && entry->dataType == 0)
4664 {
4665 TRACE("Found existing DPM block index entry at position %d, removing", k);
4666 utarray_erase(ctx->index_entries, k, 1);
4667 ctx->dirty_index_block = true;
4668 }
4669 }
4670}
4671
4684{
4685 if(ctx->dpm_block == NULL || ctx->dpm_block_length == 0)
4686 {
4687 TRACE("No DPM to write, removing any previous DPM block from the index");
4689
4690 return;
4691 }
4692
4693 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
4694 aaru_off_t block_position = aaruf_ftell(ctx->imageStream);
4695 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4696
4697 if(block_position & alignment_mask)
4698 {
4699 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4700 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
4701 block_position = aligned_position;
4702 }
4703
4704 TRACE("Writing DPM block at position %" PRId64, (int64_t)block_position);
4705
4706 if(fwrite(ctx->dpm_block, ctx->dpm_block_length, 1, ctx->imageStream) != 1)
4707 {
4708 TRACE("Could not write DPM block");
4709
4710 return;
4711 }
4712
4714
4715 IndexEntry index_entry;
4717 index_entry.dataType = 0;
4718 index_entry.offset = block_position;
4719 utarray_push_back(ctx->index_entries, &index_entry);
4720 ctx->dirty_index_block = true;
4721 TRACE("Added DPM block index entry at offset %" PRIu64, (uint64_t)block_position);
4722}
4723
4908{
4909 uint64_t data_length = record->data_length;
4910 uint64_t index_length = record->index_length;
4911 uint64_t raw_length = data_length + index_length;
4912
4913 if(raw_length > UINT32_MAX)
4914 {
4915 FATAL("Flux capture raw length exceeds 32-bit limit (%" PRIu64 ")", raw_length);
4917 }
4918
4919 uint8_t *raw_buffer = NULL;
4920 if(raw_length != 0)
4921 {
4922 raw_buffer = malloc(raw_length);
4923 if(raw_buffer == NULL)
4924 {
4925 FATAL("Could not allocate %" PRIu64 " bytes for flux serialization", raw_length);
4927 }
4928
4929 if(data_length != 0 && record->data_buffer != NULL) memcpy(raw_buffer, record->data_buffer, data_length);
4930 if(index_length != 0 && record->index_buffer != NULL)
4931 memcpy(raw_buffer + data_length, record->index_buffer, index_length);
4932 }
4933
4934 uint64_t raw_crc = raw_length != 0 && raw_buffer != NULL ? aaruf_crc64_data(raw_buffer, raw_length) : 0;
4935
4936 CompressionType compression =
4938
4939 uint8_t *compressed_buffer = NULL;
4940 uint32_t cmp_length = 0;
4941 uint64_t cmp_crc = 0;
4942
4943 if(compression == kCompressionLzma)
4944 {
4945 size_t cmp_capacity = raw_length ? raw_length * 2 + 65536 : LZMA_PROPERTIES_LENGTH + 16;
4946 if(cmp_capacity < raw_length + LZMA_PROPERTIES_LENGTH) cmp_capacity = raw_length + LZMA_PROPERTIES_LENGTH;
4947
4948 uint8_t *cmp_stream = malloc(cmp_capacity);
4949 if(cmp_stream == NULL)
4950 {
4951 free(raw_buffer);
4952 FATAL("Could not allocate %zu bytes for LZMA flux compression", cmp_capacity);
4954 }
4955
4956 size_t dst_size = cmp_capacity;
4957 uint8_t lzma_props[LZMA_PROPERTIES_LENGTH] = {0};
4958 size_t props_size = LZMA_PROPERTIES_LENGTH;
4959 int32_t error_no = aaruf_lzma_encode_buffer(cmp_stream, &dst_size, raw_buffer, raw_length, lzma_props,
4960 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
4961
4962 if(error_no != 0 || props_size != LZMA_PROPERTIES_LENGTH || dst_size >= raw_length)
4963 {
4964 TRACE("Flux capture compression fell back to uncompressed (err=%d, dst=%zu, raw=%" PRIu64 ")", error_no,
4965 dst_size, raw_length);
4966 compression = kCompressionNone;
4967 free(cmp_stream);
4968 }
4969 else
4970 {
4971 cmp_length = (uint32_t)(dst_size + LZMA_PROPERTIES_LENGTH);
4972 compressed_buffer = malloc(cmp_length);
4973 if(compressed_buffer == NULL)
4974 {
4975 free(cmp_stream);
4976 free(raw_buffer);
4977 FATAL("Could not allocate %u bytes for flux compressed payload", cmp_length);
4979 }
4980
4981 memcpy(compressed_buffer, lzma_props, LZMA_PROPERTIES_LENGTH);
4982 memcpy(compressed_buffer + LZMA_PROPERTIES_LENGTH, cmp_stream, dst_size);
4983 cmp_crc = aaruf_crc64_data(compressed_buffer, cmp_length);
4984 free(cmp_stream);
4985 free(raw_buffer);
4986 raw_buffer = NULL;
4987 }
4988 }
4989 else if(compression == kCompressionZstd)
4990 {
4991 size_t cmp_capacity = raw_length ? raw_length * 2 + 65536 : 16;
4992
4993 uint8_t *cmp_stream = malloc(cmp_capacity);
4994 if(cmp_stream == NULL)
4995 {
4996 free(raw_buffer);
4997 FATAL("Could not allocate %zu bytes for zstd flux compression", cmp_capacity);
4999 }
5000
5001 size_t dst_size =
5002 aaruf_zstd_encode_buffer(cmp_stream, cmp_capacity, raw_buffer, raw_length, ctx->zstd_level, ctx->num_threads);
5003
5004 if(dst_size == 0 || dst_size >= raw_length)
5005 {
5006 TRACE("Flux capture zstd compression fell back to uncompressed (dst=%zu, raw=%" PRIu64 ")", dst_size,
5007 raw_length);
5008 compression = kCompressionNone;
5009 free(cmp_stream);
5010 }
5011 else
5012 {
5013 cmp_length = (uint32_t)dst_size;
5014 compressed_buffer = cmp_stream;
5015 cmp_crc = aaruf_crc64_data(compressed_buffer, cmp_length);
5016 ctx->has_zstd_blocks = true;
5017 free(raw_buffer);
5018 raw_buffer = NULL;
5019 }
5020 }
5021
5022 if(compression == kCompressionNone)
5023 {
5024 cmp_length = (uint32_t)raw_length;
5025 cmp_crc = raw_crc;
5026 compressed_buffer = raw_buffer;
5027 raw_buffer = NULL;
5028 }
5029
5030 // Align stream position to block boundary
5031 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
5032 aaru_off_t payload_position = aaruf_ftell(ctx->imageStream);
5033 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
5034 if(payload_position & alignment_mask)
5035 {
5036 const uint64_t aligned_position = payload_position + alignment_mask & ~alignment_mask;
5037 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
5038 payload_position = aligned_position;
5039 }
5040
5041 DataStreamPayloadHeader payload_header = {0};
5042 payload_header.identifier = DataStreamPayloadBlock;
5043 payload_header.dataType = kDataTypeFluxData;
5044 payload_header.compression = (uint16_t)compression;
5045 payload_header.cmpLength = cmp_length;
5046 payload_header.length = (uint32_t)raw_length;
5047 payload_header.cmpCrc64 = cmp_crc;
5048 payload_header.crc64 = raw_crc;
5049
5050 if(fwrite(&payload_header, sizeof(DataStreamPayloadHeader), 1, ctx->imageStream) != 1)
5051 {
5052 free(compressed_buffer);
5053 free(raw_buffer);
5054 FATAL("Could not write flux payload header");
5056 }
5057
5058 if(cmp_length != 0 && compressed_buffer != NULL && fwrite(compressed_buffer, cmp_length, 1, ctx->imageStream) != 1)
5059 {
5060 free(compressed_buffer);
5061 free(raw_buffer);
5062 FATAL("Could not write flux payload data");
5064 }
5065
5066 IndexEntry payload_entry;
5067 payload_entry.blockType = DataStreamPayloadBlock;
5068 payload_entry.dataType = kDataTypeFluxData;
5069 payload_entry.offset = payload_position;
5070 utarray_push_back(ctx->index_entries, &payload_entry);
5071
5072 entry->head = record->entry.head;
5073 entry->track = record->entry.track;
5074 entry->subtrack = record->entry.subtrack;
5075 entry->captureIndex = record->entry.captureIndex;
5076 entry->dataResolution = record->entry.dataResolution;
5077 entry->indexResolution = record->entry.indexResolution;
5078 entry->indexOffset = record->data_length;
5079 entry->payloadOffset = payload_position >> ctx->user_data_ddt_header.blockAlignmentShift;
5080
5081 record->entry = *entry;
5082
5083 free(compressed_buffer);
5084 free(raw_buffer);
5085
5086 return AARUF_STATUS_OK;
5087}
5088
5323{
5324 TRACE("Entering write_flux_blocks(%p)", ctx);
5325
5326 if(ctx == NULL || ctx->imageStream == NULL)
5327 {
5328 FATAL("Invalid context when writing flux blocks");
5330 }
5331
5332 if(ctx->flux_captures == NULL || utarray_len(ctx->flux_captures) == 0)
5333 {
5334 TRACE("No flux captures enqueued, skipping flux block serialization");
5335 return AARUF_STATUS_OK;
5336 }
5337
5338 size_t capture_count = utarray_len(ctx->flux_captures);
5339 if(capture_count > UINT16_MAX)
5340 {
5341 FATAL("Flux capture count exceeds header capacity (%zu > %u)", capture_count, UINT16_MAX);
5343 }
5344
5345 FluxEntry *previous_entries = ctx->flux_entries;
5346 ctx->flux_entries = NULL;
5347
5348 FluxEntry *entries = malloc(capture_count * sizeof(FluxEntry));
5349 if(entries == NULL)
5350 {
5351 ctx->flux_entries = previous_entries;
5352 FATAL("Could not allocate %zu bytes for flux entries", capture_count * sizeof(FluxEntry));
5354 }
5355
5356 size_t idx = 0;
5357 for(FluxCaptureRecord *record = (FluxCaptureRecord *)utarray_front(ctx->flux_captures); record != NULL;
5358 record = (FluxCaptureRecord *)utarray_next(ctx->flux_captures, record), ++idx)
5359 {
5360 int32_t res = write_flux_capture_payload(ctx, record, &entries[idx]);
5361 if(res != AARUF_STATUS_OK)
5362 {
5363 free(entries);
5364 ctx->flux_entries = previous_entries;
5365 return res;
5366 }
5367 }
5368
5369 FluxHeader header = {0};
5370 header.identifier = FluxDataBlock;
5371 header.entries = (uint16_t)capture_count;
5373 header.crc64 =
5374 capture_count == 0 ? 0 : aaruf_crc64_data((const uint8_t *)entries, capture_count * sizeof(FluxEntry));
5375
5376 // Align stream position to block boundary
5377 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
5378 aaru_off_t metadata_position = aaruf_ftell(ctx->imageStream);
5379 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
5380 if(metadata_position & alignment_mask)
5381 {
5382 const uint64_t aligned_position = metadata_position + alignment_mask & ~alignment_mask;
5383 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
5384 metadata_position = aligned_position;
5385 }
5386
5387 if(fwrite(&header, sizeof(FluxHeader), 1, ctx->imageStream) != 1)
5388 {
5389 free(entries);
5390 ctx->flux_entries = previous_entries;
5391 FATAL("Could not write flux metadata header");
5393 }
5394
5395 if(capture_count != 0)
5396 {
5397 size_t written_entries = fwrite(entries, sizeof(FluxEntry), capture_count, ctx->imageStream);
5398 if(written_entries != capture_count)
5399 {
5400 free(entries);
5401 ctx->flux_entries = previous_entries;
5402 FATAL("Could not write %zu flux entries (wrote %zu)", capture_count, written_entries);
5404 }
5405 }
5406
5407 IndexEntry metadata_entry;
5408 metadata_entry.blockType = FluxDataBlock;
5409 metadata_entry.dataType = 0;
5410 metadata_entry.offset = metadata_position;
5411 utarray_push_back(ctx->index_entries, &metadata_entry);
5412
5413 if(previous_entries != NULL) free(previous_entries);
5414 ctx->flux_entries = entries;
5415 ctx->flux_data_header = header;
5416
5417 utarray_free(ctx->flux_captures);
5418 ctx->flux_captures = NULL;
5419
5420 int32_t map_result = flux_map_rebuild_from_entries(ctx);
5421 if(map_result != AARUF_STATUS_OK) return map_result;
5422
5423 TRACE("Wrote %zu flux captures", capture_count);
5424 return AARUF_STATUS_OK;
5425}
5426
5444{
5445 // Write the complete index at the end of the file
5446 TRACE("Writing index at the end of the file");
5447 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
5448 aaru_off_t index_position = aaruf_ftell(ctx->imageStream);
5449
5450 // Align index position to block boundary if needed
5451 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
5452 if(index_position & alignment_mask)
5453 {
5454 uint64_t aligned_position = index_position + alignment_mask & ~alignment_mask;
5455 aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
5456 index_position = aligned_position;
5457 TRACE("Aligned index position to %" PRIu64, aligned_position);
5458 }
5459
5460 // Prepare index header
5461 IndexHeader3 index_header;
5462 index_header.identifier = IndexBlock3;
5463 index_header.entries = utarray_len(ctx->index_entries);
5464 index_header.previous = 0; // No previous index for now
5465
5466 TRACE("Writing index with %" PRIu64 " entries at position %ld", index_header.entries, index_position);
5467
5468 // Calculate CRC64 of index entries
5469 crc64_ctx *index_crc64_context = aaruf_crc64_init();
5470 if(index_crc64_context != NULL && index_header.entries > 0)
5471 {
5472 size_t index_data_size = index_header.entries * sizeof(IndexEntry);
5473 aaruf_crc64_update(index_crc64_context, utarray_front(ctx->index_entries), index_data_size);
5474 aaruf_crc64_final(index_crc64_context, &index_header.crc64);
5475 TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64);
5476 }
5477 else
5478 index_header.crc64 = 0;
5479
5480 // Free CRC64 context
5481 aaruf_crc64_free(index_crc64_context);
5482
5483 // Write index header
5484 if(fwrite(&index_header, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
5485 {
5486 TRACE("Successfully wrote index header");
5487
5488 // Write index entries
5489 if(index_header.entries > 0)
5490 {
5491 size_t entries_written = 0;
5492 IndexEntry *entry = NULL;
5493
5494 for(entry = (IndexEntry *)utarray_front(ctx->index_entries); entry != NULL;
5495 entry = (IndexEntry *)utarray_next(ctx->index_entries, entry))
5496 if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1)
5497 {
5498 entries_written++;
5499 TRACE("Wrote index entry: blockType=0x%08X dataType=%u offset=%" PRIu64, entry->blockType,
5500 entry->dataType, entry->offset);
5501 }
5502 else
5503 {
5504 TRACE("Failed to write index entry %zu", entries_written);
5505 break;
5506 }
5507
5508 if(entries_written == index_header.entries)
5509 {
5510 TRACE("Successfully wrote all %zu index entries", entries_written);
5511
5512 // Update header with index offset and rewrite it
5513 ctx->header.indexOffset = index_position;
5514 TRACE("Updating header with index offset: %" PRIu64, ctx->header.indexOffset);
5515
5516 // Seek back to beginning and rewrite header
5517 aaruf_fseek(ctx->imageStream, 0, SEEK_SET);
5518 if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) == 1)
5519 TRACE("Successfully updated header with index offset");
5520 else
5521 {
5522 TRACE("Failed to update header with index offset");
5524 }
5525 }
5526 else
5527 {
5528 TRACE("Failed to write all index entries (wrote %zu of %" PRIu64 ")", entries_written,
5529 index_header.entries);
5531 }
5532 }
5533 }
5534 else
5535 {
5536 TRACE("Failed to write index header");
5538 }
5539
5540 return AARUF_STATUS_OK;
5541}
5542
5556{
5557 TRACE("File is writing");
5558
5560
5561 TRACE("Seeking to start of image");
5562 // Write the header at the beginning of the file
5563 aaruf_fseek(ctx->imageStream, 0, SEEK_SET);
5564
5565 TRACE("Writing header at position 0");
5566 if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1)
5567 {
5568 fclose(ctx->imageStream);
5569 ctx->imageStream = NULL;
5571 return -1;
5572 }
5573
5574 // Close current block first
5575 TRACE("Closing current block if any");
5576 if(ctx->writing_buffer != NULL)
5577 {
5578 int error = aaruf_close_current_block(ctx);
5579
5580 if(error != AARUF_STATUS_OK) return error;
5581 }
5582
5583 int32_t res;
5584 if(ctx->is_tape)
5585 {
5586 // Write tape DDT
5587 if(ctx->dirty_tape_ddt)
5588 {
5589 res = write_tape_ddt(ctx);
5590 if(res != AARUF_STATUS_OK) return res;
5591 }
5592 }
5593 else
5594 {
5595 // Write cached secondary DDT table if any
5596 if(ctx->dirty_secondary_ddt)
5597 {
5598 res = write_cached_secondary_ddt(ctx);
5599 if(res != AARUF_STATUS_OK) return res;
5600 }
5601
5602 // Write primary DDT table (multi-level) if applicable
5603 if(ctx->dirty_primary_ddt)
5604 {
5605 res = write_primary_ddt(ctx);
5606 if(res != AARUF_STATUS_OK) return res;
5607 }
5608
5609 // Write single-level DDT table if applicable
5610 if(ctx->dirty_single_level_ddt)
5611 {
5612 res = write_single_level_ddt(ctx);
5613 if(res != AARUF_STATUS_OK) return res;
5614 }
5615 }
5616
5617 // Finalize checksums and write checksum block
5619
5620 // Write tracks block
5622
5623 // Write flux capture blocks
5624 if(ctx->dirty_flux_block)
5625 {
5626 res = write_flux_blocks(ctx);
5627 if(res != AARUF_STATUS_OK) return res;
5628 }
5629
5630 // Write MODE 2 subheader data block
5632
5633 // Write CD sector prefix data block
5635
5636 // Write sector prefix DDT (statuses + optional indexes)
5638
5639 // Write CD sector suffix data block (EDC/ECC captures)
5641
5642 // Write sector prefix DDT (EDC/ECC captures)
5644
5645 // Write sector subchannel data block
5647
5648 // Write DVD long sector data blocks
5650
5651 // Write Blu-ray sector EDC block (long sectors)
5653
5654 // Write DVD decrypted title keys
5656
5657 // Write media tags data blocks
5658 if(ctx->dirty_media_tags) write_media_tags(ctx);
5659
5660 // Write tape files
5662
5663 // Write tape partitions
5665
5666 // Write geometry block if any
5668
5669 // Write metadata block
5671
5672 // Write dump hardware block if any
5674
5675 // Write CICM XML block if any
5676 if(ctx->dirty_cicm_block) write_cicm_block(ctx);
5677
5678 // Write Aaru metadata JSON block if any
5680
5681 // Write Data Position Measurement block
5682 if(ctx->dirty_dpm_block) write_dpm_block(ctx);
5683
5684 // Write the complete index at the end of the file
5685 if(ctx->dirty_index_block)
5686 {
5687 res = write_index_block(ctx);
5688 if(res != AARUF_STATUS_OK) return res;
5689 }
5690
5691 // Write erasure coding parity, ECMB, and recovery footer
5692 ec_finalize(ctx);
5693
5694 if(ctx->deduplicate && ctx->sector_hash_map != NULL)
5695 {
5696 TRACE("Clearing sector hash map");
5697 // Clear sector hash map
5699 ctx->sector_hash_map = NULL;
5700 }
5701
5702 return AARUF_STATUS_OK;
5703}
static void remove_dpm_index_entries(aaruformat_context *ctx)
Removes every Data Position Measurement block from the index.
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_dpm_block(aaruformat_context *ctx)
Serializes the Data Position Measurement 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.
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_bd_sector_edc_block(aaruformat_context *ctx)
Serialize Blu-ray long-sector EDC (4 bytes per sector) to the image file.
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:86
#define MD5_DIGEST_LENGTH
Definition context.h:75
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
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
crc64_ctx * aaruf_crc64_init(void)
Initializes a CRC64 context.
Definition crc64.c:32
@ 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
@ DataPositionMeasurementBlock
Block containing data position measurements (reserved / TODO).
Definition enums.h:177
@ 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
@ kDataTypeBdSectorEdc
Blu-ray Sector EDC.
Definition enums.h:150
@ 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:585
#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:82
#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:1569
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
int32_t aaruf_finalize_write(aaruformat_context *ctx)
Finalize a write-mode image: flush all pending blocks, DDTs, and the index.
#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:116
bool hasSha256
True if sha256[] buffer populated.
Definition context.h:109
uint8_t sha1[20]
SHA-1 digest (20 bytes).
Definition context.h:113
uint8_t sha256[32]
SHA-256 digest (32 bytes).
Definition context.h:114
uint8_t md5[16]
MD5 digest (16 bytes).
Definition context.h:112
bool hasSpamSum
True if spamsum pointer allocated and signature read.
Definition context.h:111
bool hasSha1
True if sha1[] buffer populated.
Definition context.h:108
uint8_t blake3[BLAKE3_OUT_LEN]
BLAKE3 digest (32 bytes).
Definition context.h:115
bool hasMd5
True if md5[] buffer populated.
Definition context.h:107
bool hasBlake3
True if blake3[] buffer populated.
Definition context.h:110
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:452
uint8_t * firmware
Firmware version string or NULL.
Definition context.h:439
uint8_t * revision
Hardware revision string or NULL.
Definition context.h:438
uint8_t * model
Model string or NULL.
Definition context.h:437
uint8_t * softwareName
Dump software name or NULL.
Definition context.h:441
struct DumpExtent * extents
Array of extents (entry.extents elements) or NULL.
Definition context.h:435
uint8_t * manufacturer
Manufacturer string (UTF-8) or NULL.
Definition context.h:436
uint8_t * softwareVersion
Dump software version or NULL.
Definition context.h:442
uint8_t * serial
Serial number string or NULL.
Definition context.h:440
DumpHardwareEntry entry
Fixed-size header with lengths & counts.
Definition context.h:434
uint8_t * softwareOperatingSystem
Host operating system string or NULL.
Definition context.h:443
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:148
uint64_t value
Value: DDT entry.
Definition context.h:149
Describes a single logical file on a tape medium.
Definition tape.h:134
TapeFileEntry fileEntry
The actual tape file data.
Definition context.h:135
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:142
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:178
uint8_t * media_barcode
Barcode of the media represented by the image.
Definition context.h:232
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:195
Checksums checksums
Whole-image checksums discovered.
Definition context.h:279
uint8_t * creator
Who (person) created the image?
Definition context.h:226
bool dirty_checksum_block
True if checksum block should be written during close.
Definition context.h:332
bool deduplicate
Storage deduplication active (duplicates coalesce).
Definition context.h:310
size_t dpm_block_length
Length of dpm_block in bytes, 0 if not present.
Definition context.h:224
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:311
uint8_t * cicm_block
CICM XML payload.
Definition context.h:221
uint8_t * sector_cpr_mai
DVD sector CPR_MAI (6 bytes) if present.
Definition context.h:214
bool dirty_media_tags
True if media tags should be written during close.
Definition context.h:343
hash_map_t * sector_hash_map
Deduplication hash map (fingerprint->entry mapping).
Definition context.h:263
FluxHeader flux_data_header
Flux data header (if present).
Definition context.h:323
UT_array * flux_captures
Pending flux capture payloads (write path).
Definition context.h:325
bool dirty_dpm_block
True if DPM block should be written or removed during close.
Definition context.h:352
sha256_ctx sha256_context
Opaque SHA-256 context for streaming updates.
Definition context.h:282
bool calculating_sha256
True if whole-image SHA-256 being calculated on-the-fly.
Definition context.h:285
bool dirty_primary_ddt
True if primary DDT table should be written during close.
Definition context.h:330
uint8_t * drive_firmware_revision
Firmware revision of the drive used to read the media represented by the image.
Definition context.h:238
uint8_t * media_serial_number
Serial number of the media represented by the image.
Definition context.h:231
uint8_t * sector_ied
DVD sector IED (2 bytes) if present.
Definition context.h:213
md5_ctx md5_context
Opaque MD5 context for streaming updates.
Definition context.h:280
bool dirty_sector_suffix_block
True if sector suffix block should be written during close.
Definition context.h:337
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:193
MetadataBlockHeader metadata_block_header
Metadata block header.
Definition context.h:240
uint8_t * sector_prefix
Raw per-sector prefix (e.g., sync+header) uncorrected.
Definition context.h:206
bool dirty_dvd_title_key_decrypted_block
True if decrypted title key block should be written during close.
Definition context.h:342
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:192
tapeFileHashEntry * tape_files
Hash table root for tape files.
Definition context.h:318
bool dirty_mode2_subheaders_block
True if MODE2 subheader block should be written during close.
Definition context.h:334
bool dirty_tape_partition_block
True if tape partition block should be written during close.
Definition context.h:346
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:196
bool dirty_cicm_block
True if CICM metadata block should be written during close.
Definition context.h:350
bool is_tape
True if the image is a tape image.
Definition context.h:320
uint8_t * sector_edc
DVD or Blu-ray sector EDC (4 bytes) if present (see DvdSectorEdc / BdSectorEdc data types).
Definition context.h:215
bool calculating_sha1
True if whole-image SHA-1 being calculated on-the-fly.
Definition context.h:284
uint8_t * media_model
Model of the media represented by the image.
Definition context.h:230
bool dirty_tracks_block
True if tracks block should be written during close.
Definition context.h:333
uint8_t * drive_serial_number
Serial number of the drive used to read the media represented by the image.
Definition context.h:236
bool dirty_sector_prefix_block
True if sector prefix block should be written during close.
Definition context.h:335
uint8_t * drive_manufacturer
Manufacturer of the drive used to read the media represented by the image.
Definition context.h:234
bool dirty_index_block
True if index block should be written during close.
Definition context.h:354
uint8_t * sector_suffix
Raw per-sector suffix (EDC/ECC) uncorrected.
Definition context.h:208
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:181
FluxEntry * flux_entries
Array of flux entries (flux_data_header.entries elements).
Definition context.h:324
bool dirty_json_block
True if JSON metadata block should be written during close.
Definition context.h:351
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:188
spamsum_ctx * spamsum_context
Opaque SpamSum context for streaming updates.
Definition context.h:277
CicmMetadataBlock cicm_block_header
CICM metadata header (if present).
Definition context.h:241
size_t sector_prefix_offset
Current position in sector_prefix.
Definition context.h:296
uint8_t * drive_model
Model of the drive used to read the media represented by the image.
Definition context.h:235
bool dirty_single_level_ddt
True if single-level DDT should be written during close.
Definition context.h:331
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:290
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:191
bool calculating_spamsum
True if whole-image SpamSum being calculated on-the-fly.
Definition context.h:286
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:198
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:274
blake3_hasher * blake3_context
Opaque BLAKE3 context for streaming updates.
Definition context.h:278
bool calculating_blake3
True if whole-image BLAKE3 being calculated on-the-fly.
Definition context.h:287
struct DumpHardwareEntriesWithData * dump_hardware_entries_with_data
Array of dump hardware entries + strings.
Definition context.h:219
bool calculating_md5
True if whole-image MD5 being calculated on-the-fly.
Definition context.h:283
bool dirty_sector_suffix_ddt
True if sector suffix DDT should be written during close.
Definition context.h:338
GeometryBlockHeader geometry_block
Logical geometry block (if present).
Definition context.h:239
size_t sector_suffix_offset
Current position in sector_suffix.
Definition context.h:297
bool has_zstd_blocks
True if any block was actually written with Zstandard compression.
Definition context.h:313
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:194
uint8_t * json_block
JSON metadata block payload (UTF-8).
Definition context.h:222
int zstd_level
Zstandard compression level (writing path, default 19).
Definition context.h:314
uint8_t * media_part_number
Part number of the media represented by the image.
Definition context.h:233
uint8_t * sector_decrypted_title_key
DVD decrypted title key (5 bytes) if present.
Definition context.h:216
bool dirty_dumphw_block
True if dump hardware block should be written during close.
Definition context.h:349
AaruMetadataJsonBlockHeader json_block_header
JSON metadata block header (if present).
Definition context.h:243
bool dirty_metadata_block
True if metadata block should be written during close.
Definition context.h:348
uint8_t * sector_subchannel
Raw 96-byte subchannel (if captured).
Definition context.h:210
uint8_t * comments
Image comments.
Definition context.h:228
bool dirty_sector_subchannel_block
True if subchannel block should be written during close.
Definition context.h:339
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:182
bool dirty_dvd_long_sector_blocks
True if DVD long sector blocks should be written during close.
Definition context.h:340
bool no_user_data_ddt
True if image intentionally has no user-data DDT (e.g. flux-only image). When set,...
Definition context.h:203
bool dirty_sector_prefix_ddt
True if sector prefix DDT should be written during close.
Definition context.h:336
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:262
int num_threads
Compression worker threads (1 = single-threaded, default).
Definition context.h:315
bool dirty_tape_file_block
True if tape file block should be written during close.
Definition context.h:345
uint8_t * mode2_subheaders
MODE2 Form1/Form2 8-byte subheaders (concatenated).
Definition context.h:211
bool dirty_bd_sector_edc_block
True if Blu-ray sector EDC data block should be written during close.
Definition context.h:341
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:270
bool dirty_flux_block
True if flux block should be written during close.
Definition context.h:353
uint8_t * sector_id
DVD sector ID (4 bytes) if present.
Definition context.h:212
DumpHardwareHeader dump_hardware_header
Dump hardware header.
Definition context.h:242
bool use_zstd
Use Zstandard instead of LZMA for data blocks.
Definition context.h:312
sha1_ctx sha1_context
Opaque SHA-1 context for streaming updates.
Definition context.h:281
bool dirty_secondary_ddt
True if secondary DDT tables should be written during close.
Definition context.h:329
bool dirty_tape_ddt
True if tape DDT should be written during close.
Definition context.h:344
TapePartitionHashEntry * tape_partitions
Hash table root for tape partitions.
Definition context.h:319
uint8_t * dpm_block
Data Position Measurement block, header included.
Definition context.h:223
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:309
TrackEntry * track_entries
Full track list (tracksHeader.entries elements).
Definition context.h:252
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:197
uint8_t * media_title
Title of the media represented by the image.
Definition context.h:227
bool dirty_geometry_block
True if geometry block should be written during close.
Definition context.h:347
uint8_t * media_manufacturer
Manufacturer of the media represented by the image.
Definition context.h:229
TracksHeader tracks_header
Tracks header (optical) if present.
Definition context.h:254
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:125
uint8_t * data
Tag data blob (opaque to library core); length bytes long.
Definition context.h:126
int32_t type
Numeric type identifier.
Definition context.h:127
uint32_t length
Length in bytes of data.
Definition context.h:128