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");
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");
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");
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");
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,
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");
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");
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");
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");
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");
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");
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");
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,
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");
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");
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");
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");
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");
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");
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");
519 }
520
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");
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");
604 }
605
606 // Calculate positive or negative sector
607 if(negative)
608 sector_address = ctx->user_data_ddt_header.negative - sector_address;
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 =
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");
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");
756 }
757
758 // Calculate positive or negative sector
759 if(negative)
760 sector_address = ctx->user_data_ddt_header.negative - sector_address;
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");
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");
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");
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");
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);
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");
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");
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,
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");
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");
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");
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");
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");
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");
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");
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");
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");
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 =
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
988bool set_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, const bool negative,
989 const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status,
990 uint64_t *ddt_entry)
991{
992 TRACE("Entering set_ddt_entry_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative, offset,
993 block_offset, sector_status);
994
995 // Check if the context and image stream are valid
996 if(ctx == NULL || ctx->imageStream == NULL)
997 {
998 FATAL("Invalid context or image stream.");
999 return false;
1000 }
1001
1002 if(ctx->user_data_ddt_header.tableShift > 0)
1003 return set_ddt_multi_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status, ddt_entry);
1004
1005 return set_ddt_single_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status, ddt_entry);
1006}
1007
1024bool set_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, const bool negative,
1025 const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status,
1026 uint64_t *ddt_entry)
1027{
1028 TRACE("Entering set_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
1029 offset, block_offset, sector_status);
1030
1031 // Check if the context and image stream are valid
1032 if(ctx == NULL || ctx->imageStream == NULL)
1033 {
1034 FATAL("Invalid context or image stream.");
1035 TRACE("Exiting set_ddt_single_level_v2() = false");
1036 return false;
1037 }
1038
1039 // Should not really be here
1040 if(ctx->user_data_ddt_header.tableShift != 0)
1041 {
1042 FATAL("DDT table shift is not zero, but we are in single-level DDT setting.");
1043 TRACE("Exiting set_ddt_single_level_v2() = false");
1044 return false;
1045 }
1046
1047 // Calculate positive or negative sector
1048 if(negative)
1049 sector_address = ctx->user_data_ddt_header.negative - sector_address;
1050 else
1051 sector_address += ctx->user_data_ddt_header.negative;
1052
1053 if(*ddt_entry == 0)
1054 {
1055 const uint64_t block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1056 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1057 block_index << ctx->user_data_ddt_header.dataShift;
1058
1059 // Overflow detection for DDT entry
1060 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1061 {
1062 FATAL("DDT overflow: media does not fit in big DDT");
1063 TRACE("Exiting set_ddt_single_level_v2() = false");
1064 return false;
1065 }
1066 }
1067
1068 // Sector status can be different from previous deduplicated sector
1069 *ddt_entry &= 0x0FFFFFFFFFFFFFFF;
1070 *ddt_entry |= (uint64_t)sector_status << 60;
1071
1072 TRACE("Setting big single-level DDT entry %d to %ull", sector_address, (uint64_t)*ddt_entry);
1073 ctx->user_data_ddt2[sector_address] = *ddt_entry;
1074 ctx->dirty_single_level_ddt = true; // Mark single-level DDT as dirty
1075
1076 TRACE("Exiting set_ddt_single_level_v2() = true");
1077 return true;
1078}
1079
1096bool set_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t offset,
1097 uint64_t block_offset, uint8_t sector_status, uint64_t *ddt_entry)
1098{
1099 TRACE("Entering set_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %" PRIu64 ", %" PRIu64 ", %d)", ctx, sector_address,
1100 negative, offset, block_offset, sector_status);
1101
1102 uint64_t items_per_ddt_entry = 0;
1103 uint64_t ddt_position = 0;
1104 uint64_t secondary_ddt_offset = 0;
1105 uint64_t block_index = 0;
1106 uint8_t *buffer = NULL;
1107 crc64_ctx *crc64_context = NULL;
1108 uint64_t crc64 = 0;
1109 DdtHeader2 ddt_header;
1110 size_t written_bytes = 0;
1111 long end_of_file = 0;
1112 bool create_new_table = false;
1113
1114 // Check if the context and image stream are valid
1115 if(ctx == NULL || ctx->imageStream == NULL)
1116 {
1117 FATAL("Invalid context or image stream.");
1118 TRACE("Exiting set_ddt_multi_level_v2() = false");
1119 return false;
1120 }
1121
1122 // Should not really be here
1123 if(ctx->user_data_ddt_header.tableShift == 0)
1124 {
1125 FATAL("DDT table shift is zero, but we are in multi-level DDT setting.");
1126 TRACE("Exiting set_ddt_multi_level_v2() = false");
1127 return false;
1128 }
1129
1130 // Calculate positive or negative sector
1131 if(negative)
1132 sector_address = ctx->user_data_ddt_header.negative - sector_address;
1133 else
1134 sector_address += ctx->user_data_ddt_header.negative;
1135
1136 // Step 1: Calculate the corresponding secondary level table
1137 items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
1138 ddt_position = sector_address / items_per_ddt_entry;
1139 secondary_ddt_offset = ctx->user_data_ddt2[ddt_position];
1140
1141 // Position in file of the child DDT table
1142 secondary_ddt_offset *= 1 << ctx->user_data_ddt_header.blockAlignmentShift;
1143
1144 // Step 2: Check if it corresponds to the currently in-memory cached secondary level table
1145 if(ctx->cached_ddt_offset == secondary_ddt_offset && secondary_ddt_offset != 0)
1146 {
1147 // Update the corresponding DDT entry directly in the cached table
1148 if(*ddt_entry == 0)
1149 {
1150 block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1151 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1152 block_index << ctx->user_data_ddt_header.dataShift;
1153
1154 // Overflow detection for DDT entry
1155 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1156 {
1157 FATAL("DDT overflow: media does not fit in big DDT");
1158 TRACE("Exiting set_ddt_multi_level_v2() = false");
1159 return false;
1160 }
1161 }
1162
1163 // Sector status can be different from previous deduplicated sector
1164 *ddt_entry &= 0x0FFFFFFFFFFFFFFF;
1165 *ddt_entry |= (uint64_t)sector_status << 60;
1166
1167 TRACE("Setting small secondary DDT entry %d to %ull", sector_address % items_per_ddt_entry,
1168 (uint64_t)*ddt_entry);
1169 ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry] = *ddt_entry;
1170 ctx->dirty_secondary_ddt = true; // Mark secondary DDT as dirty
1171
1172 TRACE("Updated cached secondary DDT entry at position %" PRIu64, sector_address % items_per_ddt_entry);
1173 TRACE("Exiting set_ddt_multi_level_v2() = true");
1174 return true;
1175 }
1176
1177 // Step 2.5: Handle case where we have a cached secondary DDT that has never been written to disk
1178 // but does not contain the requested block
1179 if(ctx->cached_ddt_offset == 0 && (ctx->cached_secondary_ddt2 != NULL))
1180 {
1181 // Only write the cached table to disk if the requested block belongs to a different DDT position
1182 if(ddt_position != ctx->cached_ddt_position)
1183 {
1184 TRACE("Current secondary DDT in memory belongs to position %" PRIu64
1185 " but requested block needs position %" PRIu64,
1186 ctx->cached_ddt_position, ddt_position);
1187
1188 // Write the cached DDT to disk before proceeding with the new one
1189
1190 // Close the current data block first
1191 if(ctx->writing_buffer != NULL) aaruf_close_current_block(ctx);
1192
1193 // Get current position and seek to end of file
1194 fseek(ctx->imageStream, 0, SEEK_END);
1195 end_of_file = ftell(ctx->imageStream);
1196
1197 // Align to block boundary
1198 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1199 end_of_file = end_of_file + alignment_mask & ~alignment_mask;
1200 fseek(ctx->imageStream, end_of_file, SEEK_SET);
1201
1202 // Prepare DDT header for the never-written cached table
1203 memset(&ddt_header, 0, sizeof(DdtHeader2));
1205 ddt_header.type = UserData;
1206 ddt_header.compression = ctx->compression_enabled ? Lzma : None; // Use no compression for simplicity
1207 ddt_header.levels = ctx->user_data_ddt_header.levels;
1208 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
1209 ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
1210 ddt_header.negative = ctx->user_data_ddt_header.negative;
1211 ddt_header.blocks = items_per_ddt_entry;
1212 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
1213 ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry; // Use cached position with table shift
1215 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
1216 ddt_header.tableShift = 0; // Secondary tables are single level
1217 ddt_header.entries = items_per_ddt_entry;
1218
1219 // Calculate data size
1220
1221 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
1222
1223 // Calculate CRC64 of the data
1224 crc64_context = aaruf_crc64_init();
1225 if(crc64_context == NULL)
1226 {
1227 FATAL("Could not initialize CRC64.");
1228 TRACE("Exiting set_ddt_multi_level_v2() = false");
1229 return false;
1230 }
1231
1232 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
1233
1234 aaruf_crc64_final(crc64_context, &crc64);
1235 ddt_header.crc64 = crc64;
1236
1237 uint8_t *cmp_buffer = NULL;
1238 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1239
1240 if(ddt_header.compression == None)
1241 {
1242
1243 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1244 ddt_header.cmpCrc64 = ddt_header.crc64;
1245 }
1246 else
1247 {
1248 cmp_buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
1249 if(cmp_buffer == NULL)
1250 {
1251 TRACE("Failed to allocate memory for secondary DDT v2 compression");
1253 }
1254
1255 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
1256 size_t props_size = LZMA_PROPERTIES_LENGTH;
1257 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2,
1258 ddt_header.length, lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0,
1259 2, 273, 8);
1260
1261 ddt_header.cmpLength = (uint32_t)dst_size;
1262
1263 if(ddt_header.cmpLength >= ddt_header.length)
1264 {
1265 ddt_header.compression = None;
1266 free(cmp_buffer);
1267
1268 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1269 }
1270 }
1271
1272 if(ddt_header.compression == None)
1273 {
1274 ddt_header.cmpLength = ddt_header.length;
1275 ddt_header.cmpCrc64 = ddt_header.crc64;
1276 }
1277 else
1278 ddt_header.cmpCrc64 = aaruf_crc64_data(cmp_buffer, (uint32_t)ddt_header.cmpLength);
1279
1280 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
1281
1282 // Write header
1283 written_bytes = fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
1284 if(written_bytes != 1)
1285 {
1286 FATAL("Could not write never-written DDT header to file.");
1287 TRACE("Exiting set_ddt_multi_level_v2() = false");
1288 return false;
1289 }
1290
1291 // Write data
1292 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1293
1294 if(fwrite(cmp_buffer, ddt_header.cmpLength, 1, ctx->imageStream) != 1)
1295 {
1296 FATAL("Could not write never-written DDT data to file.");
1297 TRACE("Exiting set_ddt_multi_level_v2() = false");
1298 return false;
1299 }
1300
1301 if(ddt_header.compression == Lzma) free(cmp_buffer);
1302
1303 // Add index entry for the newly written secondary DDT
1304 IndexEntry new_ddt_entry;
1305 new_ddt_entry.blockType = DeDuplicationTableSecondary;
1306 new_ddt_entry.dataType = UserData;
1307 new_ddt_entry.offset = end_of_file;
1308
1309 utarray_push_back(ctx->index_entries, &new_ddt_entry);
1310 ctx->dirty_index_block = true;
1311 TRACE("Added new DDT index entry for never-written table at offset %" PRIu64, end_of_file);
1312
1313 // Update the primary level table entry to point to the new location of the secondary table
1314 uint64_t new_secondary_table_block_offset = end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
1315
1316 ctx->user_data_ddt2[ctx->cached_ddt_position] = new_secondary_table_block_offset;
1317 ctx->dirty_primary_ddt = true; // Mark primary DDT as dirty
1318
1319 // Write the updated primary table back to its original position in the file
1320 long saved_pos = ftell(ctx->imageStream);
1321 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
1322
1323 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
1324
1325 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
1326
1327 if(written_bytes != 1)
1328 {
1329 FATAL("Could not flush primary DDT table to file after writing never-written secondary table.");
1330 TRACE("Exiting set_ddt_multi_level_v2() = false");
1331 return false;
1332 }
1333
1334 // Update nextBlockPosition to ensure future blocks don't overwrite the DDT
1335 uint64_t ddt_total_size = sizeof(DdtHeader2) + ddt_header.length;
1336 ctx->next_block_position = end_of_file + ddt_total_size + alignment_mask & ~alignment_mask;
1337 block_offset = ctx->next_block_position;
1338 offset = 0;
1339 TRACE("Updated nextBlockPosition after never-written DDT write to %" PRIu64, ctx->next_block_position);
1340
1341 // Free the cached table
1342
1343 free(ctx->cached_secondary_ddt2);
1344 ctx->cached_secondary_ddt2 = NULL;
1345
1346 // Reset cached values since we've written and freed the table
1347 ctx->cached_ddt_offset = 0;
1348 ctx->cached_ddt_position = 0;
1349
1350 // Restore file position
1351 fseek(ctx->imageStream, saved_pos, SEEK_SET);
1352
1353 TRACE("Successfully wrote never-written cached secondary DDT to disk");
1354 }
1355 else
1356 // The cached DDT is actually for the requested block range, so we can use it directly
1357 TRACE("Cached DDT is for the correct block range, using it directly");
1358 // No need to write to disk, just continue with the cached table
1359 }
1360
1361 // Step 3: Write the currently in-memory cached secondary level table to the end of the file
1362 if(ctx->cached_ddt_offset != 0)
1363 {
1364 long current_pos = 0;
1365 // Close the current data block first
1366 if(ctx->writing_buffer != NULL) aaruf_close_current_block(ctx);
1367
1368 // Get current position and seek to end of file
1369 current_pos = ftell(ctx->imageStream);
1370 fseek(ctx->imageStream, 0, SEEK_END);
1371 end_of_file = ftell(ctx->imageStream);
1372
1373 // Align to block boundary
1374 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1375 end_of_file = end_of_file + alignment_mask & ~alignment_mask;
1376 fseek(ctx->imageStream, end_of_file, SEEK_SET);
1377
1378 // Prepare DDT header for the cached table
1379 memset(&ddt_header, 0, sizeof(DdtHeader2));
1381 ddt_header.type = UserData;
1382 ddt_header.compression = ctx->compression_enabled ? Lzma : None;
1383 ddt_header.levels = ctx->user_data_ddt_header.levels;
1384 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
1385 ddt_header.previousLevelOffset = ctx->primary_ddt_offset; // Set to primary DDT table location
1386 ddt_header.negative = ctx->user_data_ddt_header.negative;
1387 ddt_header.blocks = items_per_ddt_entry;
1388 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
1389 ddt_header.start = ddt_position * items_per_ddt_entry; // First block this DDT table references
1391 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
1392 ddt_header.tableShift = 0; // Secondary tables are single level
1393 ddt_header.entries = items_per_ddt_entry;
1394
1395 // Calculate data size
1396
1397 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
1398
1399 // Calculate CRC64 of the data
1400 crc64_context = aaruf_crc64_init();
1401 if(crc64_context == NULL)
1402 {
1403 FATAL("Could not initialize CRC64.");
1404 TRACE("Exiting set_ddt_multi_level_v2() = false");
1405 return false;
1406 }
1407
1408 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length);
1409
1410 aaruf_crc64_final(crc64_context, &crc64);
1411 ddt_header.crc64 = crc64;
1412
1413 uint8_t *cmp_buffer = NULL;
1414 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1415
1416 if(ddt_header.compression == None)
1417 {
1418
1419 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1420 ddt_header.cmpCrc64 = ddt_header.crc64;
1421 }
1422 else
1423 {
1424 cmp_buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
1425 if(cmp_buffer == NULL)
1426 {
1427 TRACE("Failed to allocate memory for secondary DDT v2 compression");
1429 }
1430
1431 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
1432 size_t props_size = LZMA_PROPERTIES_LENGTH;
1433 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
1434 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1435
1436 ddt_header.cmpLength = (uint32_t)dst_size;
1437
1438 if(ddt_header.cmpLength >= ddt_header.length)
1439 {
1440 ddt_header.compression = None;
1441 free(cmp_buffer);
1442
1443 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1444 }
1445 }
1446
1447 if(ddt_header.compression == None)
1448 {
1449 ddt_header.cmpLength = ddt_header.length;
1450 ddt_header.cmpCrc64 = ddt_header.crc64;
1451 }
1452 else
1453 ddt_header.cmpCrc64 = aaruf_crc64_data(cmp_buffer, (uint32_t)ddt_header.cmpLength);
1454
1455 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
1456
1457 // Write header
1458 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1459
1460 written_bytes = fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
1461 if(written_bytes != 1)
1462 {
1463 FATAL("Could not write DDT header to file.");
1464 TRACE("Exiting set_ddt_multi_level_v2() = false");
1465 return false;
1466 }
1467
1468 // Write data
1469 written_bytes = fwrite(cmp_buffer, ddt_header.cmpLength, 1, ctx->imageStream);
1470
1471 if(written_bytes != 1)
1472 {
1473 FATAL("Could not write DDT data to file.");
1474 TRACE("Exiting set_ddt_multi_level_v2() = false");
1475 return false;
1476 }
1477
1478 if(ddt_header.compression == Lzma) free(cmp_buffer);
1479
1480 // Update index: remove old entry and add new one for the evicted secondary DDT
1481 TRACE("Updating index for evicted secondary DDT");
1482
1483 // Remove old index entry for the cached DDT
1484 if(ctx->cached_ddt_offset != 0)
1485 {
1486 TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
1487 IndexEntry *entry = NULL;
1488
1489 // Find and remove the old index entry
1490 for(unsigned int i = 0; i < utarray_len(ctx->index_entries); i++)
1491 {
1492 entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
1493 if(entry && entry->offset == ctx->cached_ddt_offset && entry->blockType == DeDuplicationTableSecondary)
1494 {
1495 TRACE("Found old DDT index entry at position %u, removing", i);
1496 utarray_erase(ctx->index_entries, i, 1);
1497 break;
1498 }
1499 }
1500 }
1501
1502 // Add new index entry for the newly written secondary DDT
1503 IndexEntry new_ddt_entry;
1504 new_ddt_entry.blockType = DeDuplicationTableSecondary;
1505 new_ddt_entry.dataType = UserData;
1506 new_ddt_entry.offset = end_of_file;
1507
1508 utarray_push_back(ctx->index_entries, &new_ddt_entry);
1509 ctx->dirty_index_block = true;
1510 TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
1511
1512 // Step 4: Update the primary level table entry and flush it back to file
1513 uint64_t new_secondary_table_block_offset = end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
1514
1515 // Update the primary table entry to point to the new location of the secondary table
1516 // Use ddtPosition which was calculated from sectorAddress, not cachedDdtOffset
1517
1518 ctx->user_data_ddt2[ddt_position] = new_secondary_table_block_offset;
1519 ctx->dirty_primary_ddt = true; // Mark primary DDT as dirty
1520
1521 // Write the updated primary table back to its original position in the file
1522 long saved_pos = ftell(ctx->imageStream);
1523 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
1524
1525 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
1526
1527 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
1528
1529 if(written_bytes != 1)
1530 {
1531 FATAL("Could not flush primary DDT table to file.");
1532 TRACE("Exiting set_ddt_multi_level_v2() = false");
1533 return false;
1534 }
1535
1536 // Update nextBlockPosition to ensure future blocks don't overwrite the DDT
1537 uint64_t ddt_total_size = sizeof(DdtHeader2) + ddt_header.length;
1538 ctx->next_block_position = end_of_file + ddt_total_size + alignment_mask & ~alignment_mask;
1539 block_offset = ctx->next_block_position;
1540 offset = 0;
1541 TRACE("Updated nextBlockPosition after DDT write to %" PRIu64, ctx->next_block_position);
1542
1543 fseek(ctx->imageStream, saved_pos, SEEK_SET);
1544
1545 // Free the cached table
1546
1547 free(ctx->cached_secondary_ddt2);
1548 ctx->cached_secondary_ddt2 = NULL;
1549
1550 // Restore file position
1551 fseek(ctx->imageStream, current_pos, SEEK_SET);
1552 }
1553
1554 // Step 5: Check if the specified block already has an existing secondary level table
1555 create_new_table = ctx->cached_secondary_ddt2 == NULL;
1556
1557 if(!create_new_table && secondary_ddt_offset != 0)
1558 {
1559 // Load existing table
1560 fseek(ctx->imageStream, secondary_ddt_offset, SEEK_SET);
1561 size_t read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader2), ctx->imageStream);
1562
1563 if(read_bytes != sizeof(DdtHeader2) || ddt_header.identifier != DeDuplicationTable2 ||
1564 ddt_header.type != UserData)
1565 {
1566 FATAL("Invalid secondary DDT header at %" PRIu64, secondary_ddt_offset);
1567 TRACE("Exiting set_ddt_multi_level_v2() = false");
1568 return false;
1569 }
1570
1571 // Read the table data (assuming no compression for now)
1572 buffer = malloc(ddt_header.length);
1573 if(buffer == NULL)
1574 {
1575 FATAL("Cannot allocate memory for secondary DDT.");
1576 TRACE("Exiting set_ddt_multi_level_v2() = false");
1577 return false;
1578 }
1579
1580 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
1581 if(read_bytes != ddt_header.length)
1582 {
1583 FATAL("Could not read secondary DDT data.");
1584 free(buffer);
1585 TRACE("Exiting set_ddt_multi_level_v2() = false");
1586 return false;
1587 }
1588
1589 // Verify CRC
1590 crc64_context = aaruf_crc64_init();
1591 if(crc64_context == NULL)
1592 {
1593 FATAL("Could not initialize CRC64.");
1594 free(buffer);
1595 TRACE("Exiting set_ddt_multi_level_v2() = false");
1596 return false;
1597 }
1598
1599 aaruf_crc64_update(crc64_context, buffer, read_bytes);
1600 aaruf_crc64_final(crc64_context, &crc64);
1601
1602 if(crc64 != ddt_header.crc64)
1603 {
1604 FATAL("Secondary DDT CRC mismatch. Expected 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
1605 free(buffer);
1606 TRACE("Exiting set_ddt_multi_level_v2() = false");
1607 return false;
1608 }
1609
1610 // Cache the loaded table
1611
1612 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
1613
1614 ctx->cached_ddt_offset = secondary_ddt_offset;
1615 }
1616
1617 if(create_new_table)
1618 {
1619 // Create a new empty table
1620 size_t table_size = items_per_ddt_entry * sizeof(uint64_t);
1621
1622 buffer = calloc(1, table_size);
1623 if(buffer == NULL)
1624 {
1625 FATAL("Cannot allocate memory for new secondary DDT.");
1626 TRACE("Exiting set_ddt_multi_level_v2() = false");
1627 return false;
1628 }
1629
1630 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
1631
1632 ctx->cached_ddt_offset = 0; // Will be set when written to file
1633 ctx->cached_ddt_position = ddt_position; // Track which primary DDT position this new table belongs to
1634 TRACE("Created new secondary DDT for position %" PRIu64, ddt_position);
1635 }
1636
1637 // Step 6: Update the corresponding DDT entry
1638 if(*ddt_entry == 0)
1639 {
1640 block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1641 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1642 block_index << ctx->user_data_ddt_header.dataShift;
1643
1644 // Overflow detection for DDT entry
1645 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1646 {
1647 FATAL("DDT overflow: media does not fit in big DDT");
1648 TRACE("Exiting set_ddt_multi_level_v2() = false");
1649 return false;
1650 }
1651 }
1652
1653 // Sector status can be different from previous deduplicated sector
1654 *ddt_entry &= 0x0FFFFFFFFFFFFFFF;
1655 *ddt_entry |= (uint64_t)sector_status << 60;
1656
1657 TRACE("Setting big secondary DDT entry %d to %ull", sector_address % items_per_ddt_entry, (uint64_t)*ddt_entry);
1658 ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry] = *ddt_entry;
1659 ctx->dirty_secondary_ddt = true;
1660
1661 TRACE("Updated secondary DDT entry at position %" PRIu64, sector_address % items_per_ddt_entry);
1662 TRACE("Exiting set_ddt_multi_level_v2() = true");
1663 return true;
1664}
1665
1782bool set_ddt_tape(aaruformat_context *ctx, uint64_t sector_address, const uint64_t offset, const uint64_t block_offset,
1783 const uint8_t sector_status, uint64_t *ddt_entry)
1784{
1785 TRACE("Entering set_ddt_tape(%p, %" PRIu64 ", %llu, %llu, %d)", ctx, sector_address, offset, block_offset,
1786 sector_status);
1787
1788 // Check if the context and image stream are valid
1789 if(ctx == NULL || ctx->imageStream == NULL)
1790 {
1791 FATAL("Invalid context or image stream.");
1792 TRACE("Exiting set_ddt_tape() = false");
1793 return false;
1794 }
1795
1796 // Should not really be here
1797 if(!ctx->is_tape)
1798 {
1799 FATAL("Image is not tape, wrong function called.");
1800 TRACE("Exiting set_ddt_tape() = false");
1801 return false;
1802 }
1803
1804 if(*ddt_entry == 0)
1805 {
1806 const uint64_t block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1807 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1808 block_index << ctx->user_data_ddt_header.dataShift;
1809 // Overflow detection for DDT entry
1810 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1811 {
1812 FATAL("DDT overflow: media does not fit in big DDT");
1813 TRACE("Exiting set_ddt_tape() = false");
1814 return false;
1815 }
1816
1817 *ddt_entry |= (uint64_t)sector_status << 60;
1818 }
1819
1820 // Create DDT hash entry
1821 TapeDdtHashEntry *new_entry = calloc(1, sizeof(TapeDdtHashEntry));
1822 TapeDdtHashEntry *old_entry = NULL;
1823 if(new_entry == NULL)
1824 {
1825 FATAL("Cannot allocate memory for new tape DDT hash entry.");
1826 TRACE("Exiting set_ddt_tape() = false");
1827 return false;
1828 }
1829
1830 TRACE("Setting tape DDT entry %d to %u", sector_address, (uint32_t)*ddt_entry);
1831
1832 new_entry->key = sector_address;
1833 new_entry->value = *ddt_entry;
1834
1835 // Insert entry into tape DDT
1836 HASH_REPLACE(hh, ctx->tape_ddt, key, sizeof(uint64_t), new_entry, old_entry);
1837 ctx->dirty_tape_ddt = true; // Mark tape DDT as dirty
1838 if(old_entry) free(old_entry);
1839
1840 TRACE("Exiting set_ddt_tape() = true");
1841 return true;
1842}
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
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
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:1782
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
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:1096
bool set_ddt_entry_v2(aaruformat_context *ctx, const 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 DDT v2 entry for a given sector address.
Definition ddt_v2.c:988
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
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:1024
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
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
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
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
crc64_ctx * aaruf_crc64_init()
Initializes a CRC64 context.
Definition crc64.c:32
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
@ DeDuplicationTableSecondary
Block containing a secondary deduplication table (v2).
Definition enums.h:145
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:144
@ SectorStatusNotDumped
Sector(s) not yet acquired during image dumping.
Definition enums.h:231
@ CdSectorPrefix
Compact Disc sector prefix (sync, header).
Definition enums.h:114
@ UserData
User (main) data.
Definition enums.h:46
@ CdSectorSuffix
Compact Disc sector suffix (EDC, ECC P, ECC Q).
Definition enums.h:115
@ Lzma
LZMA compression.
Definition enums.h:34
@ None
Not compressed.
Definition enums.h:33
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:75
#define AARUF_ERROR_NOT_ENOUGH_MEMORY
Memory allocation failure (critical).
Definition errors.h:48
#define AARUF_ERROR_CANNOT_READ_BLOCK
Generic block read failure (seek/read error).
Definition errors.h:46
#define AARUF_ERROR_INVALID_BLOCK_CRC
CRC64 mismatch indicating corruption.
Definition errors.h:57
#define AARUF_ERROR_NOT_AARUFORMAT
Input file/stream failed magic or structural validation.
Definition errors.h:40
#define AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK
Decompression routine failed or size mismatch.
Definition errors.h:56
int32_t aaruf_close_current_block(aaruformat_context *ctx)
Finalizes and writes the current data block to the AaruFormat image file.
Definition write.c:1427
#define FATAL(fmt,...)
Definition log.h:40
#define TRACE(fmt,...)
Definition log.h:25
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
uint16_t overflow
Trailing dumped sectors beyond user area (overflow range), still mapped with entries.
Definition ddt.h:151
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
uint16_t negative
Leading negative LBA count; added to external L to build internal index.
Definition ddt.h:149
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
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
uint64_t ImageSize
Size of the image payload in bytes (excludes headers/metadata)
Definition aaru.h:925
uint64_t Sectors
Total count of addressable logical sectors/blocks.
Definition aaru.h:926
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
uint64_t key
Key: sector address.
Definition context.h:142
uint64_t value
Value: DDT entry.
Definition context.h:143
Master context representing an open or in‑creation Aaru image.
Definition context.h:172
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:189
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:300
bool dirty_primary_ddt
True if primary DDT table should be written during close.
Definition context.h:309
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:187
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:186
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:190
bool is_tape
True if the image is a tape image.
Definition context.h:305
bool in_memory_ddt
True if primary (and possibly secondary) DDT loaded.
Definition context.h:196
bool dirty_index_block
True if index block should be written during close.
Definition context.h:330
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:182
bool dirty_single_level_ddt
True if single-level DDT should be written during close.
Definition context.h:310
int ddt_version
DDT version in use (1=legacy, 2=v2 hierarchical).
Definition context.h:194
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:280
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:185
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:192
uint64_t next_block_position
Absolute file offset where next block will be written.
Definition context.h:282
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:188
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:176
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:252
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:260
bool dirty_secondary_ddt
True if secondary DDT tables should be written during close.
Definition context.h:308
bool dirty_tape_ddt
True if tape DDT should be written during close.
Definition context.h:322
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:298
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:191
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56