libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
close.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 */
36
37#include <errno.h>
38#include <stdio.h>
39#include <stdlib.h>
40
41#ifdef __linux__
42#include <sys/mman.h>
43#endif
44
45#include <aaruformat.h>
46
47#include "internal.h"
48#include "log.h"
49
78{
79 // Write cached secondary table to file end and update primary table entry with its position
80 // Check if we have a cached table that needs to be written (either it has an offset or exists in memory)
81 bool has_cached_secondary_ddt =
82 ctx->user_data_ddt_header.tableShift > 0 && (ctx->cached_ddt_offset != 0 || ctx->cached_secondary_ddt2 != NULL);
83
84 if(!has_cached_secondary_ddt) return AARUF_STATUS_OK;
85
86 TRACE("Writing cached secondary DDT table to file");
87
88 fseek(ctx->imageStream, 0, SEEK_END);
89 long end_of_file = ftell(ctx->imageStream);
90
91 // Align the position according to block alignment shift
92 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
93 if(end_of_file & alignment_mask)
94 {
95 // Calculate the next aligned position
96 uint64_t aligned_position = end_of_file + alignment_mask & ~alignment_mask;
97
98 // Seek to the aligned position and pad with zeros if necessary
99 fseek(ctx->imageStream, aligned_position, SEEK_SET);
100 end_of_file = aligned_position;
101
102 TRACE("Aligned DDT write position from %ld to %" PRIu64 " (alignment shift: %d)",
103 ftell(ctx->imageStream) - (aligned_position - end_of_file), aligned_position,
105 }
106
107 // Prepare DDT header for the cached table
108 DdtHeader2 ddt_header = {0};
110 ddt_header.type = UserData;
111 ddt_header.compression = ctx->compression_enabled ? Lzma : None;
112 ddt_header.levels = ctx->user_data_ddt_header.levels;
113 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
114 ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
115 ddt_header.negative = ctx->user_data_ddt_header.negative;
116 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
118 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
119 ddt_header.tableShift = 0; // Secondary tables are single level
120
121 uint64_t items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
122 ddt_header.blocks = items_per_ddt_entry;
123 ddt_header.entries = items_per_ddt_entry;
124 ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry;
125
126 // Calculate data size
127 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
128
129 // Calculate CRC64 of the data
130 crc64_ctx *crc64_context = aaruf_crc64_init();
131 if(crc64_context != NULL)
132 {
133 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
134
135 uint64_t crc64;
136 aaruf_crc64_final(crc64_context, &crc64);
137 ddt_header.crc64 = crc64;
138 }
139
140 // Free CRC64 context
141 aaruf_crc64_free(crc64_context);
142
143 uint8_t *buffer = NULL;
144 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
145
146 if(ddt_header.compression == None)
147 {
148 buffer = (uint8_t *)ctx->cached_secondary_ddt2;
149 ddt_header.cmpCrc64 = ddt_header.crc64;
150 }
151 else
152 {
153 buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
154 if(buffer == NULL)
155 {
156 TRACE("Failed to allocate memory for secondary DDT v2 compression");
158 }
159
160 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
161 size_t props_size = LZMA_PROPERTIES_LENGTH;
162 aaruf_lzma_encode_buffer(buffer, &dst_size,
163
164 (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length, lzma_properties, &props_size,
165 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
166
167 ddt_header.cmpLength = (uint32_t)dst_size;
168
169 if(ddt_header.cmpLength >= ddt_header.length)
170 {
171 ddt_header.compression = None;
172 free(buffer);
173 buffer = (uint8_t *)ctx->cached_secondary_ddt2;
174 }
175 }
176
177 if(ddt_header.compression == None)
178 {
179 ddt_header.cmpLength = ddt_header.length;
180 ddt_header.cmpCrc64 = ddt_header.crc64;
181 }
182 else
183 ddt_header.cmpCrc64 = aaruf_crc64_data(buffer, ddt_header.cmpLength);
184
185 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
186
187 // Write header
188 if(fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
189 {
190 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
191
192 // Write data
193 if(fwrite(buffer, ddt_header.cmpLength, 1, ctx->imageStream) == 1)
194 {
195 // Update primary table entry to point to new location
196 const uint64_t new_secondary_table_block_offset =
197 end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
198
199 ctx->user_data_ddt2[ctx->cached_ddt_position] = (uint64_t)new_secondary_table_block_offset;
200
201 // Update index: remove old entry for cached DDT and add new one
202 TRACE("Updating index for cached secondary DDT");
203
204 // Remove old index entry for the cached DDT
205 if(ctx->cached_ddt_offset != 0)
206 {
207 TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
208 const IndexEntry *entry = NULL;
209
210 // Find and remove the old index entry
211 for(unsigned int k = 0; k < utarray_len(ctx->index_entries); k++)
212 {
213 entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
214 if(entry && entry->offset == ctx->cached_ddt_offset &&
217 {
218 TRACE("Found old DDT index entry at position %u, removing", k);
219 utarray_erase(ctx->index_entries, k, 1);
220 break;
221 }
222 }
223 }
224
225 // Add new index entry for the newly written secondary DDT
226 IndexEntry new_ddt_entry;
228 new_ddt_entry.dataType = UserData;
229 new_ddt_entry.offset = end_of_file;
230
231 utarray_push_back(ctx->index_entries, &new_ddt_entry);
232 ctx->dirty_index_block = true;
233 TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
234
235 // Write the updated primary table back to its original position in the file
236 long saved_pos = ftell(ctx->imageStream);
237 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
238
239 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
240
241 size_t primary_written_bytes = 0;
242 primary_written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
243
244 if(primary_written_bytes != 1)
245 {
246 TRACE("Could not flush primary DDT table to file.");
248 }
249
250 fseek(ctx->imageStream, saved_pos, SEEK_SET);
251 }
252 else
253 TRACE("Failed to write cached secondary DDT data");
254 }
255 else
256 TRACE("Failed to write cached secondary DDT header");
257
258 // Free the cached table
259 free(ctx->cached_secondary_ddt2);
260 ctx->cached_secondary_ddt2 = NULL;
261 ctx->cached_ddt_offset = 0;
262
263 // Set position
264 fseek(ctx->imageStream, 0, SEEK_END);
265
266 if(ddt_header.compression == Lzma) free(buffer);
267
268 return AARUF_STATUS_OK;
269}
270
289{
290 // Write the cached primary DDT table back to its position in the file
291 if(ctx->user_data_ddt_header.tableShift <= 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
292
293 TRACE("Writing cached primary DDT table back to file");
294
295 // Calculate CRC64 of the primary DDT table data first
296 crc64_ctx *crc64_context = aaruf_crc64_init();
297 if(crc64_context != NULL)
298 {
299 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
300
301 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->user_data_ddt2, primary_table_size);
302
303 uint64_t crc64;
304 aaruf_crc64_final(crc64_context, &crc64);
305
306 // Properly populate all header fields for multi-level DDT primary table
310 // levels, tableLevel, previousLevelOffset, negative, overflow, blockAlignmentShift,
311 // dataShift, tableShift, sizeType, entries, blocks, start are already set during creation
312 ctx->user_data_ddt_header.crc64 = crc64;
313 ctx->user_data_ddt_header.cmpCrc64 = crc64;
314 ctx->user_data_ddt_header.length = primary_table_size;
315 ctx->user_data_ddt_header.cmpLength = primary_table_size;
316
317 TRACE("Calculated CRC64 for primary DDT: 0x%16lX", crc64);
318 }
319
320 // Free CRC64 context
321 aaruf_crc64_free(crc64_context);
322
323 // First write the DDT header
324 fseek(ctx->imageStream, ctx->primary_ddt_offset, SEEK_SET);
325
326 size_t headerWritten = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
327 if(headerWritten != 1)
328 {
329 TRACE("Failed to write primary DDT header to file");
331 }
332
333 // Then write the table data (position is already after the header)
334 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
335
336 // Write the primary table data
337 size_t written_bytes = 0;
338 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
339
340 if(written_bytes == 1)
341 {
342 TRACE("Successfully wrote primary DDT header and table to file (%" PRIu64 " entries, %zu bytes)",
343 ctx->user_data_ddt_header.entries, primary_table_size);
344
345 // Remove any previously existing index entries of the same type before adding
346 TRACE("Removing any previously existing primary DDT index entries");
347 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
348 {
349 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
350 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == UserData)
351 {
352 TRACE("Found existing primary DDT index entry at position %d, removing", k);
353 utarray_erase(ctx->index_entries, k, 1);
354 }
355 }
356
357 // Add primary DDT to index
358 TRACE("Adding primary DDT to index");
359 IndexEntry primary_ddt_entry;
360 primary_ddt_entry.blockType = DeDuplicationTable2;
361 primary_ddt_entry.dataType = UserData;
362 primary_ddt_entry.offset = ctx->primary_ddt_offset;
363
364 utarray_push_back(ctx->index_entries, &primary_ddt_entry);
365 ctx->dirty_index_block = true;
366 TRACE("Added primary DDT index entry at offset %" PRIu64, ctx->primary_ddt_offset);
367 }
368 else
369 TRACE("Failed to write primary DDT table to file");
370
371 return AARUF_STATUS_OK;
372}
373
391{
392 // Write the single level DDT table block aligned just after the header
393 if(ctx->user_data_ddt_header.tableShift != 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
394
395 TRACE("Writing single-level DDT table to file");
396
397 // Calculate CRC64 of the primary DDT table data
398 const size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
399
400 // Properly populate all header fields
404 ctx->user_data_ddt_header.levels = 1; // Single level
405 ctx->user_data_ddt_header.tableLevel = 0; // Top level
406 ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
407 // negative and overflow are already set during creation
408 // blockAlignmentShift, dataShift, tableShift, sizeType, entries, blocks, start are already set
409 ctx->user_data_ddt_header.length = primary_table_size;
410 ctx->user_data_ddt_header.cmpLength = primary_table_size;
411
412 ctx->user_data_ddt_header.crc64 = aaruf_crc64_data((uint8_t *)ctx->user_data_ddt2, primary_table_size);
413
414 TRACE("Calculated CRC64 for single-level DDT: 0x%16lX", ctx->user_data_ddt_header.crc64);
415
416 uint8_t *cmp_buffer = NULL;
417 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
418
420 {
421
422 cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
424 }
425 else
426 {
427 cmp_buffer = malloc((size_t)ctx->user_data_ddt_header.length * 2); // Allocate double size for compression
428 if(cmp_buffer == NULL)
429 {
430 TRACE("Failed to allocate memory for secondary DDT v2 compression");
432 }
433
434 size_t dst_size = (size_t)ctx->user_data_ddt_header.length * 2 * 2;
435 size_t props_size = LZMA_PROPERTIES_LENGTH;
436 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->user_data_ddt2,
437 ctx->user_data_ddt_header.length, lzma_properties, &props_size, 9, ctx->lzma_dict_size,
438 4, 0, 2, 273, 8);
439
440 ctx->user_data_ddt_header.cmpLength = (uint32_t)dst_size;
441
443 {
445 free(cmp_buffer);
446
447 cmp_buffer = (uint8_t *)ctx->user_data_ddt2;
448 }
449 }
450
452 {
455 }
456 else
458 aaruf_crc64_data(cmp_buffer, (uint32_t)ctx->user_data_ddt_header.cmpLength);
459
461
462 // Write the DDT header first
463 fseek(ctx->imageStream, 0, SEEK_END);
464 long ddt_position = ftell(ctx->imageStream);
465 // Align index position to block boundary if needed
466 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
467 if(ddt_position & alignment_mask)
468 {
469 const uint64_t aligned_position = ddt_position + alignment_mask & ~alignment_mask;
470 fseek(ctx->imageStream, aligned_position, SEEK_SET);
471 ddt_position = aligned_position;
472 }
473
474 const size_t header_written = fwrite(&ctx->user_data_ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
475 if(header_written != 1)
476 {
477 TRACE("Failed to write single-level DDT header to file");
479 }
480
481 // Write the primary table data
482 size_t written_bytes = 0;
484 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
485
486 written_bytes = fwrite(cmp_buffer, ctx->user_data_ddt_header.cmpLength, 1, ctx->imageStream);
487
488 if(written_bytes == 1)
489 {
490 TRACE("Successfully wrote single-level DDT header and table to file (%" PRIu64
491 " entries, %zu bytes, %zu compressed bytes)",
493
494 // Remove any previously existing index entries of the same type before adding
495 TRACE("Removing any previously existing single-level DDT index entries");
496 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
497 {
498 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
499 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == UserData)
500 {
501 TRACE("Found existing single-level DDT index entry at position %d, removing", k);
502 utarray_erase(ctx->index_entries, k, 1);
503 }
504 }
505
506 // Add single-level DDT to index
507 TRACE("Adding single-level DDT to index");
508 IndexEntry single_ddt_entry;
509 single_ddt_entry.blockType = DeDuplicationTable2;
510 single_ddt_entry.dataType = UserData;
511 single_ddt_entry.offset = ddt_position;
512
513 utarray_push_back(ctx->index_entries, &single_ddt_entry);
514 ctx->dirty_index_block = true;
515 TRACE("Added single-level DDT index entry at offset %" PRIu64, ddt_position);
516 }
517 else
518 TRACE("Failed to write single-level DDT table data to file");
519
520 // Free compression buffer if it was allocated
521 if(ctx->user_data_ddt_header.compression != None && cmp_buffer != (uint8_t *)ctx->user_data_ddt2) free(cmp_buffer);
522
523 return AARUF_STATUS_OK;
524}
525
634{
635 if(!ctx->is_tape) return AARUF_STATUS_INVALID_CONTEXT;
636
637 // Traverse the tape DDT uthash and find the biggest key
638 uint64_t max_key = 0;
639 TapeDdtHashEntry *entry, *tmp;
640 HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
641 if(entry->key > max_key) max_key = entry->key;
642
643 // Initialize context user data DDT header
647 ctx->user_data_ddt_header.levels = 1; // Single level
648 ctx->user_data_ddt_header.tableLevel = 0; // Top level
649 ctx->user_data_ddt_header.previousLevelOffset = 0; // No previous level for single-level DDT
652 ctx->user_data_ddt_header.tableShift = 0; // Single level
653 ctx->user_data_ddt_header.entries = max_key + 1;
654 ctx->user_data_ddt_header.blocks = max_key + 1;
656 ctx->user_data_ddt_header.length = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
658
659 // Initialize memory for user data DDT
660 ctx->user_data_ddt2 = calloc(ctx->user_data_ddt_header.entries, sizeof(uint64_t));
661 if(ctx->user_data_ddt2 == NULL)
662 {
663 TRACE("Failed to allocate memory for tape DDT table");
665 }
666
667 // Populate user data DDT from tape DDT uthash
668 HASH_ITER(hh, ctx->tape_ddt, entry, tmp)
669 if(entry->key < ctx->user_data_ddt_header.blocks) ctx->user_data_ddt2[entry->key] = entry->value;
670
671 // Do not repeat code
672 return write_single_level_ddt(ctx);
673}
674
692{
693 uint64_t alignment_mask;
694 uint64_t aligned_position;
695
696 // Finalize pending checksums
697 if(ctx->calculating_md5)
698 {
699 ctx->checksums.hasMd5 = true;
701 }
702 if(ctx->calculating_sha1)
703 {
704 ctx->checksums.hasSha1 = true;
706 }
707 if(ctx->calculating_sha256)
708 {
709 ctx->checksums.hasSha256 = true;
711 }
712 if(ctx->calculating_spamsum)
713 {
714 ctx->checksums.hasSpamSum = true;
715 ctx->checksums.spamsum = calloc(1, FUZZY_MAX_RESULT);
718 }
719 if(ctx->calculating_blake3)
720 {
721 ctx->checksums.hasBlake3 = true;
722 blake3_hasher_finalize(ctx->blake3_context, ctx->checksums.blake3, BLAKE3_OUT_LEN);
723 free(ctx->blake3_context);
724 }
725
726 // Write the checksums block
727 bool has_checksums = ctx->checksums.hasMd5 || ctx->checksums.hasSha1 || ctx->checksums.hasSha256 ||
729
730 if(!has_checksums) return;
731
732 ChecksumHeader checksum_header = {0};
733 checksum_header.identifier = ChecksumBlock;
734
735 fseek(ctx->imageStream, 0, SEEK_END);
736 long checksum_position = ftell(ctx->imageStream);
737 // Align index position to block boundary if needed
738 alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
739 if(checksum_position & alignment_mask)
740 {
741 aligned_position = checksum_position + alignment_mask & ~alignment_mask;
742 fseek(ctx->imageStream, aligned_position, SEEK_SET);
743 checksum_position = aligned_position;
744 }
745
746 // Skip checksum_header
747 fseek(ctx->imageStream, sizeof(checksum_header), SEEK_CUR);
748
749 if(ctx->checksums.hasMd5)
750 {
751 TRACE("Writing MD5 checksum entry");
752 ChecksumEntry md5_entry = {0};
753 md5_entry.length = MD5_DIGEST_LENGTH;
754 md5_entry.type = Md5;
755 fwrite(&md5_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
756 fwrite(&ctx->checksums.md5, MD5_DIGEST_LENGTH, 1, ctx->imageStream);
757 checksum_header.length += sizeof(ChecksumEntry) + MD5_DIGEST_LENGTH;
758 checksum_header.entries++;
759 }
760
761 if(ctx->checksums.hasSha1)
762 {
763 TRACE("Writing SHA1 checksum entry");
764 ChecksumEntry sha1_entry = {0};
765 sha1_entry.length = SHA1_DIGEST_LENGTH;
766 sha1_entry.type = Sha1;
767 fwrite(&sha1_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
768 fwrite(&ctx->checksums.sha1, SHA1_DIGEST_LENGTH, 1, ctx->imageStream);
769 checksum_header.length += sizeof(ChecksumEntry) + SHA1_DIGEST_LENGTH;
770 checksum_header.entries++;
771 }
772
773 if(ctx->checksums.hasSha256)
774 {
775 TRACE("Writing SHA256 checksum entry");
776 ChecksumEntry sha256_entry = {0};
777 sha256_entry.length = SHA256_DIGEST_LENGTH;
778 sha256_entry.type = Sha256;
779 fwrite(&sha256_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
780 fwrite(&ctx->checksums.sha256, SHA256_DIGEST_LENGTH, 1, ctx->imageStream);
781 checksum_header.length += sizeof(ChecksumEntry) + SHA256_DIGEST_LENGTH;
782 checksum_header.entries++;
783 }
784
785 if(ctx->checksums.hasSpamSum)
786 {
787 TRACE("Writing SpamSum checksum entry");
788 ChecksumEntry spamsum_entry = {0};
789 spamsum_entry.length = strlen((const char *)ctx->checksums.spamsum);
790 spamsum_entry.type = SpamSum;
791 fwrite(&spamsum_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
792 fwrite(ctx->checksums.spamsum, spamsum_entry.length, 1, ctx->imageStream);
793 checksum_header.length += sizeof(ChecksumEntry) + spamsum_entry.length;
794 checksum_header.entries++;
795 }
796
797 if(ctx->checksums.hasBlake3)
798 {
799 TRACE("Writing BLAKE3 checksum entry");
800 ChecksumEntry blake3_entry = {0};
801 blake3_entry.length = BLAKE3_OUT_LEN;
802 blake3_entry.type = Blake3;
803 fwrite(&blake3_entry, sizeof(ChecksumEntry), 1, ctx->imageStream);
804 fwrite(&ctx->checksums.blake3, BLAKE3_OUT_LEN, 1, ctx->imageStream);
805 checksum_header.length += sizeof(ChecksumEntry) + BLAKE3_OUT_LEN;
806 checksum_header.entries++;
808 }
809
810 fseek(ctx->imageStream, checksum_position, SEEK_SET);
811 TRACE("Writing checksum header");
812 fwrite(&checksum_header, sizeof(ChecksumHeader), 1, ctx->imageStream);
813
814 // Remove any previously existing index entries of the same type before adding
815 TRACE("Removing any previously existing checksum block index entries");
816 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
817 {
818 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
819 if(entry && entry->blockType == ChecksumBlock && entry->dataType == 0)
820 {
821 TRACE("Found existing checksum block index entry at position %d, removing", k);
822 utarray_erase(ctx->index_entries, k, 1);
823 }
824 }
825
826 // Add checksum block to index
827 TRACE("Adding checksum block to index");
828 IndexEntry checksum_index_entry;
829 checksum_index_entry.blockType = ChecksumBlock;
830 checksum_index_entry.dataType = 0;
831 checksum_index_entry.offset = checksum_position;
832
833 utarray_push_back(ctx->index_entries, &checksum_index_entry);
834 ctx->dirty_index_block = true;
835 TRACE("Added checksum block index entry at offset %" PRIu64, checksum_position);
836}
837
849{
850 // Write tracks block
851 if(ctx->tracks_header.entries <= 0 || ctx->track_entries == NULL) return;
852
853 fseek(ctx->imageStream, 0, SEEK_END);
854 long tracks_position = ftell(ctx->imageStream);
855 // Align index position to block boundary if needed
856 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
857 if(tracks_position & alignment_mask)
858 {
859 uint64_t aligned_position = tracks_position + alignment_mask & ~alignment_mask;
860 fseek(ctx->imageStream, aligned_position, SEEK_SET);
861 tracks_position = aligned_position;
862 }
863
864 TRACE("Writing tracks block at position %ld", tracks_position);
865 // Write header
866 if(fwrite(&ctx->tracks_header, sizeof(TracksHeader), 1, ctx->imageStream) == 1)
867 {
868 // Write entries
869 size_t written_entries =
870 fwrite(ctx->track_entries, sizeof(TrackEntry), ctx->tracks_header.entries, ctx->imageStream);
871
872 if(written_entries == ctx->tracks_header.entries)
873 {
874 TRACE("Successfully wrote tracks block with %u entries", ctx->tracks_header.entries);
875
876 // Remove any previously existing index entries of the same type before adding
877 TRACE("Removing any previously existing tracks block index entries");
878 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
879 {
880 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
881 if(entry && entry->blockType == TracksBlock && entry->dataType == 0)
882 {
883 TRACE("Found existing tracks block index entry at position %d, removing", k);
884 utarray_erase(ctx->index_entries, k, 1);
885 }
886 }
887
888 // Add tracks block to index
889 TRACE("Adding tracks block to index");
890
891 IndexEntry tracks_index_entry;
892 tracks_index_entry.blockType = TracksBlock;
893 tracks_index_entry.dataType = 0;
894 tracks_index_entry.offset = tracks_position;
895 utarray_push_back(ctx->index_entries, &tracks_index_entry);
896 ctx->dirty_index_block = true;
897 TRACE("Added tracks block index entry at offset %" PRIu64, tracks_position);
898 }
899 }
900}
901
915{
916 // Write MODE 2 subheader data block
917 if(ctx->mode2_subheaders == NULL) return;
918
919 fseek(ctx->imageStream, 0, SEEK_END);
920 long mode2_subheaders_position = ftell(ctx->imageStream);
921 // Align index position to block boundary if needed
922 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
923 if(mode2_subheaders_position & alignment_mask)
924 {
925 uint64_t aligned_position = mode2_subheaders_position + alignment_mask & ~alignment_mask;
926 fseek(ctx->imageStream, aligned_position, SEEK_SET);
927 mode2_subheaders_position = aligned_position;
928 }
929
930 TRACE("Writing MODE 2 subheaders block at position %ld", mode2_subheaders_position);
931 BlockHeader subheaders_block = {0};
932 subheaders_block.identifier = DataBlock;
933 subheaders_block.type = CompactDiscMode2Subheader;
934 subheaders_block.compression = ctx->compression_enabled ? Lzma : None;
935 subheaders_block.length =
937 8;
938
939 // Calculate CRC64
940 subheaders_block.crc64 = aaruf_crc64_data(ctx->mode2_subheaders, subheaders_block.length);
941
942 uint8_t *buffer = NULL;
943 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
944
945 if(subheaders_block.compression == None)
946 {
947 buffer = ctx->mode2_subheaders;
948 subheaders_block.cmpCrc64 = subheaders_block.crc64;
949 }
950 else
951 {
952 buffer = malloc((size_t)subheaders_block.length * 2); // Allocate double size for compression
953 if(buffer == NULL)
954 {
955 TRACE("Failed to allocate memory for MODE 2 subheaders compression");
956 return;
957 }
958
959 size_t dst_size = (size_t)subheaders_block.length * 2 * 2;
960 size_t props_size = LZMA_PROPERTIES_LENGTH;
961 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->mode2_subheaders, subheaders_block.length, lzma_properties,
962 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
963
964 subheaders_block.cmpLength = (uint32_t)dst_size;
965
966 if(subheaders_block.cmpLength >= subheaders_block.length)
967 {
968 subheaders_block.compression = None;
969 free(buffer);
970 buffer = ctx->mode2_subheaders;
971 }
972 }
973
974 if(subheaders_block.compression == None)
975 {
976 subheaders_block.cmpLength = subheaders_block.length;
977 subheaders_block.cmpCrc64 = subheaders_block.crc64;
978 }
979 else
980 subheaders_block.cmpCrc64 = aaruf_crc64_data(buffer, subheaders_block.cmpLength);
981
982 const size_t length_to_write = subheaders_block.cmpLength;
983 if(subheaders_block.compression == Lzma) subheaders_block.cmpLength += LZMA_PROPERTIES_LENGTH;
984
985 // Write header
986 if(fwrite(&subheaders_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
987 {
988 if(subheaders_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
989
990 // Write data
991 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
992 if(written_bytes == 1)
993 {
994 TRACE("Successfully wrote MODE 2 subheaders block (%" PRIu64 " bytes)", subheaders_block.cmpLength);
995
996 // Remove any previously existing index entries of the same type before adding
997 TRACE("Removing any previously existing MODE 2 subheaders block index entries");
998 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
999 {
1000 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1001 if(entry && entry->blockType == DataBlock && entry->dataType == CompactDiscMode2Subheader)
1002 {
1003 TRACE("Found existing MODE 2 subheaders block index entry at position %d, removing", k);
1004 utarray_erase(ctx->index_entries, k, 1);
1005 }
1006 }
1007
1008 // Add MODE 2 subheaders block to index
1009 TRACE("Adding MODE 2 subheaders block to index");
1010 IndexEntry mode2_subheaders_index_entry;
1011 mode2_subheaders_index_entry.blockType = DataBlock;
1012 mode2_subheaders_index_entry.dataType = CompactDiscMode2Subheader;
1013 mode2_subheaders_index_entry.offset = mode2_subheaders_position;
1014 utarray_push_back(ctx->index_entries, &mode2_subheaders_index_entry);
1015 ctx->dirty_index_block = true;
1016 TRACE("Added MODE 2 subheaders block index entry at offset %" PRIu64, mode2_subheaders_position);
1017 }
1018 }
1019
1020 if(subheaders_block.compression == Lzma) free(buffer);
1021}
1022
1046{
1047 if(ctx->sector_prefix == NULL) return;
1048
1049 fseek(ctx->imageStream, 0, SEEK_END);
1050 long prefix_position = ftell(ctx->imageStream);
1051 // Align index position to block boundary if needed
1052 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1053 if(prefix_position & alignment_mask)
1054 {
1055 uint64_t aligned_position = prefix_position + alignment_mask & ~alignment_mask;
1056 fseek(ctx->imageStream, aligned_position, SEEK_SET);
1057 prefix_position = aligned_position;
1058 }
1059
1060 TRACE("Writing sector prefix block at position %ld", prefix_position);
1061 BlockHeader prefix_block = {0};
1062 prefix_block.identifier = DataBlock;
1063 prefix_block.type = CdSectorPrefix;
1064 prefix_block.compression = ctx->compression_enabled ? Lzma : None;
1065 prefix_block.length = (uint32_t)ctx->sector_prefix_offset;
1066
1067 // Calculate CRC64
1068 prefix_block.crc64 = aaruf_crc64_data(ctx->sector_prefix, prefix_block.length);
1069
1070 uint8_t *buffer = NULL;
1071 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1072
1073 if(prefix_block.compression == None)
1074 {
1075 buffer = ctx->sector_prefix;
1076 prefix_block.cmpCrc64 = prefix_block.crc64;
1077 }
1078 else
1079 {
1080 buffer = malloc((size_t)prefix_block.length * 2); // Allocate double size for compression
1081 if(buffer == NULL)
1082 {
1083 TRACE("Failed to allocate memory for CD sector prefix compression");
1084 return;
1085 }
1086
1087 size_t dst_size = (size_t)prefix_block.length * 2 * 2;
1088 size_t props_size = LZMA_PROPERTIES_LENGTH;
1089 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_prefix, prefix_block.length, lzma_properties,
1090 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1091
1092 prefix_block.cmpLength = (uint32_t)dst_size;
1093
1094 if(prefix_block.cmpLength >= prefix_block.length)
1095 {
1096 prefix_block.compression = None;
1097 free(buffer);
1098 buffer = ctx->sector_prefix;
1099 }
1100 }
1101
1102 if(prefix_block.compression == None)
1103 {
1104 prefix_block.cmpLength = prefix_block.length;
1105 prefix_block.cmpCrc64 = prefix_block.crc64;
1106 }
1107 else
1108 prefix_block.cmpCrc64 = aaruf_crc64_data(buffer, prefix_block.cmpLength);
1109
1110 const size_t length_to_write = prefix_block.cmpLength;
1111 if(prefix_block.compression == Lzma) prefix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1112
1113 // Write header
1114 if(fwrite(&prefix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1115 {
1116 if(prefix_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1117
1118 // Write data
1119 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1120 if(written_bytes == 1)
1121 {
1122 TRACE("Successfully wrote CD sector prefix block (%" PRIu64 " bytes)", prefix_block.cmpLength);
1123
1124 // Remove any previously existing index entries of the same type before adding
1125 TRACE("Removing any previously existing CD sector prefix block index entries");
1126 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1127 {
1128 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1129 if(entry && entry->blockType == DataBlock && entry->dataType == CdSectorPrefix)
1130 {
1131 TRACE("Found existing CD sector prefix block index entry at position %d, removing", k);
1132 utarray_erase(ctx->index_entries, k, 1);
1133 }
1134 }
1135
1136 // Add prefix block to index
1137 TRACE("Adding CD sector prefix block to index");
1138 IndexEntry prefix_index_entry;
1139 prefix_index_entry.blockType = DataBlock;
1140 prefix_index_entry.dataType = CdSectorPrefix;
1141 prefix_index_entry.offset = prefix_position;
1142 utarray_push_back(ctx->index_entries, &prefix_index_entry);
1143 ctx->dirty_index_block = true;
1144 TRACE("Added CD sector prefix block index entry at offset %" PRIu64, prefix_position);
1145 }
1146 }
1147
1148 if(prefix_block.compression == Lzma) free(buffer);
1149}
1150
1183{
1184 if(ctx->sector_suffix == NULL) return;
1185
1186 fseek(ctx->imageStream, 0, SEEK_END);
1187 long suffix_position = ftell(ctx->imageStream);
1188 // Align index position to block boundary if needed
1189 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1190 if(suffix_position & alignment_mask)
1191 {
1192 const uint64_t aligned_position = suffix_position + alignment_mask & ~alignment_mask;
1193 fseek(ctx->imageStream, aligned_position, SEEK_SET);
1194 suffix_position = aligned_position;
1195 }
1196
1197 TRACE("Writing sector suffix block at position %ld", suffix_position);
1198 BlockHeader suffix_block = {0};
1199 suffix_block.identifier = DataBlock;
1200 suffix_block.type = CdSectorSuffix;
1201 suffix_block.compression = ctx->compression_enabled ? Lzma : None;
1202 suffix_block.length = (uint32_t)ctx->sector_suffix_offset;
1203
1204 // Calculate CRC64
1205 suffix_block.crc64 = aaruf_crc64_data(ctx->sector_suffix, suffix_block.length);
1206
1207 uint8_t *buffer = NULL;
1208 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1209
1210 if(suffix_block.compression == None)
1211 {
1212 buffer = ctx->sector_suffix;
1213 suffix_block.cmpCrc64 = suffix_block.crc64;
1214 }
1215 else
1216 {
1217 buffer = malloc((size_t)suffix_block.length * 2); // Allocate double size for compression
1218 if(buffer == NULL)
1219 {
1220 TRACE("Failed to allocate memory for CD sector suffix compression");
1221 return;
1222 }
1223
1224 size_t dst_size = (size_t)suffix_block.length * 2 * 2;
1225 size_t props_size = LZMA_PROPERTIES_LENGTH;
1226 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_suffix, suffix_block.length, lzma_properties,
1227 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1228
1229 suffix_block.cmpLength = (uint32_t)dst_size;
1230
1231 if(suffix_block.cmpLength >= suffix_block.length)
1232 {
1233 suffix_block.compression = None;
1234 free(buffer);
1235 buffer = ctx->sector_suffix;
1236 }
1237 }
1238
1239 if(suffix_block.compression == None)
1240 {
1241 suffix_block.cmpLength = suffix_block.length;
1242 suffix_block.cmpCrc64 = suffix_block.crc64;
1243 }
1244 else
1245 suffix_block.cmpCrc64 = aaruf_crc64_data(buffer, suffix_block.cmpLength);
1246
1247 const size_t length_to_write = suffix_block.cmpLength;
1248 if(suffix_block.compression == Lzma) suffix_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1249
1250 // Write header
1251 if(fwrite(&suffix_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1252 {
1253 if(suffix_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1254
1255 // Write data
1256 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1257 if(written_bytes == 1)
1258 {
1259 TRACE("Successfully wrote CD sector suffix block (%" PRIu64 " bytes)", suffix_block.cmpLength);
1260
1261 // Remove any previously existing index entries of the same type before adding
1262 TRACE("Removing any previously existing CD sector suffix block index entries");
1263 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1264 {
1265 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1266 if(entry && entry->blockType == DataBlock && entry->dataType == CdSectorSuffix)
1267 {
1268 TRACE("Found existing CD sector suffix block index entry at position %d, removing", k);
1269 utarray_erase(ctx->index_entries, k, 1);
1270 }
1271 }
1272
1273 // Add suffix block to index
1274 TRACE("Adding CD sector suffix block to index");
1275 IndexEntry suffix_index_entry;
1276 suffix_index_entry.blockType = DataBlock;
1277 suffix_index_entry.dataType = CdSectorSuffix;
1278 suffix_index_entry.offset = suffix_position;
1279 utarray_push_back(ctx->index_entries, &suffix_index_entry);
1280 ctx->dirty_index_block = true;
1281 TRACE("Added CD sector suffix block index entry at offset %" PRIu64, suffix_position);
1282 }
1283 }
1284
1285 if(suffix_block.compression == Lzma) free(buffer);
1286}
1287
1316{
1317 if(ctx->sector_prefix_ddt2 == NULL) return;
1318
1319 fseek(ctx->imageStream, 0, SEEK_END);
1320 long prefix_ddt_position = ftell(ctx->imageStream);
1321 // Align index position to block boundary if needed
1322 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1323 if(prefix_ddt_position & alignment_mask)
1324 {
1325 const uint64_t aligned_position = prefix_ddt_position + alignment_mask & ~alignment_mask;
1326 fseek(ctx->imageStream, aligned_position, SEEK_SET);
1327 prefix_ddt_position = aligned_position;
1328 }
1329
1330 TRACE("Writing sector prefix DDT v2 at position %ld", prefix_ddt_position);
1331 DdtHeader2 ddt_header2 = {0};
1332 ddt_header2.identifier = DeDuplicationTable2;
1333 ddt_header2.type = CdSectorPrefix;
1334 ddt_header2.compression = ctx->compression_enabled ? Lzma : None;
1335 ddt_header2.levels = 1;
1336 ddt_header2.tableLevel = 0;
1337 ddt_header2.negative = ctx->user_data_ddt_header.negative;
1338 ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
1340 ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
1341 ddt_header2.tableShift = 0; // Single-level DDT
1342 ddt_header2.entries =
1344 ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
1345 ddt_header2.start = 0;
1346 ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
1347 // Calculate CRC64
1348 ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_prefix_ddt2, (uint32_t)ddt_header2.length);
1349
1350 uint8_t *buffer = NULL;
1351 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1352
1353 if(ddt_header2.compression == None)
1354 {
1355 buffer = (uint8_t *)ctx->sector_prefix_ddt2;
1356 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1357 }
1358 else
1359 {
1360 buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
1361 if(buffer == NULL)
1362 {
1363 TRACE("Failed to allocate memory for sector prefix DDT v2 compression");
1364 return;
1365 }
1366
1367 size_t dst_size = (size_t)ddt_header2.length * 2 * 2;
1368 size_t props_size = LZMA_PROPERTIES_LENGTH;
1369 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_prefix_ddt2, ddt_header2.length,
1370 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1371
1372 ddt_header2.cmpLength = (uint32_t)dst_size;
1373
1374 if(ddt_header2.cmpLength >= ddt_header2.length)
1375 {
1376 ddt_header2.compression = None;
1377 free(buffer);
1378 buffer = (uint8_t *)ctx->sector_prefix_ddt2;
1379 }
1380 }
1381
1382 if(ddt_header2.compression == None)
1383 {
1384 ddt_header2.cmpLength = ddt_header2.length;
1385 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1386 }
1387 else
1388 ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
1389
1390 const size_t length_to_write = ddt_header2.cmpLength;
1391 if(ddt_header2.compression == Lzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
1392
1393 // Write header
1394 if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
1395 {
1396 if(ddt_header2.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1397
1398 // Write data
1399 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1400 if(written_bytes == 1)
1401 {
1402 TRACE("Successfully wrote sector prefix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
1403
1404 // Remove any previously existing index entries of the same type before adding
1405 TRACE("Removing any previously existing sector prefix DDT v2 index entries");
1406 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1407 {
1408 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1409 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == CdSectorPrefix)
1410 {
1411 TRACE("Found existing sector prefix DDT v2 index entry at position %d, removing", k);
1412 utarray_erase(ctx->index_entries, k, 1);
1413 }
1414 }
1415
1416 // Add prefix block to index
1417 TRACE("Adding sector prefix DDT v2 to index");
1418 IndexEntry prefix_ddt_index_entry;
1419 prefix_ddt_index_entry.blockType = DeDuplicationTable2;
1420 prefix_ddt_index_entry.dataType = CdSectorPrefix;
1421 prefix_ddt_index_entry.offset = prefix_ddt_position;
1422 utarray_push_back(ctx->index_entries, &prefix_ddt_index_entry);
1423 ctx->dirty_index_block = true;
1424 TRACE("Added sector prefix DDT v2 index entry at offset %" PRIu64, prefix_ddt_position);
1425 }
1426 }
1427
1428 if(ddt_header2.compression == Lzma) free(buffer);
1429}
1430
1475{
1476 if(ctx->sector_suffix_ddt2 == NULL) return;
1477
1478 fseek(ctx->imageStream, 0, SEEK_END);
1479 long suffix_ddt_position = ftell(ctx->imageStream);
1480 // Align index position to block boundary if needed
1481 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1482 if(suffix_ddt_position & alignment_mask)
1483 {
1484 const uint64_t aligned_position = suffix_ddt_position + alignment_mask & ~alignment_mask;
1485 fseek(ctx->imageStream, aligned_position, SEEK_SET);
1486 suffix_ddt_position = aligned_position;
1487 }
1488
1489 TRACE("Writing sector suffix DDT v2 at position %ld", suffix_ddt_position);
1490 DdtHeader2 ddt_header2 = {0};
1491 ddt_header2.identifier = DeDuplicationTable2;
1492 ddt_header2.type = CdSectorSuffix;
1493 ddt_header2.compression = ctx->compression_enabled ? Lzma : None;
1494 ddt_header2.levels = 1;
1495 ddt_header2.tableLevel = 0;
1496 ddt_header2.negative = ctx->user_data_ddt_header.negative;
1497 ddt_header2.overflow = ctx->user_data_ddt_header.overflow;
1499 ddt_header2.dataShift = ctx->user_data_ddt_header.dataShift;
1500 ddt_header2.tableShift = 0; // Single-level DDT
1501 ddt_header2.entries =
1503 ddt_header2.blocks = ctx->user_data_ddt_header.blocks;
1504 ddt_header2.start = 0;
1505 ddt_header2.length = ddt_header2.entries * sizeof(uint64_t);
1506 // Calculate CRC64
1507 ddt_header2.crc64 = aaruf_crc64_data((uint8_t *)ctx->sector_suffix_ddt2, (uint32_t)ddt_header2.length);
1508
1509 uint8_t *buffer = NULL;
1510 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1511
1512 if(ddt_header2.compression == None)
1513 {
1514 buffer = (uint8_t *)ctx->sector_suffix_ddt2;
1515 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1516 }
1517 else
1518 {
1519 buffer = malloc((size_t)ddt_header2.length * 2); // Allocate double size for compression
1520 if(buffer == NULL)
1521 {
1522 TRACE("Failed to allocate memory for sector suffix DDT v2 compression");
1523 return;
1524 }
1525
1526 size_t dst_size = (size_t)ddt_header2.length * 2 * 2;
1527 size_t props_size = LZMA_PROPERTIES_LENGTH;
1528 aaruf_lzma_encode_buffer(buffer, &dst_size, (uint8_t *)ctx->sector_suffix_ddt2, ddt_header2.length,
1529 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1530
1531 ddt_header2.cmpLength = (uint32_t)dst_size;
1532
1533 if(ddt_header2.cmpLength >= ddt_header2.length)
1534 {
1535 ddt_header2.compression = None;
1536 free(buffer);
1537 buffer = (uint8_t *)ctx->sector_suffix_ddt2;
1538 }
1539 }
1540
1541 if(ddt_header2.compression == None)
1542 {
1543 ddt_header2.cmpLength = ddt_header2.length;
1544 ddt_header2.cmpCrc64 = ddt_header2.crc64;
1545 }
1546 else
1547 ddt_header2.cmpCrc64 = aaruf_crc64_data(buffer, (uint32_t)ddt_header2.cmpLength);
1548
1549 const size_t length_to_write = ddt_header2.cmpLength;
1550 if(ddt_header2.compression == Lzma) ddt_header2.cmpLength += LZMA_PROPERTIES_LENGTH;
1551
1552 // Write header
1553 if(fwrite(&ddt_header2, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
1554 {
1555 if(ddt_header2.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1556
1557 // Write data
1558 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1559 if(written_bytes == 1)
1560 {
1561 TRACE("Successfully wrote sector suffix DDT v2 (%" PRIu64 " bytes)", ddt_header2.cmpLength);
1562
1563 // Remove any previously existing index entries of the same type before adding
1564 TRACE("Removing any previously existing sector suffix DDT v2 block index entries");
1565 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1566 {
1567 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1568 if(entry && entry->blockType == DeDuplicationTable2 && entry->dataType == CdSectorSuffix)
1569 {
1570 TRACE("Found existing sector suffix DDT v2 index entry at position %d, removing", k);
1571 utarray_erase(ctx->index_entries, k, 1);
1572 }
1573 }
1574
1575 // Add suffix block to index
1576 TRACE("Adding sector suffix DDT v2 to index");
1577 IndexEntry suffix_ddt_index_entry;
1578 suffix_ddt_index_entry.blockType = DeDuplicationTable2;
1579 suffix_ddt_index_entry.dataType = CdSectorSuffix;
1580 suffix_ddt_index_entry.offset = suffix_ddt_position;
1581 utarray_push_back(ctx->index_entries, &suffix_ddt_index_entry);
1582 ctx->dirty_index_block = true;
1583 TRACE("Added sector suffix DDT v2 index entry at offset %" PRIu64, suffix_ddt_position);
1584 }
1585 }
1586
1587 if(ddt_header2.compression == Lzma) free(buffer);
1588}
1589
1648{
1649 if(ctx->sector_subchannel == NULL) return;
1650
1651 fseek(ctx->imageStream, 0, SEEK_END);
1652 long block_position = ftell(ctx->imageStream);
1653 // Align index position to block boundary if needed
1654 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1655 if(block_position & alignment_mask)
1656 {
1657 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
1658 fseek(ctx->imageStream, aligned_position, SEEK_SET);
1659 block_position = aligned_position;
1660 }
1661
1662 TRACE("Writing sector subchannel block at position %ld", block_position);
1663 BlockHeader subchannel_block = {0};
1664 subchannel_block.identifier = DataBlock;
1665 subchannel_block.compression = None;
1666
1667 uint8_t *buffer = ctx->sector_subchannel;
1668 bool owns_buffer = false;
1669 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1670
1672 {
1673 subchannel_block.type = CdSectorSubchannel;
1674 subchannel_block.length = (uint32_t)(ctx->user_data_ddt_header.negative + ctx->image_info.Sectors +
1676 96;
1677 subchannel_block.cmpLength = subchannel_block.length;
1678
1679 if(ctx->compression_enabled)
1680 {
1681 uint8_t *cst_buffer = malloc(subchannel_block.length);
1682
1683 if(cst_buffer == NULL)
1684 {
1685 TRACE("Failed to allocate memory for Claunia Subchannel Transform output");
1686 return;
1687 }
1688
1689 uint8_t *dst_buffer = malloc(subchannel_block.length);
1690
1691 if(dst_buffer == NULL)
1692 {
1693 TRACE("Failed to allocate memory for LZMA output");
1694 free(cst_buffer);
1695 return;
1696 }
1697
1698 aaruf_cst_transform(ctx->sector_subchannel, cst_buffer, subchannel_block.length);
1699 size_t dst_size = subchannel_block.length;
1700 size_t props_size = LZMA_PROPERTIES_LENGTH;
1701
1702 aaruf_lzma_encode_buffer(dst_buffer, &dst_size, cst_buffer, subchannel_block.length, lzma_properties,
1703 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1704
1705 free(cst_buffer);
1706
1707 if(dst_size < subchannel_block.length)
1708 {
1710 subchannel_block.cmpLength = (uint32_t)dst_size;
1711 buffer = dst_buffer;
1712 owns_buffer = true;
1713 }
1714 else
1715 {
1716 subchannel_block.compression = None;
1717 free(dst_buffer);
1718 subchannel_block.cmpLength = subchannel_block.length;
1719 }
1720 }
1721 }
1722 else if(ctx->image_info.MetadataMediaType == BlockMedia)
1723 {
1724 switch(ctx->image_info.MediaType)
1725 {
1726 case AppleProfile:
1727 case AppleFileWare:
1728 subchannel_block.type = AppleProfileTag;
1729 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 20;
1730 break;
1731 case AppleSonyDS:
1732 case AppleSonySS:
1733 subchannel_block.type = AppleSonyTag;
1734 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 12;
1735 break;
1736 case PriamDataTower:
1737 subchannel_block.type = PriamDataTowerTag;
1738 subchannel_block.length = (uint32_t)(ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow) * 24;
1739 break;
1740 default:
1741 TRACE("Incorrect media type, not writing sector subchannel block");
1742 return; // Incorrect media type
1743 }
1744 subchannel_block.cmpLength = subchannel_block.length;
1745 subchannel_block.compression = Lzma;
1746
1747 uint8_t *dst_buffer = malloc(subchannel_block.length);
1748
1749 if(dst_buffer == NULL)
1750 {
1751 TRACE("Failed to allocate memory for LZMA output");
1752 return;
1753 }
1754
1755 size_t dst_size = subchannel_block.length;
1756 size_t props_size = LZMA_PROPERTIES_LENGTH;
1757
1758 aaruf_lzma_encode_buffer(dst_buffer, &dst_size, ctx->sector_subchannel, subchannel_block.length,
1759 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1760
1761 if(dst_size < subchannel_block.length)
1762 {
1763 subchannel_block.cmpLength = (uint32_t)dst_size;
1764 buffer = dst_buffer;
1765 owns_buffer = true;
1766 }
1767 else
1768 {
1769 subchannel_block.compression = None;
1770 free(dst_buffer);
1771 subchannel_block.cmpLength = subchannel_block.length;
1772 }
1773 }
1774 else
1775 {
1776 TRACE("Incorrect media type, not writing sector subchannel block");
1777 return; // Incorrect media type
1778 }
1779
1780 // Calculate CRC64 for raw subchannel data and compressed payload when present
1781 subchannel_block.crc64 = aaruf_crc64_data(ctx->sector_subchannel, subchannel_block.length);
1782 if(subchannel_block.compression == None)
1783 subchannel_block.cmpCrc64 = subchannel_block.crc64;
1784 else
1785 subchannel_block.cmpCrc64 = aaruf_crc64_data(buffer, subchannel_block.cmpLength);
1786
1787 const size_t length_to_write = subchannel_block.cmpLength;
1788 if(subchannel_block.compression != None) subchannel_block.cmpLength += LZMA_PROPERTIES_LENGTH;
1789
1790 // Write header
1791 if(fwrite(&subchannel_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
1792 {
1793 if(subchannel_block.compression != None) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1794
1795 // Write data
1796 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
1797 if(written_bytes == 1)
1798 {
1799 TRACE("Successfully wrote sector subchannel block (%" PRIu64 " bytes)", subchannel_block.cmpLength);
1800
1801 // Remove any previously existing index entries of the same type before adding
1802 TRACE("Removing any previously existing subchannel block index entries");
1803 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
1804 {
1805 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
1806 if(entry && entry->blockType == DataBlock && entry->dataType == subchannel_block.type)
1807 {
1808 TRACE("Found existing subchannel block index entry at position %d, removing", k);
1809 utarray_erase(ctx->index_entries, k, 1);
1810 }
1811 }
1812
1813 // Add subchannel block to index
1814 TRACE("Adding sector subchannel block to index");
1815 IndexEntry subchannel_index_entry;
1816 subchannel_index_entry.blockType = DataBlock;
1817 subchannel_index_entry.dataType = subchannel_block.type;
1818 subchannel_index_entry.offset = block_position;
1819 utarray_push_back(ctx->index_entries, &subchannel_index_entry);
1820 ctx->dirty_index_block = true;
1821 TRACE("Added sector subchannel block index entry at offset %" PRIu64, block_position);
1822 }
1823 }
1824
1825 if(owns_buffer) free(buffer);
1826}
1827
1965{
1966 if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL || ctx->sector_edc == NULL)
1967 return;
1968
1969 uint64_t total_sectors =
1971
1972 // Write DVD sector ID block
1973 fseek(ctx->imageStream, 0, SEEK_END);
1974 long id_position = ftell(ctx->imageStream);
1975 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1976 if(id_position & alignment_mask)
1977 {
1978 const uint64_t aligned_position = id_position + alignment_mask & ~alignment_mask;
1979 fseek(ctx->imageStream, aligned_position, SEEK_SET);
1980 id_position = aligned_position;
1981 }
1982 TRACE("Writing DVD sector ID block at position %ld", id_position);
1983 BlockHeader id_block = {0};
1984 id_block.identifier = DataBlock;
1985 id_block.type = DvdSectorId;
1986 id_block.compression = ctx->compression_enabled ? Lzma : None;
1987 id_block.length = (uint32_t)total_sectors * 4;
1988
1989 // Calculate CRC64
1990 id_block.crc64 = aaruf_crc64_data(ctx->sector_id, id_block.length);
1991
1992 uint8_t *buffer = NULL;
1993 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1994
1995 if(id_block.compression == None)
1996 {
1997 buffer = ctx->sector_id;
1998 id_block.cmpCrc64 = id_block.crc64;
1999 }
2000 else
2001 {
2002 buffer = malloc((size_t)id_block.length * 2); // Allocate double size for compression
2003 if(buffer == NULL)
2004 {
2005 TRACE("Failed to allocate memory for DVD sector ID compression");
2006 return;
2007 }
2008
2009 size_t dst_size = (size_t)id_block.length * 2 * 2;
2010 size_t props_size = LZMA_PROPERTIES_LENGTH;
2011 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_id, id_block.length, lzma_properties, &props_size, 9,
2012 ctx->lzma_dict_size, 4, 0, 2, 273, 8);
2013
2014 id_block.cmpLength = (uint32_t)dst_size;
2015
2016 if(id_block.cmpLength >= id_block.length)
2017 {
2018 id_block.compression = None;
2019 free(buffer);
2020 buffer = ctx->sector_id;
2021 }
2022 }
2023
2024 if(id_block.compression == None)
2025 {
2026 id_block.cmpLength = id_block.length;
2027 id_block.cmpCrc64 = id_block.crc64;
2028 }
2029 else
2030 id_block.cmpCrc64 = aaruf_crc64_data(buffer, id_block.cmpLength);
2031
2032 size_t length_to_write = id_block.cmpLength;
2033 if(id_block.compression == Lzma) id_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2034
2035 // Write header
2036 if(fwrite(&id_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2037 {
2038 if(id_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2039
2040 // Write data
2041 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2042 if(written_bytes == 1)
2043 {
2044 TRACE("Successfully wrote DVD sector ID block (%" PRIu64 " bytes)", id_block.cmpLength);
2045
2046 // Remove any previously existing index entries of the same type before adding
2047 TRACE("Removing any previously existing DVD sector ID block index entries");
2048 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2049 {
2050 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2051 if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorId)
2052 {
2053 TRACE("Found existing DVD sector ID block index entry at position %d, removing", k);
2054 utarray_erase(ctx->index_entries, k, 1);
2055 }
2056 }
2057
2058 // Add ID block to index
2059 TRACE("Adding DVD sector ID block to index");
2060 IndexEntry id_index_entry;
2061 id_index_entry.blockType = DataBlock;
2062 id_index_entry.dataType = DvdSectorId;
2063 id_index_entry.offset = id_position;
2064 utarray_push_back(ctx->index_entries, &id_index_entry);
2065 ctx->dirty_index_block = true;
2066 TRACE("Added DVD sector ID block index entry at offset %" PRIu64, id_position);
2067 }
2068 }
2069
2070 if(id_block.compression == Lzma) free(buffer);
2071
2072 // Write DVD sector IED block
2073 fseek(ctx->imageStream, 0, SEEK_END);
2074 long ied_position = ftell(ctx->imageStream);
2075 if(ied_position & alignment_mask)
2076 {
2077 const uint64_t aligned_position = ied_position + alignment_mask & ~alignment_mask;
2078 fseek(ctx->imageStream, aligned_position, SEEK_SET);
2079 ied_position = aligned_position;
2080 }
2081 TRACE("Writing DVD sector IED block at position %ld", ied_position);
2082 BlockHeader ied_block = {0};
2083 ied_block.identifier = DataBlock;
2084 ied_block.type = DvdSectorIed;
2085 ied_block.compression = ctx->compression_enabled ? Lzma : None;
2086 ied_block.length = (uint32_t)total_sectors * 2;
2087 // Calculate CRC64
2088 ied_block.crc64 = aaruf_crc64_data(ctx->sector_ied, ied_block.length);
2089
2090 buffer = NULL;
2091
2092 if(ied_block.compression == None)
2093 {
2094 buffer = ctx->sector_ied;
2095 ied_block.cmpCrc64 = ied_block.crc64;
2096 }
2097 else
2098 {
2099 buffer = malloc((size_t)ied_block.length * 2); // Allocate double size for compression
2100 if(buffer == NULL)
2101 {
2102 TRACE("Failed to allocate memory for DVD sector IED compression");
2103 return;
2104 }
2105
2106 size_t dst_size = (size_t)ied_block.length * 2 * 2;
2107 size_t props_size = LZMA_PROPERTIES_LENGTH;
2108 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_ied, ied_block.length, lzma_properties, &props_size, 9,
2109 ctx->lzma_dict_size, 4, 0, 2, 273, 8);
2110
2111 ied_block.cmpLength = (uint32_t)dst_size;
2112
2113 if(ied_block.cmpLength >= ied_block.length)
2114 {
2115 ied_block.compression = None;
2116 free(buffer);
2117 buffer = ctx->sector_ied;
2118 }
2119 }
2120
2121 if(ied_block.compression == None)
2122 {
2123 ied_block.cmpLength = ied_block.length;
2124 ied_block.cmpCrc64 = ied_block.crc64;
2125 }
2126 else
2127 ied_block.cmpCrc64 = aaruf_crc64_data(buffer, ied_block.cmpLength);
2128
2129 length_to_write = ied_block.cmpLength;
2130 if(ied_block.compression == Lzma) ied_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2131
2132 // Write header
2133 if(fwrite(&ied_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2134 {
2135 if(ied_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2136
2137 // Write data
2138 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2139 if(written_bytes == 1)
2140 {
2141 TRACE("Successfully wrote DVD sector IED block (%" PRIu64 " bytes)", ied_block.cmpLength);
2142
2143 // Remove any previously existing index entries of the same type before adding
2144 TRACE("Removing any previously existing DVD sector IED block index entries");
2145 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2146 {
2147 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2148 if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorIed)
2149 {
2150 TRACE("Found existing DVD sector IED block index entry at position %d, removing", k);
2151 utarray_erase(ctx->index_entries, k, 1);
2152 }
2153 }
2154
2155 // Add IED block to index
2156 TRACE("Adding DVD sector IED block to index");
2157 IndexEntry ied_index_entry;
2158 ied_index_entry.blockType = DataBlock;
2159 ied_index_entry.dataType = DvdSectorIed;
2160 ied_index_entry.offset = ied_position;
2161 utarray_push_back(ctx->index_entries, &ied_index_entry);
2162 ctx->dirty_index_block = true;
2163 TRACE("Added DVD sector IED block index entry at offset %" PRIu64, ied_position);
2164 }
2165 }
2166
2167 if(ied_block.compression == Lzma) free(buffer);
2168
2169 // Write DVD sector CPR/MAI block
2170 fseek(ctx->imageStream, 0, SEEK_END);
2171 long cpr_mai_position = ftell(ctx->imageStream);
2172 if(cpr_mai_position & alignment_mask)
2173 {
2174 const uint64_t aligned_position = cpr_mai_position + alignment_mask & ~alignment_mask;
2175 fseek(ctx->imageStream, aligned_position, SEEK_SET);
2176 cpr_mai_position = aligned_position;
2177 }
2178 TRACE("Writing DVD sector CPR/MAI block at position %ld", cpr_mai_position);
2179 BlockHeader cpr_mai_block = {0};
2180 cpr_mai_block.identifier = DataBlock;
2181 cpr_mai_block.type = DvdSectorCprMai;
2182 cpr_mai_block.compression = ctx->compression_enabled ? Lzma : None;
2183 cpr_mai_block.length = (uint32_t)total_sectors * 6;
2184 // Calculate CRC64
2185 cpr_mai_block.crc64 = aaruf_crc64_data(ctx->sector_cpr_mai, cpr_mai_block.length);
2186
2187 buffer = NULL;
2188
2189 if(cpr_mai_block.compression == None)
2190 {
2191 buffer = ctx->sector_cpr_mai;
2192 cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
2193 }
2194 else
2195 {
2196 buffer = malloc((size_t)cpr_mai_block.length * 2); // Allocate double size for compression
2197 if(buffer == NULL)
2198 {
2199 TRACE("Failed to allocate memory for DVD sector CPR/MAI compression");
2200 return;
2201 }
2202
2203 size_t dst_size = (size_t)cpr_mai_block.length * 2 * 2;
2204 size_t props_size = LZMA_PROPERTIES_LENGTH;
2205 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_cpr_mai, cpr_mai_block.length, lzma_properties,
2206 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
2207
2208 cpr_mai_block.cmpLength = (uint32_t)dst_size;
2209
2210 if(cpr_mai_block.cmpLength >= cpr_mai_block.length)
2211 {
2212 cpr_mai_block.compression = None;
2213 free(buffer);
2214 buffer = ctx->sector_cpr_mai;
2215 }
2216 }
2217
2218 if(cpr_mai_block.compression == None)
2219 {
2220 cpr_mai_block.cmpLength = cpr_mai_block.length;
2221 cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
2222 }
2223 else
2224 cpr_mai_block.cmpCrc64 = aaruf_crc64_data(buffer, cpr_mai_block.cmpLength);
2225
2226 length_to_write = cpr_mai_block.cmpLength;
2227 if(cpr_mai_block.compression == Lzma) cpr_mai_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2228
2229 // Write header
2230 if(fwrite(&cpr_mai_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2231 {
2232 if(cpr_mai_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2233
2234 // Write data
2235 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2236 if(written_bytes == 1)
2237 {
2238 TRACE("Successfully wrote DVD sector CPR/MAI block (%" PRIu64 " bytes)", cpr_mai_block.cmpLength);
2239
2240 // Remove any previously existing index entries of the same type before adding
2241 TRACE("Removing any previously existing DVD sector CPR/MAI block index entries");
2242 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2243 {
2244 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2245 if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorCprMai)
2246 {
2247 TRACE("Found existing DVD sector CPR/MAI block index entry at position %d, removing", k);
2248 utarray_erase(ctx->index_entries, k, 1);
2249 }
2250 }
2251
2252 // Add CPR/MAI block to index
2253 TRACE("Adding DVD sector CPR/MAI block to index");
2254 IndexEntry cpr_mai_index_entry;
2255 cpr_mai_index_entry.blockType = DataBlock;
2256 cpr_mai_index_entry.dataType = DvdSectorCprMai;
2257 cpr_mai_index_entry.offset = cpr_mai_position;
2258 utarray_push_back(ctx->index_entries, &cpr_mai_index_entry);
2259 ctx->dirty_index_block = true;
2260 TRACE("Added DVD sector CPR/MAI block index entry at offset %" PRIu64, cpr_mai_position);
2261 }
2262 }
2263
2264 if(cpr_mai_block.compression == Lzma) free(buffer);
2265
2266 // Write DVD sector EDC block
2267 fseek(ctx->imageStream, 0, SEEK_END);
2268 long edc_position = ftell(ctx->imageStream);
2269 if(edc_position & alignment_mask)
2270 {
2271 const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
2272 fseek(ctx->imageStream, aligned_position, SEEK_SET);
2273 edc_position = aligned_position;
2274 }
2275 TRACE("Writing DVD sector EDC block at position %ld", edc_position);
2276 BlockHeader edc_block = {0};
2277 edc_block.identifier = DataBlock;
2278 edc_block.type = DvdSectorEdc;
2279 edc_block.compression = ctx->compression_enabled ? Lzma : None;
2280 edc_block.length = (uint32_t)total_sectors * 4;
2281 // Calculate CRC64
2282 edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
2283
2284 buffer = NULL;
2285
2286 if(edc_block.compression == None)
2287 {
2288 buffer = ctx->sector_edc;
2289 edc_block.cmpCrc64 = edc_block.crc64;
2290 }
2291 else
2292 {
2293 buffer = malloc((size_t)edc_block.length * 2); // Allocate double size for compression
2294 if(buffer == NULL)
2295 {
2296 TRACE("Failed to allocate memory for DVD sector EDC compression");
2297 return;
2298 }
2299
2300 size_t dst_size = (size_t)edc_block.length * 2 * 2;
2301 size_t props_size = LZMA_PROPERTIES_LENGTH;
2302 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_edc, edc_block.length, lzma_properties, &props_size, 9,
2303 ctx->lzma_dict_size, 4, 0, 2, 273, 8);
2304
2305 edc_block.cmpLength = (uint32_t)dst_size;
2306
2307 if(edc_block.cmpLength >= edc_block.length)
2308 {
2309 edc_block.compression = None;
2310 free(buffer);
2311 buffer = ctx->sector_edc;
2312 }
2313 }
2314
2315 if(edc_block.compression == None)
2316 {
2317 edc_block.cmpLength = edc_block.length;
2318 edc_block.cmpCrc64 = edc_block.crc64;
2319 }
2320 else
2321 edc_block.cmpCrc64 = aaruf_crc64_data(buffer, edc_block.cmpLength);
2322
2323 length_to_write = edc_block.cmpLength;
2324 if(edc_block.compression == Lzma) edc_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2325
2326 // Write header
2327 if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2328 {
2329 if(edc_block.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2330
2331 // Write data
2332 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2333 if(written_bytes == 1)
2334 {
2335 TRACE("Successfully wrote DVD sector EDC block (%" PRIu64 " bytes)", edc_block.cmpLength);
2336
2337 // Remove any previously existing index entries of the same type before adding
2338 TRACE("Removing any previously existing DVD sector EDC block index entries");
2339 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2340 {
2341 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2342 if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorEdc)
2343 {
2344 TRACE("Found existing DVD sector EDC block index entry at position %d, removing", k);
2345 utarray_erase(ctx->index_entries, k, 1);
2346 }
2347 }
2348
2349 // Add EDC block to index
2350 TRACE("Adding DVD sector EDC block to index");
2351 IndexEntry edc_index_entry;
2352 edc_index_entry.blockType = DataBlock;
2353 edc_index_entry.dataType = DvdSectorEdc;
2354 edc_index_entry.offset = edc_position;
2355 utarray_push_back(ctx->index_entries, &edc_index_entry);
2356 ctx->dirty_index_block = true;
2357 TRACE("Added DVD sector EDC block index entry at offset %" PRIu64, edc_position);
2358 }
2359 }
2360
2361 if(edc_block.compression == Lzma) free(buffer);
2362}
2363
2462{
2463 if(ctx->sector_decrypted_title_key == NULL) return;
2464
2465 fseek(ctx->imageStream, 0, SEEK_END);
2466 long block_position = ftell(ctx->imageStream);
2467 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2468 if(block_position & alignment_mask)
2469 {
2470 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
2471 fseek(ctx->imageStream, aligned_position, SEEK_SET);
2472 block_position = aligned_position;
2473 }
2474 TRACE("Writing DVD decrypted title key block at position %ld", block_position);
2475 BlockHeader decrypted_title_key_block = {0};
2476 decrypted_title_key_block.identifier = DataBlock;
2477 decrypted_title_key_block.type = DvdSectorTitleKeyDecrypted;
2478 decrypted_title_key_block.compression = ctx->compression_enabled ? Lzma : None;
2479 decrypted_title_key_block.length =
2481 5;
2482 // Calculate CRC64
2483 decrypted_title_key_block.crc64 =
2484 aaruf_crc64_data(ctx->sector_decrypted_title_key, decrypted_title_key_block.length);
2485
2486 uint8_t *buffer = NULL;
2487 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2488
2489 if(decrypted_title_key_block.compression == None)
2490 {
2491 buffer = ctx->sector_decrypted_title_key;
2492 decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
2493 }
2494 else
2495 {
2496 buffer = malloc((size_t)decrypted_title_key_block.length * 2); // Allocate double size for compression
2497 if(buffer == NULL)
2498 {
2499 TRACE("Failed to allocate memory for DVD decrypted title key compression");
2500 return;
2501 }
2502
2503 size_t dst_size = (size_t)decrypted_title_key_block.length * 2 * 2;
2504 size_t props_size = LZMA_PROPERTIES_LENGTH;
2505 aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_decrypted_title_key, decrypted_title_key_block.length,
2506 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
2507
2508 decrypted_title_key_block.cmpLength = (uint32_t)dst_size;
2509
2510 if(decrypted_title_key_block.cmpLength >= decrypted_title_key_block.length)
2511 {
2512 decrypted_title_key_block.compression = None;
2513 free(buffer);
2514 buffer = ctx->sector_decrypted_title_key;
2515 }
2516 }
2517
2518 if(decrypted_title_key_block.compression == None)
2519 {
2520 decrypted_title_key_block.cmpLength = decrypted_title_key_block.length;
2521 decrypted_title_key_block.cmpCrc64 = decrypted_title_key_block.crc64;
2522 }
2523 else
2524 decrypted_title_key_block.cmpCrc64 = aaruf_crc64_data(buffer, decrypted_title_key_block.cmpLength);
2525
2526 const size_t length_to_write = decrypted_title_key_block.cmpLength;
2527 if(decrypted_title_key_block.compression == Lzma) decrypted_title_key_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2528
2529 // Write header
2530 if(fwrite(&decrypted_title_key_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2531 {
2532 if(decrypted_title_key_block.compression == Lzma)
2533 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
2534
2535 // Write data
2536 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2537 if(written_bytes == 1)
2538 {
2539 TRACE("Successfully wrote DVD decrypted title key block (%" PRIu64 " bytes)",
2540 decrypted_title_key_block.cmpLength);
2541
2542 // Remove any previously existing index entries of the same type before adding
2543 TRACE("Removing any previously existing DVD decrypted title key block index entries");
2544 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2545 {
2546 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2547 if(entry && entry->blockType == DataBlock && entry->dataType == DvdSectorTitleKeyDecrypted)
2548 {
2549 TRACE("Found existing DVD decrypted title key block index entry at position %d, removing", k);
2550 utarray_erase(ctx->index_entries, k, 1);
2551 }
2552 }
2553
2554 // Add decrypted title key block to index
2555 TRACE("Adding DVD decrypted title key block to index");
2556 IndexEntry decrypted_title_key_index_entry;
2557 decrypted_title_key_index_entry.blockType = DataBlock;
2558 decrypted_title_key_index_entry.dataType = DvdSectorTitleKeyDecrypted;
2559 decrypted_title_key_index_entry.offset = block_position;
2560 utarray_push_back(ctx->index_entries, &decrypted_title_key_index_entry);
2561 ctx->dirty_index_block = true;
2562 TRACE("Added DVD decrypted title key block index entry at offset %" PRIu64, block_position);
2563 }
2564 }
2565
2566 if(decrypted_title_key_block.compression == Lzma) free(buffer);
2567}
2568
2641{
2642 if(ctx->mediaTags == NULL) return;
2643
2644 mediaTagEntry *media_tag = NULL;
2645 mediaTagEntry *tmp_media_tag = NULL;
2646
2647 HASH_ITER(hh, ctx->mediaTags, media_tag, tmp_media_tag)
2648 {
2649 fseek(ctx->imageStream, 0, SEEK_END);
2650 long tag_position = ftell(ctx->imageStream);
2651 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2652 if(tag_position & alignment_mask)
2653 {
2654 const uint64_t aligned_position = tag_position + alignment_mask & ~alignment_mask;
2655 fseek(ctx->imageStream, aligned_position, SEEK_SET);
2656 tag_position = aligned_position;
2657 }
2658
2659 TRACE("Writing media tag block type %d at position %ld", aaruf_get_datatype_for_media_tag_type(media_tag->type),
2660 tag_position);
2661 BlockHeader tag_block = {0};
2662 tag_block.identifier = DataBlock;
2663 tag_block.type = (uint16_t)aaruf_get_datatype_for_media_tag_type(media_tag->type);
2664 tag_block.compression = ctx->compression_enabled ? Lzma : None;
2665 tag_block.length = media_tag->length;
2666
2667 // Calculate CRC64
2668 tag_block.crc64 = aaruf_crc64_data(media_tag->data, tag_block.length);
2669
2670 uint8_t *buffer = NULL;
2671 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
2672
2673 if(tag_block.compression == None)
2674 {
2675 buffer = media_tag->data;
2676 tag_block.cmpCrc64 = tag_block.crc64;
2677 }
2678 else
2679 {
2680 buffer = malloc((size_t)tag_block.length * 2); // Allocate double size for compression
2681 if(buffer == NULL)
2682 {
2683 TRACE("Failed to allocate memory for media tag compression");
2684 return;
2685 }
2686
2687 size_t dst_size = (size_t)tag_block.length * 2 * 2;
2688 size_t props_size = LZMA_PROPERTIES_LENGTH;
2689 aaruf_lzma_encode_buffer(buffer, &dst_size, media_tag->data, tag_block.length, lzma_properties, &props_size,
2690 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
2691
2692 tag_block.cmpLength = (uint32_t)dst_size;
2693
2694 if(tag_block.cmpLength >= tag_block.length)
2695 {
2696 tag_block.compression = None;
2697 free(buffer);
2698 buffer = media_tag->data;
2699 }
2700 }
2701
2702 if(tag_block.compression == None)
2703 {
2704 tag_block.cmpLength = tag_block.length;
2705 tag_block.cmpCrc64 = tag_block.crc64;
2706 }
2707 else
2708 tag_block.cmpCrc64 = aaruf_crc64_data(buffer, tag_block.cmpLength);
2709
2710 const size_t length_to_write = tag_block.cmpLength;
2711 if(tag_block.compression == Lzma) tag_block.cmpLength += LZMA_PROPERTIES_LENGTH;
2712
2713 // Write header
2714 if(fwrite(&tag_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
2715 {
2716 if(tag_block.compression == Lzma)
2717 fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream); // Write data
2718
2719 const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
2720 if(written_bytes == 1)
2721 {
2722 TRACE("Successfully wrote media tag block type %d (%" PRIu64 " bytes)", tag_block.type,
2723 tag_block.cmpLength);
2724
2725 // Remove any previously existing index entries of the same type before adding
2726 TRACE("Removing any previously existing media tag type %d block index entries", tag_block.type);
2727 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2728 {
2729 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2730 if(entry && entry->blockType == DataBlock && entry->dataType == tag_block.type)
2731 {
2732 TRACE("Found existing media tag type %d block index entry at position %d, removing",
2733 tag_block.type, k);
2734 utarray_erase(ctx->index_entries, k, 1);
2735 }
2736 }
2737
2738 // Add media tag block to index
2739 TRACE("Adding media tag type %d block to index", tag_block.type);
2740 IndexEntry tag_index_entry;
2741 tag_index_entry.blockType = DataBlock;
2742 tag_index_entry.dataType = tag_block.type;
2743 tag_index_entry.offset = tag_position;
2744 utarray_push_back(ctx->index_entries, &tag_index_entry);
2745 ctx->dirty_index_block = true;
2746 TRACE("Added media tag block type %d index entry at offset %" PRIu64, tag_block.type, tag_position);
2747 }
2748 }
2749
2750 if(tag_block.compression == Lzma) free(buffer);
2751 }
2752}
2753
2917{
2918 if(ctx->tape_files == NULL) return;
2919
2920 // Iterate the uthash and count how many entries do we have
2921 const tapeFileHashEntry *tape_file = NULL;
2922 const tapeFileHashEntry *tmp_tape_file = NULL;
2923 size_t tape_file_count = 0;
2924 HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file) tape_file_count++;
2925
2926 // Create a memory buffer to copy all the file entries
2927 const size_t buffer_size = tape_file_count * sizeof(TapeFileEntry);
2928 TapeFileEntry *buffer = malloc(buffer_size);
2929 if(buffer == NULL)
2930 {
2931 TRACE("Failed to allocate memory for tape file entries");
2932 return;
2933 }
2934 memset(buffer, 0, buffer_size);
2935 size_t index = 0;
2936 HASH_ITER(hh, ctx->tape_files, tape_file, tmp_tape_file)
2937 {
2938 if(index >= tape_file_count) break;
2939 memcpy(&buffer[index], &tape_file->fileEntry, sizeof(TapeFileEntry));
2940 index++;
2941 }
2942
2943 // Create the tape file block in memory
2944 TapeFileHeader tape_file_block = {0};
2945 tape_file_block.identifier = TapeFileBlock;
2946 tape_file_block.length = (uint32_t)buffer_size;
2947 tape_file_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_file_block.length);
2948
2949 // Write tape file block to file, block aligned
2950 fseek(ctx->imageStream, 0, SEEK_END);
2951 long block_position = ftell(ctx->imageStream);
2952 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
2953 if(block_position & alignment_mask)
2954 {
2955 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
2956 fseek(ctx->imageStream, aligned_position, SEEK_SET);
2957 block_position = aligned_position;
2958 }
2959 TRACE("Writing tape file block at position %ld", block_position);
2960 if(fwrite(&tape_file_block, sizeof(TapeFileHeader), 1, ctx->imageStream) == 1)
2961 {
2962 const size_t written_bytes = fwrite(buffer, tape_file_block.length, 1, ctx->imageStream);
2963 if(written_bytes == 1)
2964 {
2965 TRACE("Successfully wrote tape file block (%" PRIu64 " bytes)", tape_file_block.length);
2966
2967 // Remove any previously existing index entries of the same type before adding
2968 TRACE("Removing any previously existing tape file block index entries");
2969 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
2970 {
2971 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
2972 if(entry && entry->blockType == TapeFileBlock && entry->dataType == 0)
2973 {
2974 TRACE("Found existing tape file block index entry at position %d, removing", k);
2975 utarray_erase(ctx->index_entries, k, 1);
2976 }
2977 }
2978
2979 // Add tape file block to index
2980 TRACE("Adding tape file block to index");
2981 IndexEntry index_entry;
2982 index_entry.blockType = TapeFileBlock;
2983 index_entry.dataType = 0;
2984 index_entry.offset = block_position;
2985 utarray_push_back(ctx->index_entries, &index_entry);
2986 ctx->dirty_index_block = true;
2987 TRACE("Added tape file block index entry at offset %" PRIu64, block_position);
2988 }
2989 }
2990
2991 free(buffer);
2992}
2993
3163{
3164 if(ctx->tape_partitions == NULL) return;
3165
3166 // Iterate the uthash and count how many entries do we have
3167 const TapePartitionHashEntry *tape_partition = NULL;
3168 const TapePartitionHashEntry *tmp_tape_partition = NULL;
3169 size_t tape_partition_count = 0;
3170 HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition) tape_partition_count++;
3171
3172 // Create a memory buffer to copy all the partition entries
3173 const size_t buffer_size = tape_partition_count * sizeof(TapePartitionEntry);
3174 TapePartitionEntry *buffer = malloc(buffer_size);
3175 if(buffer == NULL)
3176 {
3177 TRACE("Failed to allocate memory for tape partition entries");
3178 return;
3179 }
3180 memset(buffer, 0, buffer_size);
3181 size_t index = 0;
3182 HASH_ITER(hh, ctx->tape_partitions, tape_partition, tmp_tape_partition)
3183 {
3184 if(index >= tape_partition_count) break;
3185 memcpy(&buffer[index], &tape_partition->partitionEntry, sizeof(TapePartitionEntry));
3186 index++;
3187 }
3188
3189 // Create the tape partition block in memory
3190 TapePartitionHeader tape_partition_block = {0};
3191 tape_partition_block.identifier = TapePartitionBlock;
3192 tape_partition_block.length = (uint32_t)buffer_size;
3193 tape_partition_block.crc64 = aaruf_crc64_data((uint8_t *)buffer, (uint32_t)tape_partition_block.length);
3194
3195 // Write tape partition block to partition, block aligned
3196 fseek(ctx->imageStream, 0, SEEK_END);
3197 long block_position = ftell(ctx->imageStream);
3198 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3199 if(block_position & alignment_mask)
3200 {
3201 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3202 fseek(ctx->imageStream, aligned_position, SEEK_SET);
3203 block_position = aligned_position;
3204 }
3205 TRACE("Writing tape partition block at position %ld", block_position);
3206 if(fwrite(&tape_partition_block, sizeof(TapePartitionHeader), 1, ctx->imageStream) == 1)
3207 {
3208 const size_t written_bytes = fwrite(buffer, tape_partition_block.length, 1, ctx->imageStream);
3209 if(written_bytes == 1)
3210 {
3211 TRACE("Successfully wrote tape partition block (%" PRIu64 " bytes)", tape_partition_block.length);
3212
3213 // Remove any previously existing index entries of the same type before adding
3214 TRACE("Removing any previously existing tape partition block index entries");
3215 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3216 {
3217 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3218 if(entry && entry->blockType == TapePartitionBlock && entry->dataType == 0)
3219 {
3220 TRACE("Found existing tape partition block index entry at position %d, removing", k);
3221 utarray_erase(ctx->index_entries, k, 1);
3222 }
3223 }
3224
3225 // Add tape partition block to index
3226 TRACE("Adding tape partition block to index");
3227 IndexEntry index_entry;
3228 index_entry.blockType = TapePartitionBlock;
3229 index_entry.dataType = 0;
3230 index_entry.offset = block_position;
3231 utarray_push_back(ctx->index_entries, &index_entry);
3232 ctx->dirty_index_block = true;
3233 TRACE("Added tape partition block index entry at offset %" PRIu64, block_position);
3234 }
3235 }
3236
3237 free(buffer);
3238}
3239
3302{
3303 if(ctx->geometry_block.identifier != GeometryBlock) return;
3304
3305 fseek(ctx->imageStream, 0, SEEK_END);
3306 long block_position = ftell(ctx->imageStream);
3307 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3308 if(block_position & alignment_mask)
3309 {
3310 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3311 fseek(ctx->imageStream, aligned_position, SEEK_SET);
3312 block_position = aligned_position;
3313 }
3314
3315 TRACE("Writing geometry block at position %ld", block_position);
3316
3317 // Write header
3318 if(fwrite(&ctx->geometry_block, sizeof(GeometryBlockHeader), 1, ctx->imageStream) == 1)
3319 {
3320 TRACE("Successfully wrote geometry block");
3321
3322 // Remove any previously existing index entries of the same type before adding
3323 TRACE("Removing any previously existing geometry block index entries");
3324 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3325 {
3326 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3327 if(entry && entry->blockType == GeometryBlock && entry->dataType == 0)
3328 {
3329 TRACE("Found existing geometry block index entry at position %d, removing", k);
3330 utarray_erase(ctx->index_entries, k, 1);
3331 }
3332 }
3333
3334 // Add geometry block to index
3335 TRACE("Adding geometry block to index");
3336 IndexEntry index_entry;
3337 index_entry.blockType = GeometryBlock;
3338 index_entry.dataType = 0;
3339 index_entry.offset = block_position;
3340 utarray_push_back(ctx->index_entries, &index_entry);
3341 ctx->dirty_index_block = true;
3342 TRACE("Added geometry block index entry at offset %" PRIu64, block_position);
3343 }
3344}
3345
3451{
3453 ctx->metadata_block_header.lastMediaSequence == 0 && ctx->creator == NULL && ctx->comments == NULL &&
3454 ctx->media_title == NULL && ctx->media_manufacturer == NULL && ctx->media_model == NULL &&
3455 ctx->media_serial_number == NULL && ctx->media_barcode == NULL && ctx->media_part_number == NULL &&
3456 ctx->drive_manufacturer == NULL && ctx->drive_model == NULL && ctx->drive_serial_number == NULL &&
3457 ctx->drive_firmware_revision == NULL)
3458 return;
3459
3468
3470
3471 int pos = sizeof(MetadataBlockHeader);
3472
3473 uint8_t *buffer = calloc(1, ctx->metadata_block_header.blockSize);
3474 if(buffer == NULL) return;
3475
3476 if(ctx->creator != NULL && ctx->metadata_block_header.creatorLength > 0)
3477 {
3478 memcpy(buffer + pos, ctx->creator, ctx->metadata_block_header.creatorLength);
3481 }
3482
3483 if(ctx->comments != NULL && ctx->metadata_block_header.commentsLength > 0)
3484 {
3485 memcpy(buffer + pos, ctx->comments, ctx->metadata_block_header.commentsLength);
3488 }
3489
3490 if(ctx->media_title != NULL && ctx->metadata_block_header.mediaTitleLength > 0)
3491 {
3492 memcpy(buffer + pos, ctx->media_title, ctx->metadata_block_header.mediaTitleLength);
3495 }
3496
3498 {
3499 memcpy(buffer + pos, ctx->media_manufacturer, ctx->metadata_block_header.mediaManufacturerLength);
3502 }
3503
3504 if(ctx->media_model != NULL && ctx->metadata_block_header.mediaModelLength > 0)
3505 {
3506 memcpy(buffer + pos, ctx->media_model, ctx->metadata_block_header.mediaModelLength);
3509 }
3510
3512 {
3513 memcpy(buffer + pos, ctx->media_serial_number, ctx->metadata_block_header.mediaSerialNumberLength);
3516 }
3517
3518 if(ctx->media_barcode != NULL && ctx->metadata_block_header.mediaBarcodeLength > 0)
3519 {
3520 memcpy(buffer + pos, ctx->media_barcode, ctx->metadata_block_header.mediaBarcodeLength);
3523 }
3524
3526 {
3527 memcpy(buffer + pos, ctx->media_part_number, ctx->metadata_block_header.mediaPartNumberLength);
3530 }
3531
3533 {
3534 memcpy(buffer + pos, ctx->drive_manufacturer, ctx->metadata_block_header.driveManufacturerLength);
3537 }
3538
3539 if(ctx->drive_model != NULL && ctx->metadata_block_header.driveModelLength > 0)
3540 {
3541 memcpy(buffer + pos, ctx->drive_model, ctx->metadata_block_header.driveModelLength);
3544 }
3545
3547 {
3548 memcpy(buffer + pos, ctx->drive_serial_number, ctx->metadata_block_header.driveSerialNumberLength);
3551 }
3552
3554 {
3557 }
3558
3559 fseek(ctx->imageStream, 0, SEEK_END);
3560 long block_position = ftell(ctx->imageStream);
3561 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3562 if(block_position & alignment_mask)
3563 {
3564 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3565 fseek(ctx->imageStream, aligned_position, SEEK_SET);
3566 block_position = aligned_position;
3567 }
3568
3569 memcpy(buffer, &ctx->metadata_block_header, sizeof(MetadataBlockHeader));
3570
3571 TRACE("Writing metadata block at position %ld", block_position);
3572
3573 if(fwrite(buffer, ctx->metadata_block_header.blockSize, 1, ctx->imageStream) == 1)
3574 {
3575 TRACE("Successfully wrote metadata block");
3576
3577 // Remove any previously existing index entries of the same type before adding
3578 TRACE("Removing any previously existing metadata block index entries");
3579 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3580 {
3581 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3582 if(entry && entry->blockType == MetadataBlock && entry->dataType == 0)
3583 {
3584 TRACE("Found existing metadata block index entry at position %d, removing", k);
3585 utarray_erase(ctx->index_entries, k, 1);
3586 }
3587 }
3588
3589 // Add metadata block to index
3590 TRACE("Adding metadata block to index");
3591 IndexEntry index_entry;
3592 index_entry.blockType = MetadataBlock;
3593 index_entry.dataType = 0;
3594 index_entry.offset = block_position;
3595 utarray_push_back(ctx->index_entries, &index_entry);
3596 ctx->dirty_index_block = true;
3597 TRACE("Added metadata block index entry at offset %" PRIu64, block_position);
3598 }
3599
3600 free(buffer);
3601}
3602
3749{
3750
3751 if(ctx->dump_hardware_entries_with_data == NULL || ctx->dump_hardware_header.entries == 0 ||
3753 return;
3754
3755 const size_t required_length = sizeof(DumpHardwareHeader) + ctx->dump_hardware_header.length;
3756
3757 uint8_t *buffer = calloc(1, required_length);
3758
3759 if(buffer == NULL) return;
3760
3761 // Start to iterate and copy the data
3762 size_t offset = sizeof(DumpHardwareHeader);
3763 for(int i = 0; i < ctx->dump_hardware_header.entries; i++)
3764 {
3765 size_t entry_size = sizeof(DumpHardwareEntry) +
3775
3776 if(offset + entry_size > required_length)
3777 {
3778 FATAL("Calculated size exceeds provided buffer length");
3779 free(buffer);
3780 return;
3781 }
3782
3783 memcpy(buffer + offset, &ctx->dump_hardware_entries_with_data[i].entry, sizeof(DumpHardwareEntry));
3784 offset += sizeof(DumpHardwareEntry);
3787 {
3788 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].manufacturer,
3791 }
3793 ctx->dump_hardware_entries_with_data[i].model != NULL)
3794 {
3795 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].model,
3798 }
3801 {
3802 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].revision,
3805 }
3808 {
3809 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].firmware,
3812 }
3815 {
3816 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].serial,
3819 }
3822 {
3823 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareName,
3826 }
3829 {
3830 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareVersion,
3833 }
3836 {
3837 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].softwareOperatingSystem,
3840 }
3843 {
3844 memcpy(buffer + offset, ctx->dump_hardware_entries_with_data[i].extents,
3846 offset += ctx->dump_hardware_entries_with_data[i].entry.extents * sizeof(DumpExtent);
3847 }
3848 }
3849
3850 // Calculate CRC64
3853
3854 // Copy header
3855 memcpy(buffer, &ctx->dump_hardware_header, sizeof(DumpHardwareHeader));
3856
3857 fseek(ctx->imageStream, 0, SEEK_END);
3858 long block_position = ftell(ctx->imageStream);
3859 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3860 if(block_position & alignment_mask)
3861 {
3862 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
3863 fseek(ctx->imageStream, aligned_position, SEEK_SET);
3864 block_position = aligned_position;
3865 }
3866 TRACE("Writing dump hardware block at position %ld", block_position);
3867 if(fwrite(buffer, required_length, 1, ctx->imageStream) == 1)
3868 {
3869 TRACE("Successfully wrote dump hardware block");
3870
3871 // Remove any previously existing index entries of the same type before adding
3872 TRACE("Removing any previously existing dump hardware block index entries");
3873 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
3874 {
3875 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
3876 if(entry && entry->blockType == DumpHardwareBlock && entry->dataType == 0)
3877 {
3878 TRACE("Found existing dump hardware block index entry at position %d, removing", k);
3879 utarray_erase(ctx->index_entries, k, 1);
3880 }
3881 }
3882
3883 // Add dump hardware block to index
3884 TRACE("Adding dump hardware block to index");
3885 IndexEntry index_entry;
3886 index_entry.blockType = DumpHardwareBlock;
3887 index_entry.dataType = 0;
3888 index_entry.offset = block_position;
3889 utarray_push_back(ctx->index_entries, &index_entry);
3890 ctx->dirty_index_block = true;
3891 TRACE("Added dump hardware block index entry at offset %" PRIu64, block_position);
3892 }
3893
3894 free(buffer);
3895}
3896
3992{
3993 if(ctx->cicm_block == NULL || ctx->cicm_block_header.length == 0 || ctx->cicm_block_header.identifier != CicmBlock)
3994 return;
3995
3996 fseek(ctx->imageStream, 0, SEEK_END);
3997 long block_position = ftell(ctx->imageStream);
3998 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
3999
4000 if(block_position & alignment_mask)
4001 {
4002 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4003 fseek(ctx->imageStream, aligned_position, SEEK_SET);
4004 block_position = aligned_position;
4005 }
4006
4007 TRACE("Writing CICM XML block at position %ld", block_position);
4008 if(fwrite(&ctx->cicm_block_header, sizeof(CicmMetadataBlock), 1, ctx->imageStream) == 1 &&
4009 fwrite(ctx->cicm_block, ctx->cicm_block_header.length, 1, ctx->imageStream) == 1)
4010 {
4011 TRACE("Successfully wrote CICM XML block");
4012
4013 // Remove any previously existing index entries of the same type before adding
4014 TRACE("Removing any previously existing CICM XML block index entries");
4015 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4016 {
4017 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4018 if(entry && entry->blockType == CicmBlock && entry->dataType == 0)
4019 {
4020 TRACE("Found existing CICM XML block index entry at position %d, removing", k);
4021 utarray_erase(ctx->index_entries, k, 1);
4022 }
4023 }
4024
4025 // Add CICM block to index
4026 TRACE("Adding CICM XML block to index");
4027 IndexEntry index_entry;
4028 index_entry.blockType = CicmBlock;
4029 index_entry.dataType = 0;
4030 index_entry.offset = block_position;
4031 utarray_push_back(ctx->index_entries, &index_entry);
4032 ctx->dirty_index_block = true;
4033 TRACE("Added CICM XML block index entry at offset %" PRIu64, block_position);
4034 }
4035}
4036
4142{
4143 if(ctx->json_block == NULL || ctx->json_block_header.length == 0 ||
4145 return;
4146
4147 fseek(ctx->imageStream, 0, SEEK_END);
4148 long block_position = ftell(ctx->imageStream);
4149 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4150
4151 if(block_position & alignment_mask)
4152 {
4153 const uint64_t aligned_position = block_position + alignment_mask & ~alignment_mask;
4154 fseek(ctx->imageStream, aligned_position, SEEK_SET);
4155 block_position = aligned_position;
4156 }
4157
4158 TRACE("Writing Aaru metadata JSON block at position %ld", block_position);
4159 if(fwrite(&ctx->json_block_header, sizeof(AaruMetadataJsonBlockHeader), 1, ctx->imageStream) == 1 &&
4160 fwrite(ctx->json_block, ctx->json_block_header.length, 1, ctx->imageStream) == 1)
4161 {
4162 TRACE("Successfully wrote Aaru metadata JSON block");
4163
4164 // Remove any previously existing index entries of the same type before adding
4165 TRACE("Removing any previously existing Aaru metadata JSON block index entries");
4166 for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
4167 {
4168 const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
4169 if(entry && entry->blockType == AaruMetadataJsonBlock && entry->dataType == 0)
4170 {
4171 TRACE("Found existing Aaru metadata JSON block index entry at position %d, removing", k);
4172 utarray_erase(ctx->index_entries, k, 1);
4173 }
4174 }
4175
4176 // Add Aaru metadata JSON block to index
4177 TRACE("Adding Aaru metadata JSON block to index");
4178 IndexEntry index_entry;
4179 index_entry.blockType = AaruMetadataJsonBlock;
4180 index_entry.dataType = 0;
4181 index_entry.offset = block_position;
4182 utarray_push_back(ctx->index_entries, &index_entry);
4183 ctx->dirty_index_block = true;
4184 TRACE("Added Aaru metadata JSON block index entry at offset %" PRIu64, block_position);
4185 }
4186}
4187
4372{
4373 uint64_t data_length = record->data_length;
4374 uint64_t index_length = record->index_length;
4375 uint64_t raw_length = data_length + index_length;
4376
4377 if(raw_length > UINT32_MAX)
4378 {
4379 FATAL("Flux capture raw length exceeds 32-bit limit (%" PRIu64 ")", raw_length);
4381 }
4382
4383 uint8_t *raw_buffer = NULL;
4384 if(raw_length != 0)
4385 {
4386 raw_buffer = malloc(raw_length);
4387 if(raw_buffer == NULL)
4388 {
4389 FATAL("Could not allocate %" PRIu64 " bytes for flux serialization", raw_length);
4391 }
4392
4393 if(data_length != 0 && record->data_buffer != NULL) memcpy(raw_buffer, record->data_buffer, data_length);
4394 if(index_length != 0 && record->index_buffer != NULL)
4395 memcpy(raw_buffer + data_length, record->index_buffer, index_length);
4396 }
4397
4398 uint64_t raw_crc = raw_length != 0 && raw_buffer != NULL ? aaruf_crc64_data(raw_buffer, raw_length) : 0;
4399
4400 CompressionType compression = ctx->compression_enabled ? Lzma : None;
4401
4402 uint8_t *compressed_buffer = NULL;
4403 uint32_t cmp_length = 0;
4404 uint64_t cmp_crc = 0;
4405
4406 if(compression == Lzma)
4407 {
4408 size_t cmp_capacity = raw_length ? raw_length * 2 + 65536 : LZMA_PROPERTIES_LENGTH + 16;
4409 if(cmp_capacity < raw_length + LZMA_PROPERTIES_LENGTH) cmp_capacity = raw_length + LZMA_PROPERTIES_LENGTH;
4410
4411 uint8_t *cmp_stream = malloc(cmp_capacity);
4412 if(cmp_stream == NULL)
4413 {
4414 free(raw_buffer);
4415 FATAL("Could not allocate %zu bytes for LZMA flux compression", cmp_capacity);
4417 }
4418
4419 size_t dst_size = cmp_capacity;
4420 uint8_t lzma_props[LZMA_PROPERTIES_LENGTH] = {0};
4421 size_t props_size = LZMA_PROPERTIES_LENGTH;
4422 int32_t error_no = aaruf_lzma_encode_buffer(cmp_stream, &dst_size, raw_buffer, raw_length, lzma_props,
4423 &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
4424
4425 if(error_no != 0 || props_size != LZMA_PROPERTIES_LENGTH || dst_size >= raw_length)
4426 {
4427 TRACE("Flux capture compression fell back to uncompressed (err=%d, dst=%zu, raw=%" PRIu64 ")", error_no,
4428 dst_size, raw_length);
4429 compression = None;
4430 free(cmp_stream);
4431 }
4432 else
4433 {
4434 cmp_length = (uint32_t)(dst_size + LZMA_PROPERTIES_LENGTH);
4435 compressed_buffer = malloc(cmp_length);
4436 if(compressed_buffer == NULL)
4437 {
4438 free(cmp_stream);
4439 free(raw_buffer);
4440 FATAL("Could not allocate %u bytes for flux compressed payload", cmp_length);
4442 }
4443
4444 memcpy(compressed_buffer, lzma_props, LZMA_PROPERTIES_LENGTH);
4445 memcpy(compressed_buffer + LZMA_PROPERTIES_LENGTH, cmp_stream, dst_size);
4446 cmp_crc = aaruf_crc64_data(compressed_buffer, cmp_length);
4447 free(cmp_stream);
4448 free(raw_buffer);
4449 raw_buffer = NULL;
4450 }
4451 }
4452
4453 if(compression == None)
4454 {
4455 cmp_length = (uint32_t)raw_length;
4456 cmp_crc = raw_crc;
4457 compressed_buffer = raw_buffer;
4458 raw_buffer = NULL;
4459 }
4460
4461 // Align stream position to block boundary
4462 fseek(ctx->imageStream, 0, SEEK_END);
4463 long payload_position = ftell(ctx->imageStream);
4464 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4465 if(payload_position & alignment_mask)
4466 {
4467 const uint64_t aligned_position = payload_position + alignment_mask & ~alignment_mask;
4468 fseek(ctx->imageStream, aligned_position, SEEK_SET);
4469 payload_position = aligned_position;
4470 }
4471
4472 DataStreamPayloadHeader payload_header = {0};
4473 payload_header.identifier = DataStreamPayloadBlock;
4474 payload_header.dataType = FluxData;
4475 payload_header.compression = (uint16_t)compression;
4476 payload_header.cmpLength = cmp_length;
4477 payload_header.length = (uint32_t)raw_length;
4478 payload_header.cmpCrc64 = cmp_crc;
4479 payload_header.crc64 = raw_crc;
4480
4481 if(fwrite(&payload_header, sizeof(DataStreamPayloadHeader), 1, ctx->imageStream) != 1)
4482 {
4483 free(compressed_buffer);
4484 free(raw_buffer);
4485 FATAL("Could not write flux payload header");
4487 }
4488
4489 if(cmp_length != 0 && compressed_buffer != NULL &&
4490 fwrite(compressed_buffer, cmp_length, 1, ctx->imageStream) != 1)
4491 {
4492 free(compressed_buffer);
4493 free(raw_buffer);
4494 FATAL("Could not write flux payload data");
4496 }
4497
4498 IndexEntry payload_entry;
4499 payload_entry.blockType = DataStreamPayloadBlock;
4500 payload_entry.dataType = FluxData;
4501 payload_entry.offset = payload_position;
4502 utarray_push_back(ctx->index_entries, &payload_entry);
4503
4504 entry->head = record->entry.head;
4505 entry->track = record->entry.track;
4506 entry->subtrack = record->entry.subtrack;
4507 entry->captureIndex = record->entry.captureIndex;
4508 entry->dataResolution = record->entry.dataResolution;
4509 entry->indexResolution = record->entry.indexResolution;
4510 entry->indexOffset = record->data_length;
4511 entry->payloadOffset = payload_position >> ctx->user_data_ddt_header.blockAlignmentShift;
4512
4513 record->entry = *entry;
4514
4515 free(compressed_buffer);
4516 free(raw_buffer);
4517
4518 return AARUF_STATUS_OK;
4519}
4520
4755{
4756 TRACE("Entering write_flux_blocks(%p)", ctx);
4757
4758 if(ctx == NULL || ctx->imageStream == NULL)
4759 {
4760 FATAL("Invalid context when writing flux blocks");
4762 }
4763
4764 if(ctx->flux_captures == NULL || utarray_len(ctx->flux_captures) == 0)
4765 {
4766 TRACE("No flux captures enqueued, skipping flux block serialization");
4767 return AARUF_STATUS_OK;
4768 }
4769
4770 size_t capture_count = utarray_len(ctx->flux_captures);
4771 if(capture_count > UINT16_MAX)
4772 {
4773 FATAL("Flux capture count exceeds header capacity (%zu > %u)", capture_count, UINT16_MAX);
4775 }
4776
4777 FluxEntry *previous_entries = ctx->flux_entries;
4778 ctx->flux_entries = NULL;
4779
4780 FluxEntry *entries = malloc(capture_count * sizeof(FluxEntry));
4781 if(entries == NULL)
4782 {
4783 ctx->flux_entries = previous_entries;
4784 FATAL("Could not allocate %zu bytes for flux entries", capture_count * sizeof(FluxEntry));
4786 }
4787
4788 size_t idx = 0;
4789 for(FluxCaptureRecord *record = (FluxCaptureRecord *)utarray_front(ctx->flux_captures); record != NULL;
4790 record = (FluxCaptureRecord *)utarray_next(ctx->flux_captures, record), ++idx)
4791 {
4792 int32_t res = write_flux_capture_payload(ctx, record, &entries[idx]);
4793 if(res != AARUF_STATUS_OK)
4794 {
4795 free(entries);
4796 ctx->flux_entries = previous_entries;
4797 return res;
4798 }
4799 }
4800
4801 FluxHeader header = {0};
4802 header.identifier = FluxDataBlock;
4803 header.entries = (uint16_t)capture_count;
4805 header.crc64 =
4806 capture_count == 0 ? 0 : aaruf_crc64_data((const uint8_t *)entries, capture_count * sizeof(FluxEntry));
4807
4808 // Align stream position to block boundary
4809 fseek(ctx->imageStream, 0, SEEK_END);
4810 long metadata_position = ftell(ctx->imageStream);
4811 const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4812 if(metadata_position & alignment_mask)
4813 {
4814 const uint64_t aligned_position = metadata_position + alignment_mask & ~alignment_mask;
4815 fseek(ctx->imageStream, aligned_position, SEEK_SET);
4816 metadata_position = aligned_position;
4817 }
4818
4819 if(fwrite(&header, sizeof(FluxHeader), 1, ctx->imageStream) != 1)
4820 {
4821 free(entries);
4822 ctx->flux_entries = previous_entries;
4823 FATAL("Could not write flux metadata header");
4825 }
4826
4827 if(capture_count != 0)
4828 {
4829 size_t written_entries = fwrite(entries, sizeof(FluxEntry), capture_count, ctx->imageStream);
4830 if(written_entries != capture_count)
4831 {
4832 free(entries);
4833 ctx->flux_entries = previous_entries;
4834 FATAL("Could not write %zu flux entries (wrote %zu)", capture_count, written_entries);
4836 }
4837 }
4838
4839 IndexEntry metadata_entry;
4840 metadata_entry.blockType = FluxDataBlock;
4841 metadata_entry.dataType = 0;
4842 metadata_entry.offset = metadata_position;
4843 utarray_push_back(ctx->index_entries, &metadata_entry);
4844
4845 if(previous_entries != NULL) free(previous_entries);
4846 ctx->flux_entries = entries;
4847 ctx->flux_data_header = header;
4848
4849 utarray_free(ctx->flux_captures);
4850 ctx->flux_captures = NULL;
4851
4852 int32_t map_result = flux_map_rebuild_from_entries(ctx);
4853 if(map_result != AARUF_STATUS_OK) return map_result;
4854
4855 TRACE("Wrote %zu flux captures", capture_count);
4856 return AARUF_STATUS_OK;
4857}
4858
4876{
4877 // Write the complete index at the end of the file
4878 TRACE("Writing index at the end of the file");
4879 fseek(ctx->imageStream, 0, SEEK_END);
4880 long index_position = ftell(ctx->imageStream);
4881
4882 // Align index position to block boundary if needed
4883 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
4884 if(index_position & alignment_mask)
4885 {
4886 uint64_t aligned_position = index_position + alignment_mask & ~alignment_mask;
4887 fseek(ctx->imageStream, aligned_position, SEEK_SET);
4888 index_position = aligned_position;
4889 TRACE("Aligned index position to %" PRIu64, aligned_position);
4890 }
4891
4892 // Prepare index header
4893 IndexHeader3 index_header;
4894 index_header.identifier = IndexBlock3;
4895 index_header.entries = utarray_len(ctx->index_entries);
4896 index_header.previous = 0; // No previous index for now
4897
4898 TRACE("Writing index with %" PRIu64 " entries at position %ld", index_header.entries, index_position);
4899
4900 // Calculate CRC64 of index entries
4901 crc64_ctx *index_crc64_context = aaruf_crc64_init();
4902 if(index_crc64_context != NULL && index_header.entries > 0)
4903 {
4904 size_t index_data_size = index_header.entries * sizeof(IndexEntry);
4905 aaruf_crc64_update(index_crc64_context, utarray_front(ctx->index_entries), index_data_size);
4906 aaruf_crc64_final(index_crc64_context, &index_header.crc64);
4907 TRACE("Calculated index CRC64: 0x%16lX", index_header.crc64);
4908 }
4909 else
4910 index_header.crc64 = 0;
4911
4912 // Free CRC64 context
4913 aaruf_crc64_free(index_crc64_context);
4914
4915 // Write index header
4916 if(fwrite(&index_header, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
4917 {
4918 TRACE("Successfully wrote index header");
4919
4920 // Write index entries
4921 if(index_header.entries > 0)
4922 {
4923 size_t entries_written = 0;
4924 IndexEntry *entry = NULL;
4925
4926 for(entry = (IndexEntry *)utarray_front(ctx->index_entries); entry != NULL;
4927 entry = (IndexEntry *)utarray_next(ctx->index_entries, entry))
4928 if(fwrite(entry, sizeof(IndexEntry), 1, ctx->imageStream) == 1)
4929 {
4930 entries_written++;
4931 TRACE("Wrote index entry: blockType=0x%08X dataType=%u offset=%" PRIu64, entry->blockType,
4932 entry->dataType, entry->offset);
4933 }
4934 else
4935 {
4936 TRACE("Failed to write index entry %zu", entries_written);
4937 break;
4938 }
4939
4940 if(entries_written == index_header.entries)
4941 {
4942 TRACE("Successfully wrote all %zu index entries", entries_written);
4943
4944 // Update header with index offset and rewrite it
4945 ctx->header.indexOffset = index_position;
4946 TRACE("Updating header with index offset: %" PRIu64, ctx->header.indexOffset);
4947
4948 // Seek back to beginning and rewrite header
4949 fseek(ctx->imageStream, 0, SEEK_SET);
4950 if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) == 1)
4951 TRACE("Successfully updated header with index offset");
4952 else
4953 {
4954 TRACE("Failed to update header with index offset");
4956 }
4957 }
4958 else
4959 {
4960 TRACE("Failed to write all index entries (wrote %zu of %" PRIu64 ")", entries_written,
4961 index_header.entries);
4963 }
4964 }
4965 }
4966 else
4967 {
4968 TRACE("Failed to write index header");
4970 }
4971
4972 return AARUF_STATUS_OK;
4973}
4974
5012{
5013 TRACE("Entering aaruf_close(%p)", context);
5014
5015 mediaTagEntry *media_tag = NULL;
5016 mediaTagEntry *tmp_media_tag = NULL;
5017
5018 if(context == NULL)
5019 {
5020 FATAL("Invalid context");
5021 errno = EINVAL;
5022 return -1;
5023 }
5024
5025 aaruformat_context *ctx = context;
5026
5027 // Not a libaaruformat context
5028 if(ctx->magic != AARU_MAGIC)
5029 {
5030 FATAL("Invalid context");
5031 errno = EINVAL;
5032 return -1;
5033 }
5034
5035 if(ctx->is_writing)
5036 {
5037 TRACE("File is writing");
5038
5039 TRACE("Seeking to start of image");
5040 // Write the header at the beginning of the file
5041 fseek(ctx->imageStream, 0, SEEK_SET);
5042
5043 TRACE("Writing header at position 0");
5044 if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1)
5045 {
5046 fclose(ctx->imageStream);
5047 ctx->imageStream = NULL;
5049 return -1;
5050 }
5051
5052 // Close current block first
5053 TRACE("Closing current block if any");
5054 if(ctx->writing_buffer != NULL)
5055 {
5056 int error = aaruf_close_current_block(ctx);
5057
5058 if(error != AARUF_STATUS_OK) return error;
5059 }
5060
5061 int32_t res;
5062 if(ctx->is_tape)
5063 {
5064 // Write tape DDT
5065 if(ctx->dirty_tape_ddt)
5066 {
5067 res = write_tape_ddt(ctx);
5068 if(res != AARUF_STATUS_OK) return res;
5069 }
5070 }
5071 else
5072 {
5073 // Write cached secondary DDT table if any
5074 if(ctx->dirty_secondary_ddt)
5075 {
5076 res = write_cached_secondary_ddt(ctx);
5077 if(res != AARUF_STATUS_OK) return res;
5078 }
5079
5080 // Write primary DDT table (multi-level) if applicable
5081 if(ctx->dirty_primary_ddt)
5082 {
5083 res = write_primary_ddt(ctx);
5084 if(res != AARUF_STATUS_OK) return res;
5085 }
5086
5087 // Write single-level DDT table if applicable
5088 if(ctx->dirty_single_level_ddt)
5089 {
5090 res = write_single_level_ddt(ctx);
5091 if(res != AARUF_STATUS_OK) return res;
5092 }
5093 }
5094
5095 // Finalize checksums and write checksum block
5097
5098 // Write tracks block
5100
5101 // Write flux capture blocks
5102 if(ctx->dirty_flux_block) {
5103 res = write_flux_blocks(ctx);
5104 if(res != AARUF_STATUS_OK) return res;
5105 }
5106
5107 // Write MODE 2 subheader data block
5109
5110 // Write CD sector prefix data block
5112
5113 // Write sector prefix DDT (statuses + optional indexes)
5115
5116 // Write CD sector suffix data block (EDC/ECC captures)
5118
5119 // Write sector prefix DDT (EDC/ECC captures)
5121
5122 // Write sector subchannel data block
5124
5125 // Write DVD long sector data blocks
5127
5128 // Write DVD decrypted title keys
5130
5131 // Write media tags data blocks
5132 if(ctx->dirty_media_tags) write_media_tags(ctx);
5133
5134 // Write tape files
5136
5137 // Write tape partitions
5139
5140 // Write geometry block if any
5142
5143 // Write metadata block
5145
5146 // Write dump hardware block if any
5148
5149 // Write CICM XML block if any
5150 if(ctx->dirty_cicm_block) write_cicm_block(ctx);
5151
5152 // Write Aaru metadata JSON block if any
5154
5155 // Write the complete index at the end of the file
5156 if(ctx->dirty_index_block)
5157 {
5158 res = write_index_block(ctx);
5159 if(res != AARUF_STATUS_OK) return res;
5160 }
5161
5162 if(ctx->deduplicate && ctx->sector_hash_map != NULL)
5163 {
5164 TRACE("Clearing sector hash map");
5165 // Clear sector hash map
5167 ctx->sector_hash_map = NULL;
5168 }
5169 }
5170
5171 TRACE("Freeing memory pointers");
5172 // This may do nothing if imageStream is NULL, but as the behaviour is undefined, better sure than sorry
5173 if(ctx->imageStream != NULL)
5174 {
5175 fclose(ctx->imageStream);
5176 ctx->imageStream = NULL;
5177 }
5178
5179 // Free index entries array
5180 if(ctx->index_entries != NULL)
5181 {
5182 utarray_free(ctx->index_entries);
5183 ctx->index_entries = NULL;
5184 }
5185
5186 free(ctx->sector_prefix);
5187 ctx->sector_prefix = NULL;
5188 free(ctx->sector_prefix_corrected);
5189 ctx->sector_prefix_corrected = NULL;
5190 free(ctx->sector_suffix);
5191 ctx->sector_suffix = NULL;
5192 free(ctx->sector_suffix_corrected);
5193 ctx->sector_suffix_corrected = NULL;
5194 free(ctx->sector_subchannel);
5195 ctx->sector_subchannel = NULL;
5196 free(ctx->mode2_subheaders);
5197 ctx->mode2_subheaders = NULL;
5198
5199 TRACE("Freeing media tags");
5200 if(ctx->mediaTags != NULL) HASH_ITER(hh, ctx->mediaTags, media_tag, tmp_media_tag)
5201 {
5202 HASH_DEL(ctx->mediaTags, media_tag);
5203 free(media_tag->data);
5204 free(media_tag);
5205 }
5206
5207#ifdef __linux__ // TODO: Implement
5208 TRACE("Unmapping user data DDT if it is not in memory");
5209 if(!ctx->in_memory_ddt)
5210 {
5211 munmap(ctx->user_data_ddt, ctx->mapped_memory_ddt_size);
5212 ctx->user_data_ddt = NULL;
5213 }
5214#endif
5215
5216 free(ctx->sector_prefix_ddt2);
5217 ctx->sector_prefix_ddt2 = NULL;
5218 free(ctx->sector_prefix_ddt);
5219 ctx->sector_prefix_ddt = NULL;
5220 free(ctx->sector_suffix_ddt2);
5221 ctx->sector_suffix_ddt2 = NULL;
5222 free(ctx->sector_suffix_ddt);
5223 ctx->sector_suffix_ddt = NULL;
5224
5225 free(ctx->metadata_block);
5226 ctx->metadata_block = NULL;
5227 free(ctx->track_entries);
5228 ctx->track_entries = NULL;
5229 free(ctx->data_tracks);
5230 ctx->data_tracks = NULL;
5231 free(ctx->cicm_block);
5232 ctx->cicm_block = NULL;
5233
5234 if(ctx->dump_hardware_entries_with_data != NULL)
5235 {
5236 for(int i = 0; i < ctx->dump_hardware_header.entries; i++)
5237 {
5256 }
5257 free(ctx->dump_hardware_entries_with_data); // Free the array itself
5259 }
5260
5261 free(ctx->readableSectorTags);
5262 ctx->readableSectorTags = NULL;
5263
5265 ctx->ecc_cd_context = NULL;
5266
5267 free(ctx->checksums.spamsum);
5268 ctx->checksums.spamsum = NULL;
5269
5270 free(ctx->sector_id);
5271 free(ctx->sector_ied);
5272 free(ctx->sector_cpr_mai);
5273 free(ctx->sector_edc);
5274
5275 // Free DDT allocations (v1 and v2)
5276 free(ctx->user_data_ddt); // Legacy v1 DDT
5277 free(ctx->user_data_ddt2); // v2 DDT primary/secondary
5278 free(ctx->cached_secondary_ddt2); // Cached secondary DDT (read operations)
5279
5280 // Free LRU caches (uses cache->free_func to free cached values)
5282 free_cache(&ctx->block_cache);
5283
5284 free(context);
5285
5286 TRACE("Exiting aaruf_close() = 0");
5287 return 0;
5288}
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.
Definition close.c:4371
void write_dvd_long_sector_blocks(aaruformat_context *ctx)
Serialize DVD long sector auxiliary data blocks to the image file.
Definition close.c:1964
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.
Definition close.c:288
static void write_cicm_block(aaruformat_context *ctx)
Serialize the CICM XML metadata block to the image file.
Definition close.c:3991
static void write_sector_subchannel(aaruformat_context *ctx)
Serialize the per-sector subchannel or tag data block.
Definition close.c:1647
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.
Definition close.c:4875
static void write_media_tags(aaruformat_context *ctx)
Serialize all accumulated media tags to the image file.
Definition close.c:2640
static void write_dvd_title_key_decrypted_block(aaruformat_context *ctx)
Serialize the DVD decrypted title key data block to the image file.
Definition close.c:2461
int aaruf_close(void *context)
Close an Aaru image context, flushing pending data structures and releasing resources.
Definition close.c:5011
static int32_t write_single_level_ddt(aaruformat_context *ctx)
Serialize a single-level DDT (tableShift == 0) directly after its header.
Definition close.c:390
static void write_tape_file_block(aaruformat_context *ctx)
Serialize the tape file metadata block to the image file.
Definition close.c:2916
static void write_dumphw_block(aaruformat_context *ctx)
Serialize the dump hardware block containing acquisition environment information.
Definition close.c:3748
static void write_checksum_block(aaruformat_context *ctx)
Finalize any active checksum calculations and append a checksum block.
Definition close.c:691
static void write_sector_suffix(aaruformat_context *ctx)
Serialize the optional CD sector suffix block (EDC/ECC region capture).
Definition close.c:1182
static void write_sector_suffix_ddt(aaruformat_context *ctx)
Serialize the per-sector CD suffix status / index DeDuplication Table (DDT v2, suffix variant).
Definition close.c:1474
static void write_tracks_block(aaruformat_context *ctx)
Serialize the tracks metadata block and add it to the index.
Definition close.c:848
static void write_aaru_json_block(aaruformat_context *ctx)
Serialize the Aaru metadata JSON block to the image file.
Definition close.c:4141
static void write_geometry_block(aaruformat_context *ctx)
Serialize the geometry metadata block to the image file.
Definition close.c:3301
static void write_mode2_subheaders_block(aaruformat_context *ctx)
Serialize a MODE 2 (XA) subheaders data block.
Definition close.c:914
static void write_tape_partition_block(aaruformat_context *ctx)
Serialize the tape partition metadata block to the image file.
Definition close.c:3162
static void write_sector_prefix_ddt(aaruformat_context *ctx)
Serialize the per-sector CD prefix status / index DeDuplication Table (DDT v2, prefix variant).
Definition close.c:1315
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.
Definition close.c:633
static int32_t write_cached_secondary_ddt(aaruformat_context *ctx)
Flush a cached secondary (child) DeDuplication Table (DDT) to the image.
Definition close.c:77
static void write_metadata_block(aaruformat_context *ctx)
Serialize the metadata block containing image and media descriptive information.
Definition close.c:3450
static void write_sector_prefix(aaruformat_context *ctx)
Serialize the optional CD sector prefix block.
Definition close.c:1045
static int32_t write_flux_blocks(aaruformat_context *ctx)
Serialize all accumulated flux capture blocks to the image file.
Definition close.c:4754
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
#define AARU_MAGIC
Magic identifier for AaruFormat container (ASCII "AARUFRMT").
Definition consts.h:64
#define MD5_DIGEST_LENGTH
Definition context.h:72
struct TapeFileHashEntry tapeFileHashEntry
#define AARU_CALL
Definition decls.h:46
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
void aaruf_crc64_free(crc64_ctx *ctx)
Frees a CRC64 context.
Definition crc64.c:155
void aaruf_sha256_final(sha256_ctx *ctx, unsigned char *result)
Definition sha256.c:115
crc64_ctx * aaruf_crc64_init()
Initializes a CRC64 context.
Definition crc64.c:32
void aaruf_md5_final(md5_ctx *ctx, unsigned char *result)
Definition md5.c:485
void aaruf_spamsum_free(spamsum_ctx *ctx)
Frees a spamsum (fuzzy hash) context.
Definition spamsum.c:75
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:199
#define AARU_EXPORT
Definition decls.h:55
int aaruf_spamsum_final(spamsum_ctx *ctx, uint8_t *result)
Definition spamsum.c:191
void aaruf_ecc_cd_free(void *context)
Frees a Compact Disc ECC context and its internal tables.
Definition ecc_cd.c:99
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
@ IndexBlock3
Block containing the index v3.
Definition enums.h:151
@ ChecksumBlock
Block containing contents checksums.
Definition enums.h:156
@ DataBlock
Block containing data.
Definition enums.h:144
@ TapePartitionBlock
Block containing list of partitions for a tape image.
Definition enums.h:162
@ GeometryBlock
Block containing logical geometry.
Definition enums.h:152
@ DeDuplicationTableSecondary
Block containing a secondary deduplication table (v2).
Definition enums.h:148
@ AaruMetadataJsonBlock
Block containing JSON version of Aaru Metadata.
Definition enums.h:163
@ CicmBlock
Block containing CICM XML metadata.
Definition enums.h:155
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:146
@ TapeFileBlock
Block containing list of files for a tape image.
Definition enums.h:161
@ DeDuplicationTableSAlpha
Block containing a secondary deduplication table (v2) (mistake).
Definition enums.h:147
@ DumpHardwareBlock
Block containing an array of hardware used to create the image.
Definition enums.h:160
@ MetadataBlock
Block containing metadata.
Definition enums.h:153
@ TracksBlock
Block containing optical disc tracks.
Definition enums.h:154
@ FluxDataBlock
Block containing flux data metadata.
Definition enums.h:164
@ DataStreamPayloadBlock
Block containing compressed data stream payload (e.g., flux data, bitstreams).
Definition enums.h:165
@ OpticalDisc
Purely optical discs.
Definition enums.h:224
@ BlockMedia
Media that is physically block-based or abstracted like that.
Definition enums.h:225
@ Blake3
BLAKE3 hash.
Definition enums.h:179
@ Sha1
SHA-1 hash.
Definition enums.h:176
@ Md5
MD5 hash.
Definition enums.h:175
@ Sha256
SHA-256 hash.
Definition enums.h:177
@ SpamSum
SpamSum (context-triggered piecewise hash).
Definition enums.h:178
@ CdSectorSubchannel
Compact Disc subchannel data.
Definition enums.h:116
@ AppleProfileTag
Apple Profile (20‑byte) tag.
Definition enums.h:117
@ DvdSectorIed
DVD ID Error Detection Code (IED).
Definition enums.h:129
@ FluxData
Flux data.
Definition enums.h:134
@ DvdSectorCprMai
DVD Copyright Management Information (CPR_MAI).
Definition enums.h:126
@ AppleSonyTag
Apple Sony (12‑byte) tag.
Definition enums.h:118
@ CdSectorPrefix
Compact Disc sector prefix (sync, header).
Definition enums.h:114
@ PriamDataTowerTag
Priam Data Tower (24‑byte) tag.
Definition enums.h:119
@ UserData
User (main) data.
Definition enums.h:46
@ DvdSectorEdc
DVD Error Detection Code (EDC).
Definition enums.h:130
@ DvdSectorTitleKeyDecrypted
Decrypted DVD Title Key.
Definition enums.h:127
@ DvdSectorId
DVD Identification Data (ID).
Definition enums.h:128
@ CdSectorSuffix
Compact Disc sector suffix (EDC, ECC P, ECC Q).
Definition enums.h:115
@ CompactDiscMode2Subheader
Compact Disc MODE 2 subheader.
Definition enums.h:123
@ AARU_FEATURE_RW_BLAKE3
BLAKE3 checksum is present (read/write support for BLAKE3 hashes).
Definition enums.h:271
@ AARUF_STATUS_INVALID_CONTEXT
Provided context/handle is invalid.
Definition enums.h:215
CompressionType
List of known compression types.
Definition enums.h:32
@ Lzma
LZMA compression.
Definition enums.h:34
@ LzmaClauniaSubchannelTransform
LZMA applied to Claunia Subchannel Transform processed data.
Definition enums.h:36
@ None
Not compressed.
Definition enums.h:33
#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:77
#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:708
@ AppleSonySS
3.5", SS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
Definition aaru.h:250
@ PriamDataTower
Definition aaru.h:711
@ AppleFileWare
5.25", DS, ?D, ?? tracks, ?? spt, 512 bytes/sector, GCR, opposite side heads, aka Twiggy
Definition aaru.h:252
@ AppleSonyDS
3.5", DS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
Definition aaru.h:251
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:1438
int32_t flux_map_rebuild_from_entries(aaruformat_context *ctx)
Rebuild the flux capture lookup map from the flux_entries array.
Definition flux.c:171
#define FATAL(fmt,...)
Definition log.h:40
#define TRACE(fmt,...)
Definition log.h:25
void free_cache(struct CacheHeader *cache)
Frees all entries in the cache and clears it.
Definition lru.c:130
#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 featureCompatible
Feature bits: unimplemented bits are ignorable (still R/W safe).
Definition header.h:121
uint64_t indexOffset
Absolute byte offset to primary index block (MUST be > 0; 0 => corrupt/unreadable).
Definition header.h:115
Header for an Aaru metadata JSON block (identifier == BlockType::AaruMetadataJsonBlock).
Definition metadata.h:120
uint32_t identifier
Block identifier, must be BlockType::AaruMetadataJsonBlock.
Definition metadata.h:121
uint32_t length
Length in bytes of the Aaru metadata JSON payload that follows.
Definition metadata.h:122
Header preceding the compressed data payload of a data block (BlockType::DataBlock).
Definition data.h:71
uint32_t cmpLength
Size in bytes of the compressed payload immediately following this header.
Definition data.h:76
uint32_t length
Size in bytes of the uncompressed payload resulting after decompression.
Definition data.h:77
uint32_t identifier
Block identifier, must be BlockType::DataBlock.
Definition data.h:72
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload (cmpLength bytes).
Definition data.h:78
uint64_t crc64
CRC64-ECMA of the uncompressed payload (length bytes).
Definition data.h:79
uint16_t type
Logical data classification (value from DataType).
Definition data.h:73
uint16_t compression
Compression algorithm used (value from CompressionType).
Definition data.h:74
Per-checksum metadata immediately followed by the digest / signature bytes.
Definition checksum.h:91
uint32_t length
Length in bytes of the digest that immediately follows this structure.
Definition checksum.h:93
uint8_t type
Algorithm used (value from ChecksumAlgorithm).
Definition checksum.h:92
Header that precedes the sequence of checksum entries for a checksum block.
Definition checksum.h:74
uint32_t identifier
Block identifier, must be BlockType::ChecksumBlock.
Definition checksum.h:75
uint32_t length
Length in bytes of the payload (all entries + their digest data, excluding this header).
Definition checksum.h:76
uint8_t entries
Number of checksum entries that follow in the payload.
Definition checksum.h:77
uint8_t * spamsum
SpamSum fuzzy hash (ASCII), allocated length+1 with trailing 0.
Definition context.h:113
bool hasSha256
True if sha256[] buffer populated.
Definition context.h:106
uint8_t sha1[20]
SHA-1 digest (20 bytes).
Definition context.h:110
uint8_t sha256[32]
SHA-256 digest (32 bytes).
Definition context.h:111
uint8_t md5[16]
MD5 digest (16 bytes).
Definition context.h:109
bool hasSpamSum
True if spamsum pointer allocated and signature read.
Definition context.h:108
bool hasSha1
True if sha1[] buffer populated.
Definition context.h:105
uint8_t blake3[BLAKE3_OUT_LEN]
BLAKE3 digest (32 bytes).
Definition context.h:112
bool hasMd5
True if md5[] buffer populated.
Definition context.h:104
bool hasBlake3
True if blake3[] buffer populated.
Definition context.h:107
Header for a CICM XML metadata block (identifier == BlockType::CicmBlock).
Definition metadata.h:108
uint32_t length
Length in bytes of the CICM metadata payload that follows.
Definition metadata.h:110
uint32_t identifier
Block identifier, must be BlockType::CicmBlock.
Definition metadata.h:109
Header structure for a DataStreamPayloadBlock containing data stream payload.
Definition flux.h:243
uint16_t dataType
Data type classification (value from DataType), e.g., FluxData or BitstreamData.
Definition flux.h:245
uint32_t cmpLength
Compressed length in bytes (includes LZMA properties if compression = Lzma).
Definition flux.h:247
uint64_t cmpCrc64
CRC64-ECMA checksum of the compressed payload (or same as crc64 if uncompressed).
Definition flux.h:249
uint32_t identifier
Block identifier, must be BlockType::DataStreamPayloadBlock (0x4C505344, "DSPL").
Definition flux.h:244
uint16_t compression
Compression type: 0 = None, 1 = Lzma.
Definition flux.h:246
uint32_t length
Uncompressed length in bytes.
Definition flux.h:248
uint64_t crc64
CRC64-ECMA checksum of the uncompressed payload.
Definition flux.h:250
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
uint64_t cmpCrc64
CRC64-ECMA of compressed table payload.
Definition ddt.h:161
uint16_t type
Data classification (DataType) for sectors referenced by this table.
Definition ddt.h:144
uint64_t start
Base internal index covered by this table (used for secondary tables; currently informational).
Definition ddt.h:153
uint64_t crc64
CRC64-ECMA of uncompressed table payload.
Definition ddt.h:162
uint64_t entries
Number of entries contained in (uncompressed) table payload.
Definition ddt.h:158
uint8_t levels
Total number of hierarchy levels (root depth); > 0.
Definition ddt.h:146
uint64_t length
Uncompressed payload size in bytes.
Definition ddt.h:160
uint32_t identifier
Block identifier, must be BlockType::DeDuplicationTable2.
Definition ddt.h:143
uint8_t tableShift
2^tableShift = number of logical sectors per primary entry (multi-level only; 0 for single-level or s...
Definition ddt.h:156
uint64_t blocks
Total internal span (negative + usable + overflow) in logical sectors.
Definition ddt.h:150
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
uint32_t overflow
Trailing dumped sectors beyond user area (overflow range), still mapped with entries.
Definition ddt.h:151
uint32_t negative
Leading negative LBA count; added to external L to build internal index.
Definition ddt.h:149
uint8_t tableLevel
Zero-based level index of this table (0 = root, increases downward).
Definition ddt.h:147
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
uint8_t dataShift
2^dataShift = sectors represented per increment in blockIndex field.
Definition ddt.h:155
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
uint64_t previousLevelOffset
Absolute byte offset of the parent (previous) level table; 0 if root.
Definition ddt.h:148
Inclusive [start,end] logical sector range contributed by a single hardware environment.
Definition context.h:369
uint8_t * firmware
Firmware version string or NULL.
Definition context.h:356
uint8_t * revision
Hardware revision string or NULL.
Definition context.h:355
uint8_t * model
Model string or NULL.
Definition context.h:354
uint8_t * softwareName
Dump software name or NULL.
Definition context.h:358
struct DumpExtent * extents
Array of extents (entry.extents elements) or NULL.
Definition context.h:352
uint8_t * manufacturer
Manufacturer string (UTF-8) or NULL.
Definition context.h:353
uint8_t * softwareVersion
Dump software version or NULL.
Definition context.h:359
uint8_t * serial
Serial number string or NULL.
Definition context.h:357
DumpHardwareEntry entry
Fixed-size header with lengths & counts.
Definition context.h:351
uint8_t * softwareOperatingSystem
Host operating system string or NULL.
Definition context.h:360
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:943
uint8_t MetadataMediaType
Media type for sidecar generation (internal archival use).
Definition aaru.h:944
uint64_t Sectors
Total count of addressable logical sectors/blocks.
Definition aaru.h:936
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
uint16_t dataType
Data classification (value from DataType) or unused for untyped blocks.
Definition index.h:111
Index header (version 3) adding hierarchical chaining (identifier == IndexBlock3).
Definition index.h:93
uint64_t previous
File offset of a previous IndexBlock3 header (0 if none / root segment).
Definition index.h:97
uint32_t identifier
Block identifier (must be BlockType::IndexBlock3).
Definition index.h:94
uint64_t crc64
CRC64-ECMA of the local entries array (does NOT cover subindexes or previous chains).
Definition index.h:96
uint64_t entries
Number of IndexEntry records that follow in this (sub)index block.
Definition index.h:95
Header for a metadata block containing offsets and lengths to UTF-16LE descriptive strings.
Definition metadata.h:69
uint32_t commentsLength
Length in bytes (including null) of comments string.
Definition metadata.h:78
int32_t mediaSequence
Sequence number within a multi-disc / multi-volume set (0-based or 1-based as producer defines).
Definition metadata.h:72
uint32_t identifier
Block identifier, must be BlockType::MetadataBlock.
Definition metadata.h:70
uint32_t mediaTitleOffset
Offset to UTF-16LE media title string.
Definition metadata.h:79
uint32_t driveModelLength
Length in bytes (including null) of drive model string.
Definition metadata.h:94
uint32_t driveManufacturerLength
Length in bytes (including null) of drive manufacturer string.
Definition metadata.h:92
uint32_t blockSize
Total size in bytes of the entire metadata block (header + strings).
Definition metadata.h:71
uint32_t driveModelOffset
Offset to UTF-16LE drive model string.
Definition metadata.h:93
uint32_t mediaModelOffset
Offset to UTF-16LE media model string.
Definition metadata.h:83
uint32_t creatorOffset
Offset to UTF-16LE creator string (or undefined if creatorLength==0).
Definition metadata.h:75
uint32_t mediaTitleLength
Length in bytes (including null) of media title string.
Definition metadata.h:80
uint32_t mediaManufacturerOffset
Offset to UTF-16LE media manufacturer string.
Definition metadata.h:81
uint32_t driveSerialNumberLength
Length in bytes (including null) of drive serial number string.
Definition metadata.h:96
uint32_t driveSerialNumberOffset
Offset to UTF-16LE drive serial number string.
Definition metadata.h:95
uint32_t mediaManufacturerLength
Length in bytes (including null) of media manufacturer string.
Definition metadata.h:82
uint32_t mediaModelLength
Length in bytes (including null) of media model string.
Definition metadata.h:84
uint32_t driveFirmwareRevisionOffset
Offset to UTF-16LE drive firmware revision string.
Definition metadata.h:97
int32_t lastMediaSequence
Total number of media in the set; 0 or 1 if single item.
Definition metadata.h:74
uint32_t commentsOffset
Offset to UTF-16LE comments string.
Definition metadata.h:77
uint32_t driveManufacturerOffset
Offset to UTF-16LE drive manufacturer string.
Definition metadata.h:91
uint32_t mediaSerialNumberOffset
Offset to UTF-16LE media serial number string.
Definition metadata.h:85
uint32_t mediaSerialNumberLength
Length in bytes (including null) of media serial number string.
Definition metadata.h:86
uint32_t mediaPartNumberOffset
Offset to UTF-16LE media part number string.
Definition metadata.h:89
uint32_t mediaPartNumberLength
Length in bytes (including null) of media part number string.
Definition metadata.h:90
uint32_t mediaBarcodeLength
Length in bytes (including null) of media barcode string.
Definition metadata.h:88
uint32_t creatorLength
Length in bytes (including null) of creator string (0 if absent).
Definition metadata.h:76
uint32_t driveFirmwareRevisionLength
Length in bytes (including null) of drive firmware revision string.
Definition metadata.h:98
uint32_t mediaBarcodeOffset
Offset to UTF-16LE media barcode string.
Definition metadata.h:87
uint64_t key
Key: sector address.
Definition context.h:145
uint64_t value
Value: DDT entry.
Definition context.h:146
Describes a single logical file on a tape medium.
Definition tape.h:134
TapeFileEntry fileEntry
The actual tape file data.
Definition context.h:132
Header for a tape file metadata block containing file layout information.
Definition tape.h:238
uint32_t identifier
Block type identifier.
Definition tape.h:239
uint64_t crc64
CRC64-ECMA checksum of the entry data.
Definition tape.h:245
uint64_t length
Size of entry data in bytes (excluding this header).
Definition tape.h:243
Describes a single physical partition on a tape medium.
Definition tape.h:320
TapePartitionEntry partitionEntry
The actual tape partition data.
Definition context.h:139
Header for a tape partition metadata block containing partition layout information.
Definition tape.h:441
uint64_t crc64
CRC64-ECMA checksum of the entry data.
Definition tape.h:448
uint64_t length
Size of entry data in bytes (excluding this header).
Definition tape.h:446
uint32_t identifier
Block type identifier.
Definition tape.h:442
Single optical disc track descriptor (sequence, type, LBAs, session, ISRC, flags).
Definition optical.h:72
Header for an optical tracks block listing track entries.
Definition optical.h:62
uint16_t entries
Number of TrackEntry records following this header.
Definition optical.h:64
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
uint8_t * media_barcode
Barcode of the media represented by the image.
Definition context.h:225
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:192
Checksums checksums
Whole-image checksums discovered.
Definition context.h:272
uint8_t * creator
Who (person) created the image?
Definition context.h:219
bool dirty_checksum_block
True if checksum block should be written during close.
Definition context.h:320
bool deduplicate
Storage deduplication active (duplicates coalesce).
Definition context.h:302
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:303
uint8_t * cicm_block
CICM XML payload.
Definition context.h:217
uint8_t * sector_cpr_mai
DVD sector CPR_MAI (6 bytes) if present.
Definition context.h:210
bool dirty_media_tags
True if media tags should be written during close.
Definition context.h:330
hash_map_t * sector_hash_map
Deduplication hash map (fingerprint->entry mapping).
Definition context.h:256
FluxHeader flux_data_header
Flux data header (if present).
Definition context.h:311
uint8_t * sector_prefix_corrected
Corrected variant (post error correction) if stored.
Definition context.h:203
uint64_t * user_data_ddt
Legacy flat DDT pointer (NULL when using v2 mini/big arrays).
Definition context.h:184
UT_array * flux_captures
Pending flux capture payloads (write path).
Definition context.h:313
sha256_ctx sha256_context
Opaque SHA-256 context for streaming updates.
Definition context.h:275
TrackEntry * data_tracks
Filtered list of data tracks (subset of trackEntries).
Definition context.h:246
bool calculating_sha256
True if whole-image SHA-256 being calculated on-the-fly.
Definition context.h:278
bool dirty_primary_ddt
True if primary DDT table should be written during close.
Definition context.h:318
uint8_t * drive_firmware_revision
Firmware revision of the drive used to read the media represented by the image.
Definition context.h:231
uint8_t * media_serial_number
Serial number of the media represented by the image.
Definition context.h:224
struct CacheHeader block_header_cache
LRU/Cache header for block headers.
Definition context.h:259
uint8_t * sector_ied
DVD sector IED (2 bytes) if present.
Definition context.h:209
md5_ctx md5_context
Opaque MD5 context for streaming updates.
Definition context.h:273
bool dirty_sector_suffix_block
True if sector suffix block should be written during close.
Definition context.h:325
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:190
MetadataBlockHeader metadata_block_header
Metadata block header.
Definition context.h:233
uint8_t * sector_prefix
Raw per-sector prefix (e.g., sync+header) uncorrected.
Definition context.h:202
bool dirty_dvd_title_key_decrypted_block
True if decrypted title key block should be written during close.
Definition context.h:329
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:189
tapeFileHashEntry * tape_files
Hash table root for tape files.
Definition context.h:306
bool dirty_mode2_subheaders_block
True if MODE2 subheader block should be written during close.
Definition context.h:322
bool dirty_tape_partition_block
True if tape partition block should be written during close.
Definition context.h:333
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:193
bool dirty_cicm_block
True if CICM metadata block should be written during close.
Definition context.h:337
bool is_tape
True if the image is a tape image.
Definition context.h:308
uint8_t * sector_edc
DVD sector EDC (4 bytes) if present.
Definition context.h:211
bool calculating_sha1
True if whole-image SHA-1 being calculated on-the-fly.
Definition context.h:277
uint8_t * media_model
Model of the media represented by the image.
Definition context.h:223
bool dirty_tracks_block
True if tracks block should be written during close.
Definition context.h:321
uint8_t * drive_serial_number
Serial number of the drive used to read the media represented by the image.
Definition context.h:229
CdEccContext * ecc_cd_context
CD ECC/EDC helper tables (allocated on demand).
Definition context.h:251
struct CacheHeader block_cache
LRU/Cache header for block payloads.
Definition context.h:260
uint32_t * sector_suffix_ddt
Legacy CD sector suffix DDT.
Definition context.h:187
bool dirty_sector_prefix_block
True if sector prefix block should be written during close.
Definition context.h:323
uint8_t * drive_manufacturer
Manufacturer of the drive used to read the media represented by the image.
Definition context.h:227
bool in_memory_ddt
True if primary (and possibly secondary) DDT loaded.
Definition context.h:199
bool dirty_index_block
True if index block should be written during close.
Definition context.h:340
uint8_t * sector_suffix
Raw per-sector suffix (EDC/ECC) uncorrected.
Definition context.h:204
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:178
FluxEntry * flux_entries
Array of flux entries (flux_data_header.entries elements).
Definition context.h:312
bool dirty_json_block
True if JSON metadata block should be written during close.
Definition context.h:338
bool is_writing
True if context opened/created for writing.
Definition context.h:295
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:185
spamsum_ctx * spamsum_context
Opaque SpamSum context for streaming updates.
Definition context.h:270
CicmMetadataBlock cicm_block_header
CICM metadata header (if present).
Definition context.h:234
size_t sector_prefix_offset
Current position in sector_prefix.
Definition context.h:289
uint8_t * drive_model
Model of the drive used to read the media represented by the image.
Definition context.h:228
bool dirty_single_level_ddt
True if single-level DDT should be written during close.
Definition context.h:319
uint64_t magic
File magic (AARU_MAGIC) post-open.
Definition context.h:177
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:283
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:188
bool calculating_spamsum
True if whole-image SpamSum being calculated on-the-fly.
Definition context.h:279
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:195
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:267
blake3_hasher * blake3_context
Opaque BLAKE3 context for streaming updates.
Definition context.h:271
bool calculating_blake3
True if whole-image BLAKE3 being calculated on-the-fly.
Definition context.h:280
struct DumpHardwareEntriesWithData * dump_hardware_entries_with_data
Array of dump hardware entries + strings.
Definition context.h:215
bool calculating_md5
True if whole-image MD5 being calculated on-the-fly.
Definition context.h:276
bool dirty_sector_suffix_ddt
True if sector suffix DDT should be written during close.
Definition context.h:326
GeometryBlockHeader geometry_block
Logical geometry block (if present).
Definition context.h:232
size_t sector_suffix_offset
Current position in sector_suffix.
Definition context.h:290
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:191
uint8_t * json_block
JSON metadata block payload (UTF-8).
Definition context.h:218
uint8_t * media_part_number
Part number of the media represented by the image.
Definition context.h:226
uint8_t * sector_decrypted_title_key
DVD decrypted title key (5 bytes) if present.
Definition context.h:212
bool dirty_dumphw_block
True if dump hardware block should be written during close.
Definition context.h:336
AaruMetadataJsonBlockHeader json_block_header
JSON metadata block header (if present).
Definition context.h:236
bool dirty_metadata_block
True if metadata block should be written during close.
Definition context.h:335
uint8_t * sector_subchannel
Raw 96-byte subchannel (if captured).
Definition context.h:206
uint8_t * comments
Image comments.
Definition context.h:221
bool dirty_sector_subchannel_block
True if subchannel block should be written during close.
Definition context.h:327
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:179
bool dirty_dvd_long_sector_blocks
True if DVD long sector blocks should be written during close.
Definition context.h:328
bool dirty_sector_prefix_ddt
True if sector prefix DDT should be written during close.
Definition context.h:324
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:255
bool dirty_tape_file_block
True if tape file block should be written during close.
Definition context.h:332
uint8_t * mode2_subheaders
MODE2 Form1/Form2 8-byte subheaders (concatenated).
Definition context.h:207
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:263
bool dirty_flux_block
True if flux block should be written during close.
Definition context.h:339
uint8_t * sector_id
DVD sector ID (4 bytes) if present.
Definition context.h:208
DumpHardwareHeader dump_hardware_header
Dump hardware header.
Definition context.h:235
sha1_ctx sha1_context
Opaque SHA-1 context for streaming updates.
Definition context.h:274
bool dirty_secondary_ddt
True if secondary DDT tables should be written during close.
Definition context.h:317
bool * readableSectorTags
Per-sector boolean array (optical tags read successfully?).
Definition context.h:266
bool dirty_tape_ddt
True if tape DDT should be written during close.
Definition context.h:331
TapePartitionHashEntry * tape_partitions
Hash table root for tape partitions.
Definition context.h:307
uint32_t * sector_prefix_ddt
Legacy CD sector prefix DDT (deprecated by *2).
Definition context.h:186
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:301
TrackEntry * track_entries
Full track list (tracksHeader.entries elements).
Definition context.h:245
uint8_t * sector_suffix_corrected
Corrected suffix if stored separately.
Definition context.h:205
uint8_t * metadata_block
Raw metadata UTF-16LE concatenated strings.
Definition context.h:216
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:194
uint8_t * media_title
Title of the media represented by the image.
Definition context.h:220
bool dirty_geometry_block
True if geometry block should be written during close.
Definition context.h:334
size_t mapped_memory_ddt_size
Length of mmapped DDT if userDataDdt is mmapped.
Definition context.h:196
uint8_t * media_manufacturer
Manufacturer of the media represented by the image.
Definition context.h:222
TracksHeader tracks_header
Tracks header (optical) if present.
Definition context.h:247
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56
Hash table entry for an arbitrary media tag (e.g., proprietary drive/medium descriptor).
Definition context.h:122
uint8_t * data
Tag data blob (opaque to library core); length bytes long.
Definition context.h:123
int32_t type
Numeric type identifier.
Definition context.h:124
uint32_t length
Length in bytes of data.
Definition context.h:125