libaaruformat 1.0 Aaru Data Preservation Suite - Format Library

Loading...
Searching...
No Matches
ddt_v2.c
Go to the documentation of this file.
1/*
2 * This file is part of the Aaru Data Preservation Suite.
3 * Copyright (c) 2019-2025 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 */
18
19#include <inttypes.h>
20#include <stdint.h>
21#include <stdio.h>
22#include <stdlib.h>
23
24#include "aaruformat.h"
25#include "internal.h"
26#include "log.h"
27
96int32_t process_ddt_v2(aaruformat_context *ctx, IndexEntry *entry, bool *found_user_data_ddt)
97{
98 TRACE("Entering process_ddt_v2(%p, %p, %d)", ctx, entry, *found_user_data_ddt);
99
100 int pos = 0;
101 size_t read_bytes = 0;
102 DdtHeader2 ddt_header;
103 uint8_t *cmp_data = NULL;
104 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH];
105 size_t lzma_size = 0;
106 int error_no = 0;
107 crc64_ctx *crc64_context = NULL;
108 uint64_t crc64 = 0;
109 uint8_t *buffer = NULL;
110
111 // Check if the context and image stream are valid
112 if(ctx == NULL || ctx->imageStream == NULL)
113 {
114 FATAL("Invalid context or image stream.");
115
116 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_NOT_AARUFORMAT");
117 return AARUF_ERROR_NOT_AARUFORMAT;
118 }
119
120 // Seek to block
121 pos = fseek(ctx->imageStream, entry->offset, SEEK_SET);
122 if(pos < 0 || ftell(ctx->imageStream) != entry->offset)
123 {
124 FATAL("Could not seek to %" PRIu64 " as indicated by index entry...", entry->offset);
125
126 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
127 return AARUF_ERROR_CANNOT_READ_BLOCK;
128 }
129
130 // Even if those two checks shall have been done before
131 TRACE("Reading DDT block header at position %" PRIu64, entry->offset);
132 read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader2), ctx->imageStream);
133
134 if(read_bytes != sizeof(DdtHeader2))
135 {
136 FATAL("Could not read block header at %" PRIu64 "", entry->offset);
137
138 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
139 return AARUF_ERROR_CANNOT_READ_BLOCK;
140 }
141
142 ctx->image_info.ImageSize += ddt_header.cmpLength;
143
144 if(entry->dataType == UserData)
145 {
146 // User area sectors is blocks stored in DDT minus the negative and overflow displacement blocks
147 ctx->image_info.Sectors = ddt_header.blocks - ddt_header.negative - ddt_header.overflow;
148 // We need the header later for the shift calculations
149 ctx->user_data_ddt_header = ddt_header;
150 ctx->ddt_version = 2;
151 // Store the primary DDT table's file offset for secondary table references
152 ctx->primary_ddt_offset = entry->offset;
153
154 // Check for DDT compression
155 switch(ddt_header.compression)
156 {
157 case Lzma:
158 if(ddt_header.cmpLength <= LZMA_PROPERTIES_LENGTH)
159 {
160 FATAL("Compressed DDT payload too small (%" PRIu64 ") for LZMA properties.", ddt_header.cmpLength);
161 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
162 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
163 }
164
165 lzma_size = (size_t)(ddt_header.cmpLength - LZMA_PROPERTIES_LENGTH);
166
167 cmp_data = (uint8_t *)malloc(lzma_size);
168 if(cmp_data == NULL)
169 {
170 TRACE("Cannot allocate memory for DDT, continuing...");
171 break;
172 }
173
174 buffer = malloc(ddt_header.length);
175 if(buffer == NULL)
176 {
177 TRACE("Cannot allocate memory for DDT, continuing...");
178 free(cmp_data);
179 break;
180 }
181
182 read_bytes = fread(lzma_properties, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
183 if(read_bytes != LZMA_PROPERTIES_LENGTH)
184 {
185 TRACE("Could not read LZMA properties, continuing...");
186 free(cmp_data);
187 free(buffer);
188 break;
189 }
190
191 read_bytes = fread(cmp_data, 1, lzma_size, ctx->imageStream);
192 if(read_bytes != lzma_size)
193 {
194 TRACE("Could not read compressed block, continuing...");
195 free(cmp_data);
196 free(buffer);
197 break;
198 }
199
200 read_bytes = ddt_header.length;
201 TRACE("Decompressing block of size %zu bytes", ddt_header.length);
202 error_no = aaruf_lzma_decode_buffer(buffer, &read_bytes, cmp_data, &lzma_size, lzma_properties,
203 LZMA_PROPERTIES_LENGTH);
204
205 if(error_no != 0)
206 {
207 FATAL("Got error %d from LZMA, stopping...", error_no);
208 free(cmp_data);
209 free(buffer);
210 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
211 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
212 }
213
214 if(read_bytes != ddt_header.length)
215 {
216 FATAL("Error decompressing block, should be {0} bytes but got {1} bytes., stopping...");
217 free(cmp_data);
218 free(buffer);
219 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
220 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
221 }
222
223 free(cmp_data);
224
225 crc64_context = aaruf_crc64_init();
226
227 if(crc64_context == NULL)
228 {
229 FATAL("Could not initialize CRC64.");
230 free(buffer);
231
232 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
233 return AARUF_ERROR_CANNOT_READ_BLOCK;
234 }
235
236 aaruf_crc64_update(crc64_context, buffer, read_bytes);
237 aaruf_crc64_final(crc64_context, &crc64);
238
239 if(crc64 != ddt_header.crc64)
240 {
241 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
242 free(buffer);
243 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
244 return AARUF_ERROR_INVALID_BLOCK_CRC;
245 }
246
247 ctx->user_data_ddt2 = (uint64_t *)buffer;
248
249 ctx->in_memory_ddt = true;
250 *found_user_data_ddt = true;
251
252 break;
253 case None:
254 buffer = malloc(ddt_header.length);
255
256 if(buffer == NULL)
257 {
258 TRACE("Cannot allocate memory for DDT, continuing...");
259 break;
260 }
261
262 TRACE("Reading DDT of length %zu bytes", ddt_header.length);
263 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
264
265 if(read_bytes != ddt_header.length)
266 {
267 free(buffer);
268 FATAL("Could not read deduplication table, continuing...");
269 break;
270 }
271
272 crc64_context = aaruf_crc64_init();
273
274 if(crc64_context == NULL)
275 {
276 FATAL("Could not initialize CRC64.");
277 free(buffer);
278 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
279 return AARUF_ERROR_CANNOT_READ_BLOCK;
280 }
281
282 aaruf_crc64_update(crc64_context, buffer, read_bytes);
283 aaruf_crc64_final(crc64_context, &crc64);
284
285 if(crc64 != ddt_header.crc64)
286 {
287 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
288 free(buffer);
289 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
290 return AARUF_ERROR_INVALID_BLOCK_CRC;
291 }
292
293 ctx->user_data_ddt2 = (uint64_t *)buffer;
294
295 ctx->in_memory_ddt = true;
296 *found_user_data_ddt = true;
297
298 break;
299 default:
300 TRACE("Found unknown compression type %d, continuing...", ddt_header.compression);
301 *found_user_data_ddt = false;
302 break;
303 }
304 }
305 else if(entry->dataType == CdSectorPrefix || entry->dataType == CdSectorSuffix)
306 switch(ddt_header.compression)
307 {
308 case Lzma:
309 if(ddt_header.cmpLength <= LZMA_PROPERTIES_LENGTH)
310 {
311 FATAL("Compressed DDT payload too small (%" PRIu64 ") for LZMA properties.", ddt_header.cmpLength);
312 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
313 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
314 }
315
316 lzma_size = (size_t)(ddt_header.cmpLength - LZMA_PROPERTIES_LENGTH);
317
318 cmp_data = (uint8_t *)malloc(lzma_size);
319 if(cmp_data == NULL)
320 {
321 TRACE("Cannot allocate memory for DDT, continuing...");
322 break;
323 }
324
325 buffer = malloc(ddt_header.length);
326 if(buffer == NULL)
327 {
328 TRACE("Cannot allocate memory for DDT, continuing...");
329 free(cmp_data);
330 break;
331 }
332
333 read_bytes = fread(lzma_properties, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
334 if(read_bytes != LZMA_PROPERTIES_LENGTH)
335 {
336 TRACE("Could not read LZMA properties, continuing...");
337 free(cmp_data);
338 free(buffer);
339 break;
340 }
341
342 read_bytes = fread(cmp_data, 1, lzma_size, ctx->imageStream);
343 if(read_bytes != lzma_size)
344 {
345 TRACE("Could not read compressed block, continuing...");
346 free(cmp_data);
347 free(buffer);
348 break;
349 }
350
351 read_bytes = ddt_header.length;
352 TRACE("Decompressing block of size %zu bytes", ddt_header.length);
353 error_no = aaruf_lzma_decode_buffer(buffer, &read_bytes, cmp_data, &lzma_size, lzma_properties,
354 LZMA_PROPERTIES_LENGTH);
355
356 if(error_no != 0)
357 {
358 FATAL("Got error %d from LZMA, stopping...", error_no);
359 free(cmp_data);
360 free(buffer);
361 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
362 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
363 }
364
365 if(read_bytes != ddt_header.length)
366 {
367 FATAL("Error decompressing block, should be {0} bytes but got {1} bytes., stopping...");
368 free(cmp_data);
369 free(buffer);
370 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
371 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
372 }
373
374 free(cmp_data);
375 cmp_data = NULL;
376
377 crc64_context = aaruf_crc64_init();
378
379 if(crc64_context == NULL)
380 {
381 FATAL("Could not initialize CRC64.");
382 free(buffer);
383 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
384 return AARUF_ERROR_CANNOT_READ_BLOCK;
385 }
386
387 aaruf_crc64_update(crc64_context, buffer, read_bytes);
388 aaruf_crc64_final(crc64_context, &crc64);
389
390 if(crc64 != ddt_header.crc64)
391 {
392 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
393 free(buffer);
394 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
395 return AARUF_ERROR_INVALID_BLOCK_CRC;
396 }
397
398 if(entry->dataType == CdSectorPrefix)
399 ctx->sector_prefix_ddt2 = (uint64_t *)buffer;
400 else if(entry->dataType == CdSectorSuffix)
401 ctx->sector_suffix_ddt2 = (uint64_t *)buffer;
402 else
403 free(buffer);
404
405 break;
406
407 case None:
408 buffer = malloc(ddt_header.length);
409
410 if(buffer == NULL)
411 {
412 TRACE("Cannot allocate memory for deduplication table.");
413 break;
414 }
415
416 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
417
418 if(read_bytes != ddt_header.length)
419 {
420 free(buffer);
421 FATAL("Could not read deduplication table, continuing...");
422 break;
423 }
424
425 crc64_context = aaruf_crc64_init();
426
427 if(crc64_context == NULL)
428 {
429 FATAL("Could not initialize CRC64.");
430 free(buffer);
431 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
432 return AARUF_ERROR_CANNOT_READ_BLOCK;
433 }
434
435 aaruf_crc64_update(crc64_context, buffer, read_bytes);
436 aaruf_crc64_final(crc64_context, &crc64);
437
438 if(crc64 != ddt_header.crc64)
439 {
440 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
441 free(buffer);
442 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
443 return AARUF_ERROR_INVALID_BLOCK_CRC;
444 }
445
446 if(entry->dataType == CdSectorPrefix)
447 ctx->sector_prefix_ddt2 = (uint64_t *)buffer;
448 else if(entry->dataType == CdSectorSuffix)
449 ctx->sector_suffix_ddt2 = (uint64_t *)buffer;
450 else
451 free(buffer);
452
453 break;
454 default:
455 TRACE("Found unknown compression type %d, continuing...", ddt_header.compression);
456 break;
457 }
458
459 TRACE("Exiting process_ddt_v2() = AARUF_STATUS_OK");
460 return AARUF_STATUS_OK;
461}
462
507int32_t decode_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, bool negative, uint64_t *offset,
508 uint64_t *block_offset, uint8_t *sector_status)
509{
510 TRACE("Entering decode_ddt_entry_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative, *offset,
511 *block_offset, *sector_status);
512 // Check if the context and image stream are valid
513 if(ctx == NULL || ctx->imageStream == NULL)
514 {
515 FATAL("Invalid context or image stream.");
516
517 TRACE("Exiting decode_ddt_entry_v2() = AARUF_ERROR_NOT_AARUFORMAT");
518 return AARUF_ERROR_NOT_AARUFORMAT;
519 }
520
521 if(ctx->user_data_ddt_header.tableShift > 0)
522 return decode_ddt_multi_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status);
523
524 return decode_ddt_single_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status);
525}
526
581int32_t decode_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t *offset,
582 uint64_t *block_offset, uint8_t *sector_status)
583{
584 TRACE("Entering decode_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
585 *offset, *block_offset, *sector_status);
586
587 uint64_t ddt_entry = 0;
588
589 // Check if the context and image stream are valid
590 if(ctx == NULL || ctx->imageStream == NULL)
591 {
592 FATAL("Invalid context or image stream.");
593
594 TRACE("Exiting decode_ddt_single_level_v2() = AARUF_ERROR_NOT_AARUFORMAT");
595 return AARUF_ERROR_NOT_AARUFORMAT;
596 }
597
598 // Should not really be here
599 if(ctx->user_data_ddt_header.tableShift != 0)
600 {
601 FATAL("DDT table shift is not zero, but we are in single-level DDT decoding.");
602 TRACE("Exiting decode_ddt_single_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
603 return AARUF_ERROR_CANNOT_READ_BLOCK;
604 }
605
606 // Calculate positive or negative sector
607 if(negative)
608 sector_address -= ctx->user_data_ddt_header.negative;
609 else
610 sector_address += ctx->user_data_ddt_header.negative;
611
612 ddt_entry = ctx->user_data_ddt2[sector_address];
613
614 if(ddt_entry == 0)
615 {
616 *sector_status = SectorStatusNotDumped;
617 *offset = 0;
618 *block_offset = 0;
619 TRACE("Exiting decode_ddt_single_level_v2(%p, %" PRIu64 ", %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
620 sector_address, *offset, *block_offset, *sector_status);
621 return AARUF_STATUS_OK;
622 }
623
624 *sector_status = ddt_entry >> 60;
625 ddt_entry &= 0xFFFFFFFFFFFFFFF;
626
627 const uint64_t offset_mask = (uint64_t)((1 << ctx->user_data_ddt_header.dataShift) - 1);
628 *offset = ddt_entry & offset_mask;
629 *block_offset =
630 (ddt_entry >> ctx->user_data_ddt_header.dataShift) * (1 << ctx->user_data_ddt_header.blockAlignmentShift);
631
632 TRACE("Exiting decode_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
633 sector_address, negative, *offset, *block_offset, *sector_status);
634 return AARUF_STATUS_OK;
635}
636
724int32_t decode_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t *offset,
725 uint64_t *block_offset, uint8_t *sector_status)
726{
727 TRACE("Entering decode_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
728 *offset, *block_offset, *sector_status);
729
730 uint64_t ddt_entry = 0;
731 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH];
732 size_t lzma_size = 0;
733 uint8_t *cmp_data = NULL;
734 uint8_t *buffer = NULL;
735 crc64_ctx *crc64_context = NULL;
736 uint64_t crc64 = 0;
737 int items_per_ddt_entry = 0;
738 uint64_t ddt_position = 0;
739 uint64_t secondary_ddt_offset = 0;
740
741 // Check if the context and image stream are valid
742 if(ctx == NULL || ctx->imageStream == NULL)
743 {
744 FATAL("Invalid context or image stream.");
745
746 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_NOT_AARUFORMAT");
747 return AARUF_ERROR_NOT_AARUFORMAT;
748 }
749
750 // Should not really be here
751 if(ctx->user_data_ddt_header.tableShift == 0)
752 {
753 FATAL("DDT table shift is zero, but we are in multi-level DDT decoding.");
754 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
755 return AARUF_ERROR_CANNOT_READ_BLOCK;
756 }
757
758 // Calculate positive or negative sector
759 if(negative)
760 sector_address -= ctx->user_data_ddt_header.negative;
761 else
762 sector_address += ctx->user_data_ddt_header.negative;
763
764 items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
765 ddt_position = sector_address / items_per_ddt_entry;
766 secondary_ddt_offset = ctx->user_data_ddt2[ddt_position];
767
768 // Position in file of the child DDT table
769 secondary_ddt_offset *= 1 << ctx->user_data_ddt_header.blockAlignmentShift;
770
771 // Is the one we have cached the same as the one we need to read?
772 if(ctx->cached_ddt_offset != secondary_ddt_offset)
773 {
774 int32_t error_no = 0;
775 fseek(ctx->imageStream, secondary_ddt_offset, SEEK_SET);
776 DdtHeader2 ddt_header;
777 size_t read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader2), ctx->imageStream);
778
779 if(read_bytes != sizeof(DdtHeader2))
780 {
781 FATAL("Could not read block header at %" PRIu64 "", secondary_ddt_offset);
782 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
783 return AARUF_ERROR_CANNOT_READ_BLOCK;
784 }
785
786 if(ddt_header.identifier != DeDuplicationTableSecondary || ddt_header.type != UserData)
787 {
788 FATAL("Invalid block header at %" PRIu64 "", secondary_ddt_offset);
789 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
790 return AARUF_ERROR_CANNOT_READ_BLOCK;
791 }
792
793 // Check for DDT compression
794 switch(ddt_header.compression)
795 {
796 case Lzma:
797 if(ddt_header.cmpLength <= LZMA_PROPERTIES_LENGTH)
798 {
799 FATAL("Compressed DDT payload too small (%" PRIu64 ") for LZMA properties.", ddt_header.cmpLength);
800 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
801 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
802 }
803
804 lzma_size = (size_t)(ddt_header.cmpLength - LZMA_PROPERTIES_LENGTH);
805
806 cmp_data = (uint8_t *)malloc(lzma_size);
807 if(cmp_data == NULL)
808 {
809 FATAL("Cannot allocate memory for DDT, stopping...");
810 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
811 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
812 }
813
814 buffer = malloc(ddt_header.length);
815 if(buffer == NULL)
816 {
817 FATAL("Cannot allocate memory for DDT, stopping...");
818 free(cmp_data);
819 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
820 }
821
822 read_bytes = fread(lzma_properties, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
823 if(read_bytes != LZMA_PROPERTIES_LENGTH)
824 {
825 FATAL("Could not read LZMA properties, stopping...");
826 free(cmp_data);
827 free(buffer);
828 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
829 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
830 }
831
832 read_bytes = fread(cmp_data, 1, lzma_size, ctx->imageStream);
833 if(read_bytes != lzma_size)
834 {
835 FATAL("Could not read compressed block, stopping...");
836 free(cmp_data);
837 free(buffer);
838 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
839 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
840 }
841
842 TRACE("Decompressing block of size %zu bytes", ddt_header.length);
843 read_bytes = ddt_header.length;
844 error_no = aaruf_lzma_decode_buffer(buffer, &read_bytes, cmp_data, &lzma_size, lzma_properties,
845 LZMA_PROPERTIES_LENGTH);
846
847 if(error_no != 0)
848 {
849 FATAL("Got error %d from LZMA, stopping...", error_no);
850 free(cmp_data);
851 free(buffer);
852 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
853 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
854 }
855
856 if(read_bytes != ddt_header.length)
857 {
858 FATAL("Error decompressing block, should be {0} bytes but got {1} bytes., stopping...");
859 free(cmp_data);
860 free(buffer);
861 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
862 return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
863 }
864
865 free(cmp_data);
866
867 crc64_context = aaruf_crc64_init();
868
869 if(crc64_context == NULL)
870 {
871 FATAL("Could not initialize CRC64.");
872 free(buffer);
873 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
874 return AARUF_ERROR_CANNOT_READ_BLOCK;
875 }
876
877 aaruf_crc64_update(crc64_context, buffer, read_bytes);
878 aaruf_crc64_final(crc64_context, &crc64);
879
880 if(crc64 != ddt_header.crc64)
881 {
882 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
883 free(buffer);
884 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
885 return AARUF_ERROR_INVALID_BLOCK_CRC;
886 }
887
888 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
889
890 ctx->cached_ddt_offset = secondary_ddt_offset;
891
892 break;
893 case None:
894 buffer = malloc(ddt_header.length);
895
896 if(buffer == NULL)
897 {
898 FATAL("Cannot allocate memory for DDT, stopping...");
899 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
900 return AARUF_ERROR_CANNOT_READ_BLOCK;
901 }
902
903 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
904
905 if(read_bytes != ddt_header.length)
906 {
907 free(buffer);
908 FATAL("Could not read deduplication table, stopping...");
909 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
910 return AARUF_ERROR_CANNOT_READ_BLOCK;
911 }
912
913 crc64_context = aaruf_crc64_init();
914
915 if(crc64_context == NULL)
916 {
917 FATAL("Could not initialize CRC64.");
918 free(buffer);
919 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
920 return AARUF_ERROR_CANNOT_READ_BLOCK;
921 }
922
923 aaruf_crc64_update(crc64_context, buffer, read_bytes);
924 aaruf_crc64_final(crc64_context, &crc64);
925
926 if(crc64 != ddt_header.crc64)
927 {
928 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
929 free(buffer);
930 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
931 return AARUF_ERROR_INVALID_BLOCK_CRC;
932 }
933
934 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
935
936 ctx->cached_ddt_offset = secondary_ddt_offset;
937
938 break;
939 default:
940 FATAL("Found unknown compression type %d, stopping...", ddt_header.compression);
941 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
942 return AARUF_ERROR_CANNOT_READ_BLOCK;
943 }
944 }
945
946 ddt_entry = ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry];
947
948 if(ddt_entry == 0)
949 {
950 *sector_status = SectorStatusNotDumped;
951 *offset = 0;
952 *block_offset = 0;
953
954 TRACE("Exiting decode_ddt_multi_level_v2(%p, %" PRIu64 ", %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
955 sector_address, *offset, *block_offset, *sector_status);
956 return AARUF_STATUS_OK;
957 }
958
959 *sector_status = ddt_entry >> 60;
960 ddt_entry &= 0x0FFFFFFFFFFFFFFF;
961
962 const uint64_t offset_mask = (uint64_t)((1 << ctx->user_data_ddt_header.dataShift) - 1);
963 *offset = ddt_entry & offset_mask;
964 *block_offset =
965 (ddt_entry >> ctx->user_data_ddt_header.dataShift) * (1 << ctx->user_data_ddt_header.blockAlignmentShift);
966
967 TRACE("Exiting decode_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
968 sector_address, negative, *offset, *block_offset, *sector_status);
969 return AARUF_STATUS_OK;
970}
971
988 bool set_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, bool negative, const uint64_t offset,
989 const uint64_t block_offset, const uint8_t sector_status, uint64_t *ddt_entry)
990{
991 TRACE("Entering set_ddt_entry_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative, offset,
992 block_offset, sector_status);
993
994 // Check if the context and image stream are valid
995 if(ctx == NULL || ctx->imageStream == NULL)
996 {
997 FATAL("Invalid context or image stream.");
998 return false;
999 }
1000
1001 if(ctx->user_data_ddt_header.tableShift > 0)
1002 return set_ddt_multi_level_v2(ctx, sector_address, false, offset, block_offset, sector_status, ddt_entry);
1003
1004 return set_ddt_single_level_v2(ctx, sector_address, false, offset, block_offset, sector_status, ddt_entry);
1005}
1006
1023 bool set_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, const bool negative,
1024 const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status,
1025 uint64_t *ddt_entry)
1026{
1027 TRACE("Entering set_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
1028 offset, block_offset, sector_status);
1029
1030 // Check if the context and image stream are valid
1031 if(ctx == NULL || ctx->imageStream == NULL)
1032 {
1033 FATAL("Invalid context or image stream.");
1034 TRACE("Exiting set_ddt_single_level_v2() = false");
1035 return false;
1036 }
1037
1038 // Should not really be here
1039 if(ctx->user_data_ddt_header.tableShift != 0)
1040 {
1041 FATAL("DDT table shift is not zero, but we are in single-level DDT setting.");
1042 TRACE("Exiting set_ddt_single_level_v2() = false");
1043 return false;
1044 }
1045
1046 // Calculate positive or negative sector
1047 if(negative)
1048 sector_address -= ctx->user_data_ddt_header.negative;
1049 else
1050 sector_address += ctx->user_data_ddt_header.negative;
1051
1052 if(*ddt_entry == 0)
1053 {
1054 const uint64_t block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1055 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1056 block_index << ctx->user_data_ddt_header.dataShift;
1057
1058 // Overflow detection for DDT entry
1059 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1060 {
1061 FATAL("DDT overflow: media does not fit in big DDT");
1062 TRACE("Exiting set_ddt_single_level_v2() = false");
1063 return false;
1064 }
1065
1066 *ddt_entry |= (uint64_t)sector_status << 60;
1067 }
1068
1069 TRACE("Setting big single-level DDT entry %d to %ull", sector_address, (uint64_t)*ddt_entry);
1070 ctx->user_data_ddt2[sector_address] = *ddt_entry;
1071
1072 TRACE("Exiting set_ddt_single_level_v2() = true");
1073 return true;
1074}
1075
1092 bool set_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t offset,
1093 uint64_t block_offset, uint8_t sector_status, uint64_t *ddt_entry)
1094{
1095 TRACE("Entering set_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %" PRIu64 ", %" PRIu64 ", %d)", ctx, sector_address,
1096 negative, offset, block_offset, sector_status);
1097
1098 uint64_t items_per_ddt_entry = 0;
1099 uint64_t ddt_position = 0;
1100 uint64_t secondary_ddt_offset = 0;
1101 uint64_t block_index = 0;
1102 uint8_t *buffer = NULL;
1103 crc64_ctx *crc64_context = NULL;
1104 uint64_t crc64 = 0;
1105 DdtHeader2 ddt_header;
1106 size_t written_bytes = 0;
1107 long end_of_file = 0;
1108 bool create_new_table = false;
1109
1110 // Check if the context and image stream are valid
1111 if(ctx == NULL || ctx->imageStream == NULL)
1112 {
1113 FATAL("Invalid context or image stream.");
1114 TRACE("Exiting set_ddt_multi_level_v2() = false");
1115 return false;
1116 }
1117
1118 // Should not really be here
1119 if(ctx->user_data_ddt_header.tableShift == 0)
1120 {
1121 FATAL("DDT table shift is zero, but we are in multi-level DDT setting.");
1122 TRACE("Exiting set_ddt_multi_level_v2() = false");
1123 return false;
1124 }
1125
1126 // Calculate positive or negative sector
1127 if(negative)
1128 sector_address -= ctx->user_data_ddt_header.negative;
1129 else
1130 sector_address += ctx->user_data_ddt_header.negative;
1131
1132 // Step 1: Calculate the corresponding secondary level table
1133 items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
1134 ddt_position = sector_address / items_per_ddt_entry;
1135 secondary_ddt_offset = ctx->user_data_ddt2[ddt_position];
1136
1137 // Position in file of the child DDT table
1138 secondary_ddt_offset *= 1 << ctx->user_data_ddt_header.blockAlignmentShift;
1139
1140 // Step 2: Check if it corresponds to the currently in-memory cached secondary level table
1141 if(ctx->cached_ddt_offset == secondary_ddt_offset && secondary_ddt_offset != 0)
1142 {
1143 // Update the corresponding DDT entry directly in the cached table
1144 if(*ddt_entry == 0)
1145 {
1146 block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1147 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1148 block_index << ctx->user_data_ddt_header.dataShift;
1149
1150 // Overflow detection for DDT entry
1151 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1152 {
1153 FATAL("DDT overflow: media does not fit in big DDT");
1154 TRACE("Exiting set_ddt_multi_level_v2() = false");
1155 return false;
1156 }
1157
1158 *ddt_entry |= (uint64_t)sector_status << 60;
1159 }
1160
1161 TRACE("Setting small secondary DDT entry %d to %ull", sector_address % items_per_ddt_entry,
1162 (uint64_t)*ddt_entry);
1163 ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry] = *ddt_entry;
1164
1165 TRACE("Updated cached secondary DDT entry at position %" PRIu64, sector_address % items_per_ddt_entry);
1166 TRACE("Exiting set_ddt_multi_level_v2() = true");
1167 return true;
1168 }
1169
1170 // Step 2.5: Handle case where we have a cached secondary DDT that has never been written to disk
1171 // but does not contain the requested block
1172 if(ctx->cached_ddt_offset == 0 && (ctx->cached_secondary_ddt2 != NULL))
1173 {
1174 // Only write the cached table to disk if the requested block belongs to a different DDT position
1175 if(ddt_position != ctx->cached_ddt_position)
1176 {
1177 TRACE("Current secondary DDT in memory belongs to position %" PRIu64
1178 " but requested block needs position %" PRIu64,
1179 ctx->cached_ddt_position, ddt_position);
1180
1181 // Write the cached DDT to disk before proceeding with the new one
1182
1183 // Close the current data block first
1184 if(ctx->writing_buffer != NULL) aaruf_close_current_block(ctx);
1185
1186 // Get current position and seek to end of file
1187 fseek(ctx->imageStream, 0, SEEK_END);
1188 end_of_file = ftell(ctx->imageStream);
1189
1190 // Align to block boundary
1191 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1192 end_of_file = end_of_file + alignment_mask & ~alignment_mask;
1193 fseek(ctx->imageStream, end_of_file, SEEK_SET);
1194
1195 // Prepare DDT header for the never-written cached table
1196 memset(&ddt_header, 0, sizeof(DdtHeader2));
1197 ddt_header.identifier = DeDuplicationTableSecondary;
1198 ddt_header.type = UserData;
1199 ddt_header.compression = ctx->compression_enabled ? Lzma : None; // Use no compression for simplicity
1200 ddt_header.levels = ctx->user_data_ddt_header.levels;
1201 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
1202 ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
1203 ddt_header.negative = ctx->user_data_ddt_header.negative;
1204 ddt_header.blocks = items_per_ddt_entry;
1205 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
1206 ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry; // Use cached position with table shift
1207 ddt_header.blockAlignmentShift = ctx->user_data_ddt_header.blockAlignmentShift;
1208 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
1209 ddt_header.tableShift = 0; // Secondary tables are single level
1210 ddt_header.entries = items_per_ddt_entry;
1211
1212 // Calculate data size
1213
1214 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
1215
1216 // Calculate CRC64 of the data
1217 crc64_context = aaruf_crc64_init();
1218 if(crc64_context == NULL)
1219 {
1220 FATAL("Could not initialize CRC64.");
1221 TRACE("Exiting set_ddt_multi_level_v2() = false");
1222 return false;
1223 }
1224
1225 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
1226
1227 aaruf_crc64_final(crc64_context, &crc64);
1228 ddt_header.crc64 = crc64;
1229
1230 uint8_t *cmp_buffer = NULL;
1231 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1232
1233 if(ddt_header.compression == None)
1234 {
1235
1236 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1237 ddt_header.cmpCrc64 = ddt_header.crc64;
1238 }
1239 else
1240 {
1241 cmp_buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
1242 if(cmp_buffer == NULL)
1243 {
1244 TRACE("Failed to allocate memory for secondary DDT v2 compression");
1245 return AARUF_ERROR_NOT_ENOUGH_MEMORY;
1246 }
1247
1248 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
1249 size_t props_size = LZMA_PROPERTIES_LENGTH;
1250 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2,
1251 ddt_header.length, lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0,
1252 2, 273, 8);
1253
1254 ddt_header.cmpLength = (uint32_t)dst_size;
1255
1256 if(ddt_header.cmpLength >= ddt_header.length)
1257 {
1258 ddt_header.compression = None;
1259 free(cmp_buffer);
1260
1261 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1262 }
1263 }
1264
1265 if(ddt_header.compression == None)
1266 {
1267 ddt_header.cmpLength = ddt_header.length;
1268 ddt_header.cmpCrc64 = ddt_header.crc64;
1269 }
1270 else
1271 ddt_header.cmpCrc64 = aaruf_crc64_data(cmp_buffer, (uint32_t)ddt_header.cmpLength);
1272
1273 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
1274
1275 // Write header
1276 written_bytes = fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
1277 if(written_bytes != 1)
1278 {
1279 FATAL("Could not write never-written DDT header to file.");
1280 TRACE("Exiting set_ddt_multi_level_v2() = false");
1281 return false;
1282 }
1283
1284 // Write data
1285 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1286
1287 if(fwrite(cmp_buffer, ddt_header.cmpLength, 1, ctx->imageStream) != 1)
1288 {
1289 FATAL("Could not write never-written DDT data to file.");
1290 TRACE("Exiting set_ddt_multi_level_v2() = false");
1291 return false;
1292 }
1293
1294 if(ddt_header.compression == Lzma) free(cmp_buffer);
1295
1296 // Add index entry for the newly written secondary DDT
1297 IndexEntry new_ddt_entry;
1298 new_ddt_entry.blockType = DeDuplicationTableSecondary;
1299 new_ddt_entry.dataType = UserData;
1300 new_ddt_entry.offset = end_of_file;
1301
1302 utarray_push_back(ctx->index_entries, &new_ddt_entry);
1303 TRACE("Added new DDT index entry for never-written table at offset %" PRIu64, end_of_file);
1304
1305 // Update the primary level table entry to point to the new location of the secondary table
1306 uint64_t new_secondary_table_block_offset = end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
1307
1308 ctx->user_data_ddt2[ctx->cached_ddt_position] = new_secondary_table_block_offset;
1309
1310 // Write the updated primary table back to its original position in the file
1311 long saved_pos = ftell(ctx->imageStream);
1312 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
1313
1314 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
1315
1316 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
1317
1318 if(written_bytes != 1)
1319 {
1320 FATAL("Could not flush primary DDT table to file after writing never-written secondary table.");
1321 TRACE("Exiting set_ddt_multi_level_v2() = false");
1322 return false;
1323 }
1324
1325 // Update nextBlockPosition to ensure future blocks don't overwrite the DDT
1326 uint64_t ddt_total_size = sizeof(DdtHeader2) + ddt_header.length;
1327 ctx->next_block_position = end_of_file + ddt_total_size + alignment_mask & ~alignment_mask;
1328 block_offset = ctx->next_block_position;
1329 offset = 0;
1330 TRACE("Updated nextBlockPosition after never-written DDT write to %" PRIu64, ctx->next_block_position);
1331
1332 // Free the cached table
1333
1334 free(ctx->cached_secondary_ddt2);
1335 ctx->cached_secondary_ddt2 = NULL;
1336
1337 // Reset cached values since we've written and freed the table
1338 ctx->cached_ddt_offset = 0;
1339 ctx->cached_ddt_position = 0;
1340
1341 // Restore file position
1342 fseek(ctx->imageStream, saved_pos, SEEK_SET);
1343
1344 TRACE("Successfully wrote never-written cached secondary DDT to disk");
1345 }
1346 else
1347 // The cached DDT is actually for the requested block range, so we can use it directly
1348 TRACE("Cached DDT is for the correct block range, using it directly");
1349 // No need to write to disk, just continue with the cached table
1350 }
1351
1352 // Step 3: Write the currently in-memory cached secondary level table to the end of the file
1353 if(ctx->cached_ddt_offset != 0)
1354 {
1355 long current_pos = 0;
1356 // Close the current data block first
1357 if(ctx->writing_buffer != NULL) aaruf_close_current_block(ctx);
1358
1359 // Get current position and seek to end of file
1360 current_pos = ftell(ctx->imageStream);
1361 fseek(ctx->imageStream, 0, SEEK_END);
1362 end_of_file = ftell(ctx->imageStream);
1363
1364 // Align to block boundary
1365 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1366 end_of_file = end_of_file + alignment_mask & ~alignment_mask;
1367 fseek(ctx->imageStream, end_of_file, SEEK_SET);
1368
1369 // Prepare DDT header for the cached table
1370 memset(&ddt_header, 0, sizeof(DdtHeader2));
1371 ddt_header.identifier = DeDuplicationTableSecondary;
1372 ddt_header.type = UserData;
1373 ddt_header.compression = ctx->compression_enabled ? Lzma : None;
1374 ddt_header.levels = ctx->user_data_ddt_header.levels;
1375 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
1376 ddt_header.previousLevelOffset = ctx->primary_ddt_offset; // Set to primary DDT table location
1377 ddt_header.negative = ctx->user_data_ddt_header.negative;
1378 ddt_header.blocks = items_per_ddt_entry;
1379 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
1380 ddt_header.start = ddt_position * items_per_ddt_entry; // First block this DDT table references
1381 ddt_header.blockAlignmentShift = ctx->user_data_ddt_header.blockAlignmentShift;
1382 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
1383 ddt_header.tableShift = 0; // Secondary tables are single level
1384 ddt_header.entries = items_per_ddt_entry;
1385
1386 // Calculate data size
1387
1388 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
1389
1390 // Calculate CRC64 of the data
1391 crc64_context = aaruf_crc64_init();
1392 if(crc64_context == NULL)
1393 {
1394 FATAL("Could not initialize CRC64.");
1395 TRACE("Exiting set_ddt_multi_level_v2() = false");
1396 return false;
1397 }
1398
1399 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length);
1400
1401 aaruf_crc64_final(crc64_context, &crc64);
1402 ddt_header.crc64 = crc64;
1403
1404 uint8_t *cmp_buffer = NULL;
1405 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1406
1407 if(ddt_header.compression == None)
1408 {
1409
1410 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1411 ddt_header.cmpCrc64 = ddt_header.crc64;
1412 }
1413 else
1414 {
1415 cmp_buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
1416 if(cmp_buffer == NULL)
1417 {
1418 TRACE("Failed to allocate memory for secondary DDT v2 compression");
1419 return AARUF_ERROR_NOT_ENOUGH_MEMORY;
1420 }
1421
1422 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
1423 size_t props_size = LZMA_PROPERTIES_LENGTH;
1424 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
1425 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1426
1427 ddt_header.cmpLength = (uint32_t)dst_size;
1428
1429 if(ddt_header.cmpLength >= ddt_header.length)
1430 {
1431 ddt_header.compression = None;
1432 free(cmp_buffer);
1433
1434 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1435 }
1436 }
1437
1438 if(ddt_header.compression == None)
1439 {
1440 ddt_header.cmpLength = ddt_header.length;
1441 ddt_header.cmpCrc64 = ddt_header.crc64;
1442 }
1443 else
1444 ddt_header.cmpCrc64 = aaruf_crc64_data(cmp_buffer, (uint32_t)ddt_header.cmpLength);
1445
1446 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
1447
1448 // Write header
1449 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1450
1451 written_bytes = fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
1452 if(written_bytes != 1)
1453 {
1454 FATAL("Could not write DDT header to file.");
1455 TRACE("Exiting set_ddt_multi_level_v2() = false");
1456 return false;
1457 }
1458
1459 // Write data
1460 written_bytes = fwrite(cmp_buffer, ddt_header.cmpLength, 1, ctx->imageStream);
1461
1462 if(written_bytes != 1)
1463 {
1464 FATAL("Could not write DDT data to file.");
1465 TRACE("Exiting set_ddt_multi_level_v2() = false");
1466 return false;
1467 }
1468
1469 if(ddt_header.compression == Lzma) free(cmp_buffer);
1470
1471 // Update index: remove old entry and add new one for the evicted secondary DDT
1472 TRACE("Updating index for evicted secondary DDT");
1473
1474 // Remove old index entry for the cached DDT
1475 if(ctx->cached_ddt_offset != 0)
1476 {
1477 TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
1478 IndexEntry *entry = NULL;
1479
1480 // Find and remove the old index entry
1481 for(unsigned int i = 0; i < utarray_len(ctx->index_entries); i++)
1482 {
1483 entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
1484 if(entry && entry->offset == ctx->cached_ddt_offset && entry->blockType == DeDuplicationTableSecondary)
1485 {
1486 TRACE("Found old DDT index entry at position %u, removing", i);
1487 utarray_erase(ctx->index_entries, i, 1);
1488 break;
1489 }
1490 }
1491 }
1492
1493 // Add new index entry for the newly written secondary DDT
1494 IndexEntry new_ddt_entry;
1495 new_ddt_entry.blockType = DeDuplicationTableSecondary;
1496 new_ddt_entry.dataType = UserData;
1497 new_ddt_entry.offset = end_of_file;
1498
1499 utarray_push_back(ctx->index_entries, &new_ddt_entry);
1500 TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
1501
1502 // Step 4: Update the primary level table entry and flush it back to file
1503 uint64_t new_secondary_table_block_offset = end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
1504
1505 // Update the primary table entry to point to the new location of the secondary table
1506 // Use ddtPosition which was calculated from sectorAddress, not cachedDdtOffset
1507
1508 ctx->user_data_ddt2[ddt_position] = new_secondary_table_block_offset;
1509
1510 // Write the updated primary table back to its original position in the file
1511 long saved_pos = ftell(ctx->imageStream);
1512 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
1513
1514 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
1515
1516 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
1517
1518 if(written_bytes != 1)
1519 {
1520 FATAL("Could not flush primary DDT table to file.");
1521 TRACE("Exiting set_ddt_multi_level_v2() = false");
1522 return false;
1523 }
1524
1525 // Update nextBlockPosition to ensure future blocks don't overwrite the DDT
1526 uint64_t ddt_total_size = sizeof(DdtHeader2) + ddt_header.length;
1527 ctx->next_block_position = end_of_file + ddt_total_size + alignment_mask & ~alignment_mask;
1528 block_offset = ctx->next_block_position;
1529 offset = 0;
1530 TRACE("Updated nextBlockPosition after DDT write to %" PRIu64, ctx->next_block_position);
1531
1532 fseek(ctx->imageStream, saved_pos, SEEK_SET);
1533
1534 // Free the cached table
1535
1536 free(ctx->cached_secondary_ddt2);
1537 ctx->cached_secondary_ddt2 = NULL;
1538
1539 // Restore file position
1540 fseek(ctx->imageStream, current_pos, SEEK_SET);
1541 }
1542
1543 // Step 5: Check if the specified block already has an existing secondary level table
1544 create_new_table = ctx->cached_secondary_ddt2 == NULL;
1545
1546 if(!create_new_table && secondary_ddt_offset != 0)
1547 {
1548 // Load existing table
1549 fseek(ctx->imageStream, secondary_ddt_offset, SEEK_SET);
1550 size_t read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader2), ctx->imageStream);
1551
1552 if(read_bytes != sizeof(DdtHeader2) || ddt_header.identifier != DeDuplicationTable2 ||
1553 ddt_header.type != UserData)
1554 {
1555 FATAL("Invalid secondary DDT header at %" PRIu64, secondary_ddt_offset);
1556 TRACE("Exiting set_ddt_multi_level_v2() = false");
1557 return false;
1558 }
1559
1560 // Read the table data (assuming no compression for now)
1561 buffer = malloc(ddt_header.length);
1562 if(buffer == NULL)
1563 {
1564 FATAL("Cannot allocate memory for secondary DDT.");
1565 TRACE("Exiting set_ddt_multi_level_v2() = false");
1566 return false;
1567 }
1568
1569 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
1570 if(read_bytes != ddt_header.length)
1571 {
1572 FATAL("Could not read secondary DDT data.");
1573 free(buffer);
1574 TRACE("Exiting set_ddt_multi_level_v2() = false");
1575 return false;
1576 }
1577
1578 // Verify CRC
1579 crc64_context = aaruf_crc64_init();
1580 if(crc64_context == NULL)
1581 {
1582 FATAL("Could not initialize CRC64.");
1583 free(buffer);
1584 TRACE("Exiting set_ddt_multi_level_v2() = false");
1585 return false;
1586 }
1587
1588 aaruf_crc64_update(crc64_context, buffer, read_bytes);
1589 aaruf_crc64_final(crc64_context, &crc64);
1590
1591 if(crc64 != ddt_header.crc64)
1592 {
1593 FATAL("Secondary DDT CRC mismatch. Expected 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
1594 free(buffer);
1595 TRACE("Exiting set_ddt_multi_level_v2() = false");
1596 return false;
1597 }
1598
1599 // Cache the loaded table
1600
1601 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
1602
1603 ctx->cached_ddt_offset = secondary_ddt_offset;
1604 }
1605
1606 if(create_new_table)
1607 {
1608 // Create a new empty table
1609 size_t table_size = items_per_ddt_entry * sizeof(uint64_t);
1610
1611 buffer = calloc(1, table_size);
1612 if(buffer == NULL)
1613 {
1614 FATAL("Cannot allocate memory for new secondary DDT.");
1615 TRACE("Exiting set_ddt_multi_level_v2() = false");
1616 return false;
1617 }
1618
1619 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
1620
1621 ctx->cached_ddt_offset = 0; // Will be set when written to file
1622 ctx->cached_ddt_position = ddt_position; // Track which primary DDT position this new table belongs to
1623 TRACE("Created new secondary DDT for position %" PRIu64, ddt_position);
1624 }
1625
1626 // Step 6: Update the corresponding DDT entry
1627 if(*ddt_entry == 0)
1628 {
1629 block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1630 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1631 block_index << ctx->user_data_ddt_header.dataShift;
1632
1633 // Overflow detection for DDT entry
1634 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1635 {
1636 FATAL("DDT overflow: media does not fit in big DDT");
1637 TRACE("Exiting set_ddt_multi_level_v2() = false");
1638 return false;
1639 }
1640
1641 *ddt_entry |= (uint64_t)sector_status << 60;
1642 }
1643
1644 TRACE("Setting big secondary DDT entry %d to %ull", sector_address % items_per_ddt_entry, (uint64_t)*ddt_entry);
1645 ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry] = *ddt_entry;
1646
1647 TRACE("Updated secondary DDT entry at position %" PRIu64, sector_address % items_per_ddt_entry);
1648 TRACE("Exiting set_ddt_multi_level_v2() = true");
1649 return true;
1650}
1651
1768 bool set_ddt_tape(aaruformat_context *ctx, uint64_t sector_address, const uint64_t offset, const uint64_t block_offset,
1769 const uint8_t sector_status, uint64_t *ddt_entry)
1770{
1771 TRACE("Entering set_ddt_tape(%p, %" PRIu64 ", %llu, %llu, %d)", ctx, sector_address, offset, block_offset,
1772 sector_status);
1773
1774 // Check if the context and image stream are valid
1775 if(ctx == NULL || ctx->imageStream == NULL)
1776 {
1777 FATAL("Invalid context or image stream.");
1778 TRACE("Exiting set_ddt_tape() = false");
1779 return false;
1780 }
1781
1782 // Should not really be here
1783 if(!ctx->is_tape)
1784 {
1785 FATAL("Image is not tape, wrong function called.");
1786 TRACE("Exiting set_ddt_tape() = false");
1787 return false;
1788 }
1789
1790 if(*ddt_entry == 0)
1791 {
1792 const uint64_t block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1793 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1794 block_index << ctx->user_data_ddt_header.dataShift;
1795 // Overflow detection for DDT entry
1796 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1797 {
1798 FATAL("DDT overflow: media does not fit in big DDT");
1799 TRACE("Exiting set_ddt_tape() = false");
1800 return false;
1801 }
1802
1803 *ddt_entry |= (uint64_t)sector_status << 60;
1804 }
1805
1806 // Create DDT hash entry
1807 TapeDdtHashEntry *new_entry = calloc(1, sizeof(TapeDdtHashEntry));
1808 TapeDdtHashEntry *old_entry = NULL;
1809 if(new_entry == NULL)
1810 {
1811 FATAL("Cannot allocate memory for new tape DDT hash entry.");
1812 TRACE("Exiting set_ddt_tape() = false");
1813 return false;
1814 }
1815
1816 TRACE("Setting tape DDT entry %d to %u", sector_address, (uint32_t)*ddt_entry);
1817
1818 new_entry->key = sector_address;
1819 new_entry->value = *ddt_entry;
1820
1821 // Insert entry into tape DDT
1822 HASH_REPLACE(hh, ctx->tape_ddt, key, sizeof(uint64_t), new_entry, old_entry);
1823 if(old_entry) free(old_entry);
1824
1825 TRACE("Exiting set_ddt_tape() = true");
1826 return true;
1827}
aaruformat.h
LZMA_PROPERTIES_LENGTH
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
process_ddt_v2
int32_t process_ddt_v2(aaruformat_context *ctx, IndexEntry *entry, bool *found_user_data_ddt)
Processes a DDT v2 block from the image stream.
Definition ddt_v2.c:96
set_ddt_tape
bool set_ddt_tape(aaruformat_context *ctx, uint64_t sector_address, const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status, uint64_t *ddt_entry)
Sets a DDT entry for tape media using a hash-based lookup table.
Definition ddt_v2.c:1768
decode_ddt_single_level_v2
int32_t decode_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t *offset, uint64_t *block_offset, uint8_t *sector_status)
Decodes a single-level DDT v2 entry for a given sector address.
Definition ddt_v2.c:581
set_ddt_multi_level_v2
bool set_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t offset, uint64_t block_offset, uint8_t sector_status, uint64_t *ddt_entry)
Sets a multi-level DDT v2 entry for a given sector address.
Definition ddt_v2.c:1092
decode_ddt_multi_level_v2
int32_t decode_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t *offset, uint64_t *block_offset, uint8_t *sector_status)
Decodes a multi-level DDT v2 entry for a given sector address.
Definition ddt_v2.c:724
set_ddt_single_level_v2
bool set_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, const bool negative, const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status, uint64_t *ddt_entry)
Sets a single-level DDT v2 entry for a given sector address.
Definition ddt_v2.c:1023
set_ddt_entry_v2
bool set_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, bool negative, const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status, uint64_t *ddt_entry)
Sets a DDT v2 entry for a given sector address.
Definition ddt_v2.c:988
decode_ddt_entry_v2
int32_t decode_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, bool negative, uint64_t *offset, uint64_t *block_offset, uint8_t *sector_status)
Decodes a DDT v2 entry for a given sector address.
Definition ddt_v2.c:507
aaruf_lzma_encode_buffer
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
aaruf_crc64_data
uint64_t aaruf_crc64_data(const uint8_t *data, uint32_t len)
Definition crc64.c:160
aaruf_lzma_decode_buffer
int32_t aaruf_lzma_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t *src_size, const uint8_t *props, size_t props_size)
Decodes an LZMA-compressed buffer.
Definition lzma.c:39
aaruf_crc64_update
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
aaruf_crc64_init
crc64_ctx * aaruf_crc64_init()
Initializes a CRC64 context.
Definition crc64.c:32
aaruf_crc64_final
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
DeDuplicationTableSecondary
@ DeDuplicationTableSecondary
Block containing a secondary deduplication table (v2).
Definition enums.h:144
DeDuplicationTable2
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:143
SectorStatusNotDumped
@ SectorStatusNotDumped
Sector(s) not yet acquired during image dumping.
Definition enums.h:230
CdSectorPrefix
@ CdSectorPrefix
Compact Disc sector prefix (sync, header).
Definition enums.h:114
UserData
@ UserData
User (main) data.
Definition enums.h:46
CdSectorSuffix
@ CdSectorSuffix
Compact Disc sector suffix (EDC, ECC P, ECC Q).
Definition enums.h:115
Lzma
@ Lzma
LZMA compression.
Definition enums.h:34
None
@ None
Not compressed.
Definition enums.h:33
AARUF_STATUS_OK
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:75
AARUF_ERROR_NOT_ENOUGH_MEMORY
#define AARUF_ERROR_NOT_ENOUGH_MEMORY
Memory allocation failure (critical).
Definition errors.h:48
AARUF_ERROR_CANNOT_READ_BLOCK
#define AARUF_ERROR_CANNOT_READ_BLOCK
Generic block read failure (seek/read error).
Definition errors.h:46
AARUF_ERROR_INVALID_BLOCK_CRC
#define AARUF_ERROR_INVALID_BLOCK_CRC
CRC64 mismatch indicating corruption.
Definition errors.h:57
AARUF_ERROR_NOT_AARUFORMAT
#define AARUF_ERROR_NOT_AARUFORMAT
Input file/stream failed magic or structural validation.
Definition errors.h:40
AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK
#define AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK
Decompression routine failed or size mismatch.
Definition errors.h:56
internal.h
aaruf_close_current_block
int32_t aaruf_close_current_block(aaruformat_context *ctx)
Finalizes and writes the current data block to the AaruFormat image file.
Definition write.c:1393
log.h
FATAL
#define FATAL(fmt,...)
Definition log.h:40
TRACE
#define TRACE(fmt,...)
Definition log.h:25
DdtHeader2
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
DdtHeader2::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA of compressed table payload.
Definition ddt.h:161
DdtHeader2::type
uint16_t type
Data classification (DataType) for sectors referenced by this table.
Definition ddt.h:144
DdtHeader2::start
uint64_t start
Base internal index covered by this table (used for secondary tables; currently informational).
Definition ddt.h:153
DdtHeader2::overflow
uint16_t overflow
Trailing dumped sectors beyond user area (overflow range), still mapped with entries.
Definition ddt.h:151
DdtHeader2::crc64
uint64_t crc64
CRC64-ECMA of uncompressed table payload.
Definition ddt.h:162
DdtHeader2::entries
uint64_t entries
Number of entries contained in (uncompressed) table payload.
Definition ddt.h:158
DdtHeader2::levels
uint8_t levels
Total number of hierarchy levels (root depth); > 0.
Definition ddt.h:146
DdtHeader2::length
uint64_t length
Uncompressed payload size in bytes.
Definition ddt.h:160
DdtHeader2::identifier
uint32_t identifier
Block identifier, must be BlockType::DeDuplicationTable2.
Definition ddt.h:143
DdtHeader2::tableShift
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
DdtHeader2::blocks
uint64_t blocks
Total internal span (negative + usable + overflow) in logical sectors.
Definition ddt.h:150
DdtHeader2::negative
uint16_t negative
Leading negative LBA count; added to external L to build internal index.
Definition ddt.h:149
DdtHeader2::blockAlignmentShift
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
DdtHeader2::tableLevel
uint8_t tableLevel
Zero-based level index of this table (0 = root, increases downward).
Definition ddt.h:147
DdtHeader2::compression
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
DdtHeader2::dataShift
uint8_t dataShift
2^dataShift = sectors represented per increment in blockIndex field.
Definition ddt.h:155
DdtHeader2::cmpLength
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
DdtHeader2::previousLevelOffset
uint64_t previousLevelOffset
Absolute byte offset of the parent (previous) level table; 0 if root.
Definition ddt.h:148
ImageInfo::ImageSize
uint64_t ImageSize
Size of the image payload in bytes (excludes headers/metadata)
Definition aaru.h:873
ImageInfo::Sectors
uint64_t Sectors
Total count of addressable logical sectors/blocks.
Definition aaru.h:874
IndexEntry
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
IndexEntry::blockType
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
IndexEntry::offset
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
IndexEntry::dataType
uint16_t dataType
Data classification (value from DataType) or unused for untyped blocks.
Definition index.h:111
TapeDdtHashEntry
Definition context.h:141
TapeDdtHashEntry::key
uint64_t key
Key: sector address.
Definition context.h:142
TapeDdtHashEntry::value
uint64_t value
Value: DDT entry.
Definition context.h:143
aaruformat_context
Master context representing an open or in‑creation Aaru image.
Definition context.h:172
aaruformat_context::user_data_ddt_header
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:189
aaruformat_context::compression_enabled
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:299
aaruformat_context::user_data_ddt2
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:187
aaruformat_context::sector_suffix_ddt2
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:186
aaruformat_context::cached_ddt_offset
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:190
aaruformat_context::is_tape
bool is_tape
True if the image is a tape image.
Definition context.h:304
aaruformat_context::in_memory_ddt
bool in_memory_ddt
True if primary (and possibly secondary) DDT loaded.
Definition context.h:196
aaruformat_context::tape_ddt
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:182
aaruformat_context::ddt_version
int ddt_version
DDT version in use (1=legacy, 2=v2 hierarchical).
Definition context.h:194
aaruformat_context::writing_buffer
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:280
aaruformat_context::sector_prefix_ddt2
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:185
aaruformat_context::primary_ddt_offset
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:192
aaruformat_context::next_block_position
uint64_t next_block_position
Absolute file offset where next block will be written.
Definition context.h:282
aaruformat_context::cached_secondary_ddt2
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:188
aaruformat_context::imageStream
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:176
aaruformat_context::index_entries
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:252
aaruformat_context::image_info
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:260
aaruformat_context::lzma_dict_size
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:297
aaruformat_context::cached_ddt_position
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:191
crc64_ctx
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56