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-2026 Natalia Portillo.
4 *
5 * This library is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of the
8 * License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
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 aaruf_crc64_free(crc64_context);
239
240 if(crc64 != ddt_header.crc64)
241 {
242 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
243 free(buffer);
244 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
246 }
247
248 ctx->user_data_ddt2 = (uint64_t *)buffer;
249
250 ctx->in_memory_ddt = true;
251 *found_user_data_ddt = true;
252
253 break;
254 case None:
255 buffer = malloc(ddt_header.length);
256
257 if(buffer == NULL)
258 {
259 TRACE("Cannot allocate memory for DDT, continuing...");
260 break;
261 }
262
263 TRACE("Reading DDT of length %zu bytes", ddt_header.length);
264 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
265
266 if(read_bytes != ddt_header.length)
267 {
268 free(buffer);
269 FATAL("Could not read deduplication table, continuing...");
270 break;
271 }
272
273 crc64_context = aaruf_crc64_init();
274
275 if(crc64_context == NULL)
276 {
277 FATAL("Could not initialize CRC64.");
278 free(buffer);
279 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
281 }
282
283 aaruf_crc64_update(crc64_context, buffer, read_bytes);
284 aaruf_crc64_final(crc64_context, &crc64);
285 aaruf_crc64_free(crc64_context);
286
287 if(crc64 != ddt_header.crc64)
288 {
289 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
290 free(buffer);
291 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
293 }
294
295 ctx->user_data_ddt2 = (uint64_t *)buffer;
296
297 ctx->in_memory_ddt = true;
298 *found_user_data_ddt = true;
299
300 break;
301 default:
302 TRACE("Found unknown compression type %d, continuing...", ddt_header.compression);
303 *found_user_data_ddt = false;
304 break;
305 }
306 }
307 else if(entry->dataType == CdSectorPrefix || entry->dataType == CdSectorSuffix)
308 switch(ddt_header.compression)
309 {
310 case Lzma:
311 if(ddt_header.cmpLength <= LZMA_PROPERTIES_LENGTH)
312 {
313 FATAL("Compressed DDT payload too small (%" PRIu64 ") for LZMA properties.", ddt_header.cmpLength);
314 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
316 }
317
318 lzma_size = (size_t)(ddt_header.cmpLength - LZMA_PROPERTIES_LENGTH);
319
320 cmp_data = (uint8_t *)malloc(lzma_size);
321 if(cmp_data == NULL)
322 {
323 TRACE("Cannot allocate memory for DDT, continuing...");
324 break;
325 }
326
327 buffer = malloc(ddt_header.length);
328 if(buffer == NULL)
329 {
330 TRACE("Cannot allocate memory for DDT, continuing...");
331 free(cmp_data);
332 break;
333 }
334
335 read_bytes = fread(lzma_properties, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
336 if(read_bytes != LZMA_PROPERTIES_LENGTH)
337 {
338 TRACE("Could not read LZMA properties, continuing...");
339 free(cmp_data);
340 free(buffer);
341 break;
342 }
343
344 read_bytes = fread(cmp_data, 1, lzma_size, ctx->imageStream);
345 if(read_bytes != lzma_size)
346 {
347 TRACE("Could not read compressed block, continuing...");
348 free(cmp_data);
349 free(buffer);
350 break;
351 }
352
353 read_bytes = ddt_header.length;
354 TRACE("Decompressing block of size %zu bytes", ddt_header.length);
355 error_no = aaruf_lzma_decode_buffer(buffer, &read_bytes, cmp_data, &lzma_size, lzma_properties,
357
358 if(error_no != 0)
359 {
360 FATAL("Got error %d from LZMA, stopping...", error_no);
361 free(cmp_data);
362 free(buffer);
363 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
365 }
366
367 if(read_bytes != ddt_header.length)
368 {
369 FATAL("Error decompressing block, should be {0} bytes but got {1} bytes., stopping...");
370 free(cmp_data);
371 free(buffer);
372 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
374 }
375
376 free(cmp_data);
377 cmp_data = NULL;
378
379 crc64_context = aaruf_crc64_init();
380
381 if(crc64_context == NULL)
382 {
383 FATAL("Could not initialize CRC64.");
384 free(buffer);
385 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
387 }
388
389 aaruf_crc64_update(crc64_context, buffer, read_bytes);
390 aaruf_crc64_final(crc64_context, &crc64);
391 aaruf_crc64_free(crc64_context);
392
393 if(crc64 != ddt_header.crc64)
394 {
395 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
396 free(buffer);
397 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
399 }
400
401 if(entry->dataType == CdSectorPrefix)
402 ctx->sector_prefix_ddt2 = (uint64_t *)buffer;
403 else if(entry->dataType == CdSectorSuffix)
404 ctx->sector_suffix_ddt2 = (uint64_t *)buffer;
405 else
406 free(buffer);
407
408 break;
409
410 case None:
411 buffer = malloc(ddt_header.length);
412
413 if(buffer == NULL)
414 {
415 TRACE("Cannot allocate memory for deduplication table.");
416 break;
417 }
418
419 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
420
421 if(read_bytes != ddt_header.length)
422 {
423 free(buffer);
424 FATAL("Could not read deduplication table, continuing...");
425 break;
426 }
427
428 crc64_context = aaruf_crc64_init();
429
430 if(crc64_context == NULL)
431 {
432 FATAL("Could not initialize CRC64.");
433 free(buffer);
434 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
436 }
437
438 aaruf_crc64_update(crc64_context, buffer, read_bytes);
439 aaruf_crc64_final(crc64_context, &crc64);
440 aaruf_crc64_free(crc64_context);
441
442 if(crc64 != ddt_header.crc64)
443 {
444 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
445 free(buffer);
446 TRACE("Exiting process_ddt_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
448 }
449
450 if(entry->dataType == CdSectorPrefix)
451 ctx->sector_prefix_ddt2 = (uint64_t *)buffer;
452 else if(entry->dataType == CdSectorSuffix)
453 ctx->sector_suffix_ddt2 = (uint64_t *)buffer;
454 else
455 free(buffer);
456
457 break;
458 default:
459 TRACE("Found unknown compression type %d, continuing...", ddt_header.compression);
460 break;
461 }
462
463 TRACE("Exiting process_ddt_v2() = AARUF_STATUS_OK");
464 return AARUF_STATUS_OK;
465}
466
511int32_t decode_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, bool negative, uint64_t *offset,
512 uint64_t *block_offset, uint8_t *sector_status)
513{
514 TRACE("Entering decode_ddt_entry_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative, *offset,
515 *block_offset, *sector_status);
516 // Check if the context and image stream are valid
517 if(ctx == NULL || ctx->imageStream == NULL)
518 {
519 FATAL("Invalid context or image stream.");
520
521 TRACE("Exiting decode_ddt_entry_v2() = AARUF_ERROR_NOT_AARUFORMAT");
523 }
524
526 return decode_ddt_multi_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status);
527
528 return decode_ddt_single_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status);
529}
530
585int32_t decode_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t *offset,
586 uint64_t *block_offset, uint8_t *sector_status)
587{
588 TRACE("Entering decode_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
589 *offset, *block_offset, *sector_status);
590
591 uint64_t ddt_entry = 0;
592
593 // Check if the context and image stream are valid
594 if(ctx == NULL || ctx->imageStream == NULL)
595 {
596 FATAL("Invalid context or image stream.");
597
598 TRACE("Exiting decode_ddt_single_level_v2() = AARUF_ERROR_NOT_AARUFORMAT");
600 }
601
602 // Should not really be here
603 if(ctx->user_data_ddt_header.tableShift != 0)
604 {
605 FATAL("DDT table shift is not zero, but we are in single-level DDT decoding.");
606 TRACE("Exiting decode_ddt_single_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
608 }
609
610 // Calculate positive or negative sector
611 if(negative)
612 sector_address = ctx->user_data_ddt_header.negative - sector_address;
613 else
614 sector_address += ctx->user_data_ddt_header.negative;
615
616 ddt_entry = ctx->user_data_ddt2[sector_address];
617
618 if(ddt_entry == 0)
619 {
620 *sector_status = SectorStatusNotDumped;
621 *offset = 0;
622 *block_offset = 0;
623 TRACE("Exiting decode_ddt_single_level_v2(%p, %" PRIu64 ", %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
624 sector_address, *offset, *block_offset, *sector_status);
625 return AARUF_STATUS_OK;
626 }
627
628 *sector_status = ddt_entry >> 60;
629 ddt_entry &= 0xFFFFFFFFFFFFFFF;
630
631 const uint64_t offset_mask = (uint64_t)((1 << ctx->user_data_ddt_header.dataShift) - 1);
632 *offset = ddt_entry & offset_mask;
633 *block_offset =
635
636 TRACE("Exiting decode_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
637 sector_address, negative, *offset, *block_offset, *sector_status);
638 return AARUF_STATUS_OK;
639}
640
728int32_t decode_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t *offset,
729 uint64_t *block_offset, uint8_t *sector_status)
730{
731 TRACE("Entering decode_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
732 *offset, *block_offset, *sector_status);
733
734 uint64_t ddt_entry = 0;
735 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH];
736 size_t lzma_size = 0;
737 uint8_t *cmp_data = NULL;
738 uint8_t *buffer = NULL;
739 crc64_ctx *crc64_context = NULL;
740 uint64_t crc64 = 0;
741 int items_per_ddt_entry = 0;
742 uint64_t ddt_position = 0;
743 uint64_t secondary_ddt_offset = 0;
744
745 // Check if the context and image stream are valid
746 if(ctx == NULL || ctx->imageStream == NULL)
747 {
748 FATAL("Invalid context or image stream.");
749
750 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_NOT_AARUFORMAT");
752 }
753
754 // Should not really be here
755 if(ctx->user_data_ddt_header.tableShift == 0)
756 {
757 FATAL("DDT table shift is zero, but we are in multi-level DDT decoding.");
758 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
760 }
761
762 // Calculate positive or negative sector
763 if(negative)
764 sector_address = ctx->user_data_ddt_header.negative - sector_address;
765 else
766 sector_address += ctx->user_data_ddt_header.negative;
767
768 items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
769 ddt_position = sector_address / items_per_ddt_entry;
770 secondary_ddt_offset = ctx->user_data_ddt2[ddt_position];
771
772 // Position in file of the child DDT table
773 secondary_ddt_offset *= 1 << ctx->user_data_ddt_header.blockAlignmentShift;
774
775 // Is the one we have cached the same as the one we need to read?
776 if(ctx->cached_ddt_offset != secondary_ddt_offset)
777 {
778 int32_t error_no = 0;
779 fseek(ctx->imageStream, secondary_ddt_offset, SEEK_SET);
780 DdtHeader2 ddt_header;
781 size_t read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader2), ctx->imageStream);
782
783 if(read_bytes != sizeof(DdtHeader2))
784 {
785 FATAL("Could not read block header at %" PRIu64 "", secondary_ddt_offset);
786 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
788 }
789
790 if((ddt_header.identifier != DeDuplicationTableSecondary &&
791 ddt_header.identifier != DeDuplicationTableSAlpha) ||
792 ddt_header.type != UserData)
793 {
794 FATAL("Invalid block header at %" PRIu64 "", secondary_ddt_offset);
795 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
797 }
798
799 // Check for DDT compression
800 switch(ddt_header.compression)
801 {
802 case Lzma:
803 if(ddt_header.cmpLength <= LZMA_PROPERTIES_LENGTH)
804 {
805 FATAL("Compressed DDT payload too small (%" PRIu64 ") for LZMA properties.", ddt_header.cmpLength);
806 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
808 }
809
810 lzma_size = (size_t)(ddt_header.cmpLength - LZMA_PROPERTIES_LENGTH);
811
812 cmp_data = (uint8_t *)malloc(lzma_size);
813 if(cmp_data == NULL)
814 {
815 FATAL("Cannot allocate memory for DDT, stopping...");
816 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
818 }
819
820 buffer = malloc(ddt_header.length);
821 if(buffer == NULL)
822 {
823 FATAL("Cannot allocate memory for DDT, stopping...");
824 free(cmp_data);
826 }
827
828 read_bytes = fread(lzma_properties, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
829 if(read_bytes != LZMA_PROPERTIES_LENGTH)
830 {
831 FATAL("Could not read LZMA properties, stopping...");
832 free(cmp_data);
833 free(buffer);
834 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
836 }
837
838 read_bytes = fread(cmp_data, 1, lzma_size, ctx->imageStream);
839 if(read_bytes != lzma_size)
840 {
841 FATAL("Could not read compressed block, stopping...");
842 free(cmp_data);
843 free(buffer);
844 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
846 }
847
848 TRACE("Decompressing block of size %zu bytes", ddt_header.length);
849 read_bytes = ddt_header.length;
850 error_no = aaruf_lzma_decode_buffer(buffer, &read_bytes, cmp_data, &lzma_size, lzma_properties,
852
853 if(error_no != 0)
854 {
855 FATAL("Got error %d from LZMA, stopping...", error_no);
856 free(cmp_data);
857 free(buffer);
858 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
860 }
861
862 if(read_bytes != ddt_header.length)
863 {
864 FATAL("Error decompressing block, should be {0} bytes but got {1} bytes., stopping...");
865 free(cmp_data);
866 free(buffer);
867 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
869 }
870
871 free(cmp_data);
872
873 crc64_context = aaruf_crc64_init();
874
875 if(crc64_context == NULL)
876 {
877 FATAL("Could not initialize CRC64.");
878 free(buffer);
879 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
881 }
882
883 aaruf_crc64_update(crc64_context, buffer, read_bytes);
884 aaruf_crc64_final(crc64_context, &crc64);
885 aaruf_crc64_free(crc64_context);
886
887 if(crc64 != ddt_header.crc64)
888 {
889 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
890 free(buffer);
891 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
893 }
894
895 // Free old cached DDT before replacing it
896 free(ctx->cached_secondary_ddt2);
897
898 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
899
900 ctx->cached_ddt_offset = secondary_ddt_offset;
901
902 break;
903 case None:
904 buffer = malloc(ddt_header.length);
905
906 if(buffer == NULL)
907 {
908 FATAL("Cannot allocate memory for DDT, stopping...");
909 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
911 }
912
913 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
914
915 if(read_bytes != ddt_header.length)
916 {
917 free(buffer);
918 FATAL("Could not read deduplication table, stopping...");
919 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
921 }
922
923 crc64_context = aaruf_crc64_init();
924
925 if(crc64_context == NULL)
926 {
927 FATAL("Could not initialize CRC64.");
928 free(buffer);
929 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
931 }
932
933 aaruf_crc64_update(crc64_context, buffer, read_bytes);
934 aaruf_crc64_final(crc64_context, &crc64);
935 aaruf_crc64_free(crc64_context);
936
937 if(crc64 != ddt_header.crc64)
938 {
939 FATAL("Expected DDT CRC 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
940 free(buffer);
941 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
943 }
944
945 // Free old cached DDT before replacing it
946 free(ctx->cached_secondary_ddt2);
947
948 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
949
950 ctx->cached_ddt_offset = secondary_ddt_offset;
951
952 break;
953 default:
954 FATAL("Found unknown compression type %d, stopping...", ddt_header.compression);
955 TRACE("Exiting decode_ddt_multi_level_v2() = AARUF_ERROR_CANNOT_READ_BLOCK");
957 }
958 }
959
960 ddt_entry = ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry];
961
962 if(ddt_entry == 0)
963 {
964 *sector_status = SectorStatusNotDumped;
965 *offset = 0;
966 *block_offset = 0;
967
968 TRACE("Exiting decode_ddt_multi_level_v2(%p, %" PRIu64 ", %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
969 sector_address, *offset, *block_offset, *sector_status);
970 return AARUF_STATUS_OK;
971 }
972
973 *sector_status = ddt_entry >> 60;
974 ddt_entry &= 0x0FFFFFFFFFFFFFFF;
975
976 const uint64_t offset_mask = (uint64_t)((1 << ctx->user_data_ddt_header.dataShift) - 1);
977 *offset = ddt_entry & offset_mask;
978 *block_offset =
980
981 TRACE("Exiting decode_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d) = AARUF_STATUS_OK", ctx,
982 sector_address, negative, *offset, *block_offset, *sector_status);
983 return AARUF_STATUS_OK;
984}
985
1002bool set_ddt_entry_v2(aaruformat_context *ctx, const uint64_t sector_address, const bool negative,
1003 const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status,
1004 uint64_t *ddt_entry)
1005{
1006 TRACE("Entering set_ddt_entry_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative, offset,
1007 block_offset, sector_status);
1008
1009 // Check if the context and image stream are valid
1010 if(ctx == NULL || ctx->imageStream == NULL)
1011 {
1012 FATAL("Invalid context or image stream.");
1013 return false;
1014 }
1015
1016 if(ctx->user_data_ddt_header.tableShift > 0)
1017 return set_ddt_multi_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status, ddt_entry);
1018
1019 return set_ddt_single_level_v2(ctx, sector_address, negative, offset, block_offset, sector_status, ddt_entry);
1020}
1021
1038bool set_ddt_single_level_v2(aaruformat_context *ctx, uint64_t sector_address, const bool negative,
1039 const uint64_t offset, const uint64_t block_offset, const uint8_t sector_status,
1040 uint64_t *ddt_entry)
1041{
1042 TRACE("Entering set_ddt_single_level_v2(%p, %" PRIu64 ", %d, %llu, %llu, %d)", ctx, sector_address, negative,
1043 offset, block_offset, sector_status);
1044
1045 // Check if the context and image stream are valid
1046 if(ctx == NULL || ctx->imageStream == NULL)
1047 {
1048 FATAL("Invalid context or image stream.");
1049 TRACE("Exiting set_ddt_single_level_v2() = false");
1050 return false;
1051 }
1052
1053 // Should not really be here
1054 if(ctx->user_data_ddt_header.tableShift != 0)
1055 {
1056 FATAL("DDT table shift is not zero, but we are in single-level DDT setting.");
1057 TRACE("Exiting set_ddt_single_level_v2() = false");
1058 return false;
1059 }
1060
1061 // Calculate positive or negative sector
1062 if(negative)
1063 sector_address = ctx->user_data_ddt_header.negative - sector_address;
1064 else
1065 sector_address += ctx->user_data_ddt_header.negative;
1066
1067 if(*ddt_entry == 0)
1068 {
1069 const uint64_t block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1070 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1071 block_index << ctx->user_data_ddt_header.dataShift;
1072
1073 // Overflow detection for DDT entry
1074 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1075 {
1076 FATAL("DDT overflow: media does not fit in big DDT");
1077 TRACE("Exiting set_ddt_single_level_v2() = false");
1078 return false;
1079 }
1080 }
1081
1082 // Sector status can be different from previous deduplicated sector
1083 *ddt_entry &= 0x0FFFFFFFFFFFFFFF;
1084 *ddt_entry |= (uint64_t)sector_status << 60;
1085
1086 TRACE("Setting big single-level DDT entry %d to %ull", sector_address, (uint64_t)*ddt_entry);
1087 ctx->user_data_ddt2[sector_address] = *ddt_entry;
1088 ctx->dirty_single_level_ddt = true; // Mark single-level DDT as dirty
1089
1090 TRACE("Exiting set_ddt_single_level_v2() = true");
1091 return true;
1092}
1093
1110bool set_ddt_multi_level_v2(aaruformat_context *ctx, uint64_t sector_address, bool negative, uint64_t offset,
1111 uint64_t block_offset, uint8_t sector_status, uint64_t *ddt_entry)
1112{
1113 TRACE("Entering set_ddt_multi_level_v2(%p, %" PRIu64 ", %d, %" PRIu64 ", %" PRIu64 ", %d)", ctx, sector_address,
1114 negative, offset, block_offset, sector_status);
1115
1116 uint64_t items_per_ddt_entry = 0;
1117 uint64_t ddt_position = 0;
1118 uint64_t secondary_ddt_offset = 0;
1119 uint64_t block_index = 0;
1120 uint8_t *buffer = NULL;
1121 crc64_ctx *crc64_context = NULL;
1122 uint64_t crc64 = 0;
1123 DdtHeader2 ddt_header;
1124 size_t written_bytes = 0;
1125 long end_of_file = 0;
1126 bool create_new_table = false;
1127
1128 // Check if the context and image stream are valid
1129 if(ctx == NULL || ctx->imageStream == NULL)
1130 {
1131 FATAL("Invalid context or image stream.");
1132 TRACE("Exiting set_ddt_multi_level_v2() = false");
1133 return false;
1134 }
1135
1136 // Should not really be here
1137 if(ctx->user_data_ddt_header.tableShift == 0)
1138 {
1139 FATAL("DDT table shift is zero, but we are in multi-level DDT setting.");
1140 TRACE("Exiting set_ddt_multi_level_v2() = false");
1141 return false;
1142 }
1143
1144 // Calculate positive or negative sector
1145 if(negative)
1146 sector_address = ctx->user_data_ddt_header.negative - sector_address;
1147 else
1148 sector_address += ctx->user_data_ddt_header.negative;
1149
1150 // Step 1: Calculate the corresponding secondary level table
1151 items_per_ddt_entry = 1 << ctx->user_data_ddt_header.tableShift;
1152 ddt_position = sector_address / items_per_ddt_entry;
1153 secondary_ddt_offset = ctx->user_data_ddt2[ddt_position];
1154
1155 // Position in file of the child DDT table
1156 secondary_ddt_offset *= 1 << ctx->user_data_ddt_header.blockAlignmentShift;
1157
1158 // Step 2: Check if it corresponds to the currently in-memory cached secondary level table
1159 if(ctx->cached_ddt_offset == secondary_ddt_offset && secondary_ddt_offset != 0)
1160 {
1161 // Update the corresponding DDT entry directly in the cached table
1162 if(*ddt_entry == 0)
1163 {
1164 block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1165 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1166 block_index << ctx->user_data_ddt_header.dataShift;
1167
1168 // Overflow detection for DDT entry
1169 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1170 {
1171 FATAL("DDT overflow: media does not fit in big DDT");
1172 TRACE("Exiting set_ddt_multi_level_v2() = false");
1173 return false;
1174 }
1175 }
1176
1177 // Sector status can be different from previous deduplicated sector
1178 *ddt_entry &= 0x0FFFFFFFFFFFFFFF;
1179 *ddt_entry |= (uint64_t)sector_status << 60;
1180
1181 TRACE("Setting small secondary DDT entry %d to %ull", sector_address % items_per_ddt_entry,
1182 (uint64_t)*ddt_entry);
1183 ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry] = *ddt_entry;
1184 ctx->dirty_secondary_ddt = true; // Mark secondary DDT as dirty
1185
1186 TRACE("Updated cached secondary DDT entry at position %" PRIu64, sector_address % items_per_ddt_entry);
1187 TRACE("Exiting set_ddt_multi_level_v2() = true");
1188 return true;
1189 }
1190
1191 // Step 2.5: Handle case where we have a cached secondary DDT that has never been written to disk
1192 // but does not contain the requested block
1193 if(ctx->cached_ddt_offset == 0 && (ctx->cached_secondary_ddt2 != NULL))
1194 {
1195 // Only write the cached table to disk if the requested block belongs to a different DDT position
1196 if(ddt_position != ctx->cached_ddt_position)
1197 {
1198 TRACE("Current secondary DDT in memory belongs to position %" PRIu64
1199 " but requested block needs position %" PRIu64,
1200 ctx->cached_ddt_position, ddt_position);
1201
1202 // Write the cached DDT to disk before proceeding with the new one
1203
1204 // Close the current data block first
1205 if(ctx->writing_buffer != NULL) aaruf_close_current_block(ctx);
1206
1207 // Get current position and seek to end of file
1208 fseek(ctx->imageStream, 0, SEEK_END);
1209 end_of_file = ftell(ctx->imageStream);
1210
1211 // Align to block boundary
1212 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1213 end_of_file = end_of_file + alignment_mask & ~alignment_mask;
1214 fseek(ctx->imageStream, end_of_file, SEEK_SET);
1215
1216 // Prepare DDT header for the never-written cached table
1217 memset(&ddt_header, 0, sizeof(DdtHeader2));
1219 ddt_header.type = UserData;
1220 ddt_header.compression = ctx->compression_enabled ? Lzma : None; // Use no compression for simplicity
1221 ddt_header.levels = ctx->user_data_ddt_header.levels;
1222 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
1223 ddt_header.previousLevelOffset = ctx->primary_ddt_offset;
1224 ddt_header.negative = ctx->user_data_ddt_header.negative;
1225 ddt_header.blocks = items_per_ddt_entry;
1226 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
1227 ddt_header.start = ctx->cached_ddt_position * items_per_ddt_entry; // Use cached position with table shift
1229 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
1230 ddt_header.tableShift = 0; // Secondary tables are single level
1231 ddt_header.entries = items_per_ddt_entry;
1232
1233 // Calculate data size
1234
1235 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
1236
1237 // Calculate CRC64 of the data
1238 crc64_context = aaruf_crc64_init();
1239 if(crc64_context == NULL)
1240 {
1241 FATAL("Could not initialize CRC64.");
1242 TRACE("Exiting set_ddt_multi_level_v2() = false");
1243 return false;
1244 }
1245
1246 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, (uint32_t)ddt_header.length);
1247
1248 aaruf_crc64_final(crc64_context, &crc64);
1249 aaruf_crc64_free(crc64_context);
1250 ddt_header.crc64 = crc64;
1251
1252 uint8_t *cmp_buffer = NULL;
1253 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1254
1255 if(ddt_header.compression == None)
1256 {
1257
1258 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1259 ddt_header.cmpCrc64 = ddt_header.crc64;
1260 }
1261 else
1262 {
1263 cmp_buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
1264 if(cmp_buffer == NULL)
1265 {
1266 TRACE("Failed to allocate memory for secondary DDT v2 compression");
1268 }
1269
1270 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
1271 size_t props_size = LZMA_PROPERTIES_LENGTH;
1272 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2,
1273 ddt_header.length, lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0,
1274 2, 273, 8);
1275
1276 ddt_header.cmpLength = (uint32_t)dst_size;
1277
1278 if(ddt_header.cmpLength >= ddt_header.length)
1279 {
1280 ddt_header.compression = None;
1281 free(cmp_buffer);
1282
1283 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1284 }
1285 }
1286
1287 if(ddt_header.compression == None)
1288 {
1289 ddt_header.cmpLength = ddt_header.length;
1290 ddt_header.cmpCrc64 = ddt_header.crc64;
1291 }
1292 else
1293 ddt_header.cmpCrc64 = aaruf_crc64_data(cmp_buffer, (uint32_t)ddt_header.cmpLength);
1294
1295 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
1296
1297 // Write header
1298 written_bytes = fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
1299 if(written_bytes != 1)
1300 {
1301 FATAL("Could not write never-written DDT header to file.");
1302 TRACE("Exiting set_ddt_multi_level_v2() = false");
1303 return false;
1304 }
1305
1306 // Write data
1307 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1308
1309 if(fwrite(cmp_buffer, ddt_header.cmpLength, 1, ctx->imageStream) != 1)
1310 {
1311 FATAL("Could not write never-written DDT data to file.");
1312 TRACE("Exiting set_ddt_multi_level_v2() = false");
1313 return false;
1314 }
1315
1316 if(ddt_header.compression == Lzma) free(cmp_buffer);
1317
1318 // Add index entry for the newly written secondary DDT
1319 IndexEntry new_ddt_entry;
1320 new_ddt_entry.blockType = DeDuplicationTableSecondary;
1321 new_ddt_entry.dataType = UserData;
1322 new_ddt_entry.offset = end_of_file;
1323
1324 utarray_push_back(ctx->index_entries, &new_ddt_entry);
1325 ctx->dirty_index_block = true;
1326 TRACE("Added new DDT index entry for never-written table at offset %" PRIu64, end_of_file);
1327
1328 // Update the primary level table entry to point to the new location of the secondary table
1329 uint64_t new_secondary_table_block_offset = end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
1330
1331 ctx->user_data_ddt2[ctx->cached_ddt_position] = new_secondary_table_block_offset;
1332 ctx->dirty_primary_ddt = true; // Mark primary DDT as dirty
1333
1334 // Write the updated primary table back to its original position in the file
1335 long saved_pos = ftell(ctx->imageStream);
1336 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
1337
1338 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
1339
1340 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
1341
1342 if(written_bytes != 1)
1343 {
1344 FATAL("Could not flush primary DDT table to file after writing never-written secondary table.");
1345 TRACE("Exiting set_ddt_multi_level_v2() = false");
1346 return false;
1347 }
1348
1349 // Update nextBlockPosition to ensure future blocks don't overwrite the DDT
1350 uint64_t ddt_total_size = sizeof(DdtHeader2) + ddt_header.length;
1351 ctx->next_block_position = end_of_file + ddt_total_size + alignment_mask & ~alignment_mask;
1352 block_offset = ctx->next_block_position;
1353 offset = 0;
1354 TRACE("Updated nextBlockPosition after never-written DDT write to %" PRIu64, ctx->next_block_position);
1355
1356 // Free the cached table
1357
1358 free(ctx->cached_secondary_ddt2);
1359 ctx->cached_secondary_ddt2 = NULL;
1360
1361 // Reset cached values since we've written and freed the table
1362 ctx->cached_ddt_offset = 0;
1363 ctx->cached_ddt_position = 0;
1364
1365 // Restore file position
1366 fseek(ctx->imageStream, saved_pos, SEEK_SET);
1367
1368 TRACE("Successfully wrote never-written cached secondary DDT to disk");
1369 }
1370 else
1371 // The cached DDT is actually for the requested block range, so we can use it directly
1372 TRACE("Cached DDT is for the correct block range, using it directly");
1373 // No need to write to disk, just continue with the cached table
1374 }
1375
1376 // Step 3: Write the currently in-memory cached secondary level table to the end of the file
1377 if(ctx->cached_ddt_offset != 0)
1378 {
1379 long current_pos = 0;
1380 // Close the current data block first
1381 if(ctx->writing_buffer != NULL) aaruf_close_current_block(ctx);
1382
1383 // Get current position and seek to end of file
1384 current_pos = ftell(ctx->imageStream);
1385 fseek(ctx->imageStream, 0, SEEK_END);
1386 end_of_file = ftell(ctx->imageStream);
1387
1388 // Align to block boundary
1389 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
1390 end_of_file = end_of_file + alignment_mask & ~alignment_mask;
1391 fseek(ctx->imageStream, end_of_file, SEEK_SET);
1392
1393 // Prepare DDT header for the cached table
1394 memset(&ddt_header, 0, sizeof(DdtHeader2));
1396 ddt_header.type = UserData;
1397 ddt_header.compression = ctx->compression_enabled ? Lzma : None;
1398 ddt_header.levels = ctx->user_data_ddt_header.levels;
1399 ddt_header.tableLevel = ctx->user_data_ddt_header.tableLevel + 1;
1400 ddt_header.previousLevelOffset = ctx->primary_ddt_offset; // Set to primary DDT table location
1401 ddt_header.negative = ctx->user_data_ddt_header.negative;
1402 ddt_header.blocks = items_per_ddt_entry;
1403 ddt_header.overflow = ctx->user_data_ddt_header.overflow;
1404 ddt_header.start = ddt_position * items_per_ddt_entry; // First block this DDT table references
1406 ddt_header.dataShift = ctx->user_data_ddt_header.dataShift;
1407 ddt_header.tableShift = 0; // Secondary tables are single level
1408 ddt_header.entries = items_per_ddt_entry;
1409
1410 // Calculate data size
1411
1412 ddt_header.length = items_per_ddt_entry * sizeof(uint64_t);
1413
1414 // Calculate CRC64 of the data
1415 crc64_context = aaruf_crc64_init();
1416 if(crc64_context == NULL)
1417 {
1418 FATAL("Could not initialize CRC64.");
1419 TRACE("Exiting set_ddt_multi_level_v2() = false");
1420 return false;
1421 }
1422
1423 aaruf_crc64_update(crc64_context, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length);
1424
1425 aaruf_crc64_final(crc64_context, &crc64);
1426 aaruf_crc64_free(crc64_context);
1427 ddt_header.crc64 = crc64;
1428
1429 uint8_t *cmp_buffer = NULL;
1430 uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
1431
1432 if(ddt_header.compression == None)
1433 {
1434
1435 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1436 ddt_header.cmpCrc64 = ddt_header.crc64;
1437 }
1438 else
1439 {
1440 cmp_buffer = malloc((size_t)ddt_header.length * 2); // Allocate double size for compression
1441 if(cmp_buffer == NULL)
1442 {
1443 TRACE("Failed to allocate memory for secondary DDT v2 compression");
1445 }
1446
1447 size_t dst_size = (size_t)ddt_header.length * 2 * 2;
1448 size_t props_size = LZMA_PROPERTIES_LENGTH;
1449 aaruf_lzma_encode_buffer(cmp_buffer, &dst_size, (uint8_t *)ctx->cached_secondary_ddt2, ddt_header.length,
1450 lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8);
1451
1452 ddt_header.cmpLength = (uint32_t)dst_size;
1453
1454 if(ddt_header.cmpLength >= ddt_header.length)
1455 {
1456 ddt_header.compression = None;
1457 free(cmp_buffer);
1458
1459 cmp_buffer = (uint8_t *)ctx->cached_secondary_ddt2;
1460 }
1461 }
1462
1463 if(ddt_header.compression == None)
1464 {
1465 ddt_header.cmpLength = ddt_header.length;
1466 ddt_header.cmpCrc64 = ddt_header.crc64;
1467 }
1468 else
1469 ddt_header.cmpCrc64 = aaruf_crc64_data(cmp_buffer, (uint32_t)ddt_header.cmpLength);
1470
1471 if(ddt_header.compression == Lzma) ddt_header.cmpLength += LZMA_PROPERTIES_LENGTH;
1472
1473 // Write header
1474 if(ddt_header.compression == Lzma) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
1475
1476 written_bytes = fwrite(&ddt_header, sizeof(DdtHeader2), 1, ctx->imageStream);
1477 if(written_bytes != 1)
1478 {
1479 FATAL("Could not write DDT header to file.");
1480 TRACE("Exiting set_ddt_multi_level_v2() = false");
1481 return false;
1482 }
1483
1484 // Write data
1485 written_bytes = fwrite(cmp_buffer, ddt_header.cmpLength, 1, ctx->imageStream);
1486
1487 if(written_bytes != 1)
1488 {
1489 FATAL("Could not write DDT data to file.");
1490 TRACE("Exiting set_ddt_multi_level_v2() = false");
1491 return false;
1492 }
1493
1494 if(ddt_header.compression == Lzma) free(cmp_buffer);
1495
1496 // Update index: remove old entry and add new one for the evicted secondary DDT
1497 TRACE("Updating index for evicted secondary DDT");
1498
1499 // Remove old index entry for the cached DDT
1500 if(ctx->cached_ddt_offset != 0)
1501 {
1502 TRACE("Removing old index entry for DDT at offset %" PRIu64, ctx->cached_ddt_offset);
1503 IndexEntry *entry = NULL;
1504
1505 // Find and remove the old index entry
1506 for(unsigned int i = 0; i < utarray_len(ctx->index_entries); i++)
1507 {
1508 entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
1509 if(entry && entry->offset == ctx->cached_ddt_offset &&
1511 {
1512 TRACE("Found old DDT index entry at position %u, removing", i);
1513 utarray_erase(ctx->index_entries, i, 1);
1514 break;
1515 }
1516 }
1517 }
1518
1519 // Add new index entry for the newly written secondary DDT
1520 IndexEntry new_ddt_entry;
1521 new_ddt_entry.blockType = DeDuplicationTableSecondary;
1522 new_ddt_entry.dataType = UserData;
1523 new_ddt_entry.offset = end_of_file;
1524
1525 utarray_push_back(ctx->index_entries, &new_ddt_entry);
1526 ctx->dirty_index_block = true;
1527 TRACE("Added new DDT index entry at offset %" PRIu64, end_of_file);
1528
1529 // Step 4: Update the primary level table entry and flush it back to file
1530 uint64_t new_secondary_table_block_offset = end_of_file >> ctx->user_data_ddt_header.blockAlignmentShift;
1531
1532 // Update the primary table entry to point to the new location of the secondary table
1533 // Use ddtPosition which was calculated from sectorAddress, not cachedDdtOffset
1534
1535 ctx->user_data_ddt2[ddt_position] = new_secondary_table_block_offset;
1536 ctx->dirty_primary_ddt = true; // Mark primary DDT as dirty
1537
1538 // Write the updated primary table back to its original position in the file
1539 long saved_pos = ftell(ctx->imageStream);
1540 fseek(ctx->imageStream, ctx->primary_ddt_offset + sizeof(DdtHeader2), SEEK_SET);
1541
1542 size_t primary_table_size = ctx->user_data_ddt_header.entries * sizeof(uint64_t);
1543
1544 written_bytes = fwrite(ctx->user_data_ddt2, primary_table_size, 1, ctx->imageStream);
1545
1546 if(written_bytes != 1)
1547 {
1548 FATAL("Could not flush primary DDT table to file.");
1549 TRACE("Exiting set_ddt_multi_level_v2() = false");
1550 return false;
1551 }
1552
1553 // Update nextBlockPosition to ensure future blocks don't overwrite the DDT
1554 uint64_t ddt_total_size = sizeof(DdtHeader2) + ddt_header.length;
1555 ctx->next_block_position = end_of_file + ddt_total_size + alignment_mask & ~alignment_mask;
1556 block_offset = ctx->next_block_position;
1557 offset = 0;
1558 TRACE("Updated nextBlockPosition after DDT write to %" PRIu64, ctx->next_block_position);
1559
1560 fseek(ctx->imageStream, saved_pos, SEEK_SET);
1561
1562 // Free the cached table
1563
1564 free(ctx->cached_secondary_ddt2);
1565 ctx->cached_secondary_ddt2 = NULL;
1566
1567 // Restore file position
1568 fseek(ctx->imageStream, current_pos, SEEK_SET);
1569 }
1570
1571 // Step 5: Check if the specified block already has an existing secondary level table
1572 create_new_table = ctx->cached_secondary_ddt2 == NULL;
1573
1574 if(!create_new_table && secondary_ddt_offset != 0)
1575 {
1576 // Load existing table
1577 fseek(ctx->imageStream, secondary_ddt_offset, SEEK_SET);
1578 size_t read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader2), ctx->imageStream);
1579
1580 if(read_bytes != sizeof(DdtHeader2) || ddt_header.identifier != DeDuplicationTable2 ||
1581 ddt_header.type != UserData)
1582 {
1583 FATAL("Invalid secondary DDT header at %" PRIu64, secondary_ddt_offset);
1584 TRACE("Exiting set_ddt_multi_level_v2() = false");
1585 return false;
1586 }
1587
1588 // Read the table data (assuming no compression for now)
1589 buffer = malloc(ddt_header.length);
1590 if(buffer == NULL)
1591 {
1592 FATAL("Cannot allocate memory for secondary DDT.");
1593 TRACE("Exiting set_ddt_multi_level_v2() = false");
1594 return false;
1595 }
1596
1597 read_bytes = fread(buffer, 1, ddt_header.length, ctx->imageStream);
1598 if(read_bytes != ddt_header.length)
1599 {
1600 FATAL("Could not read secondary DDT data.");
1601 free(buffer);
1602 TRACE("Exiting set_ddt_multi_level_v2() = false");
1603 return false;
1604 }
1605
1606 // Verify CRC
1607 crc64_context = aaruf_crc64_init();
1608 if(crc64_context == NULL)
1609 {
1610 FATAL("Could not initialize CRC64.");
1611 free(buffer);
1612 TRACE("Exiting set_ddt_multi_level_v2() = false");
1613 return false;
1614 }
1615
1616 aaruf_crc64_update(crc64_context, buffer, read_bytes);
1617 aaruf_crc64_final(crc64_context, &crc64);
1618 aaruf_crc64_free(crc64_context);
1619
1620 if(crc64 != ddt_header.crc64)
1621 {
1622 FATAL("Secondary DDT CRC mismatch. Expected 0x%16lX but got 0x%16lX.", ddt_header.crc64, crc64);
1623 free(buffer);
1624 TRACE("Exiting set_ddt_multi_level_v2() = false");
1625 return false;
1626 }
1627
1628 // Cache the loaded table
1629
1630 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
1631
1632 ctx->cached_ddt_offset = secondary_ddt_offset;
1633 }
1634
1635 if(create_new_table)
1636 {
1637 // Create a new empty table
1638 size_t table_size = items_per_ddt_entry * sizeof(uint64_t);
1639
1640 buffer = calloc(1, table_size);
1641 if(buffer == NULL)
1642 {
1643 FATAL("Cannot allocate memory for new secondary DDT.");
1644 TRACE("Exiting set_ddt_multi_level_v2() = false");
1645 return false;
1646 }
1647
1648 ctx->cached_secondary_ddt2 = (uint64_t *)buffer;
1649
1650 ctx->cached_ddt_offset = 0; // Will be set when written to file
1651 ctx->cached_ddt_position = ddt_position; // Track which primary DDT position this new table belongs to
1652 TRACE("Created new secondary DDT for position %" PRIu64, ddt_position);
1653 }
1654
1655 // Step 6: Update the corresponding DDT entry
1656 if(*ddt_entry == 0)
1657 {
1658 block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1659 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1660 block_index << ctx->user_data_ddt_header.dataShift;
1661
1662 // Overflow detection for DDT entry
1663 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1664 {
1665 FATAL("DDT overflow: media does not fit in big DDT");
1666 TRACE("Exiting set_ddt_multi_level_v2() = false");
1667 return false;
1668 }
1669 }
1670
1671 // Sector status can be different from previous deduplicated sector
1672 *ddt_entry &= 0x0FFFFFFFFFFFFFFF;
1673 *ddt_entry |= (uint64_t)sector_status << 60;
1674
1675 TRACE("Setting big secondary DDT entry %d to %ull", sector_address % items_per_ddt_entry, (uint64_t)*ddt_entry);
1676 ctx->cached_secondary_ddt2[sector_address % items_per_ddt_entry] = *ddt_entry;
1677 ctx->dirty_secondary_ddt = true;
1678
1679 TRACE("Updated secondary DDT entry at position %" PRIu64, sector_address % items_per_ddt_entry);
1680 TRACE("Exiting set_ddt_multi_level_v2() = true");
1681 return true;
1682}
1683
1800bool set_ddt_tape(aaruformat_context *ctx, uint64_t sector_address, const uint64_t offset, const uint64_t block_offset,
1801 const uint8_t sector_status, uint64_t *ddt_entry)
1802{
1803 TRACE("Entering set_ddt_tape(%p, %" PRIu64 ", %llu, %llu, %d)", ctx, sector_address, offset, block_offset,
1804 sector_status);
1805
1806 // Check if the context and image stream are valid
1807 if(ctx == NULL || ctx->imageStream == NULL)
1808 {
1809 FATAL("Invalid context or image stream.");
1810 TRACE("Exiting set_ddt_tape() = false");
1811 return false;
1812 }
1813
1814 // Should not really be here
1815 if(!ctx->is_tape)
1816 {
1817 FATAL("Image is not tape, wrong function called.");
1818 TRACE("Exiting set_ddt_tape() = false");
1819 return false;
1820 }
1821
1822 if(*ddt_entry == 0)
1823 {
1824 const uint64_t block_index = block_offset >> ctx->user_data_ddt_header.blockAlignmentShift;
1825 *ddt_entry = offset & (1ULL << ctx->user_data_ddt_header.dataShift) - 1 |
1826 block_index << ctx->user_data_ddt_header.dataShift;
1827 // Overflow detection for DDT entry
1828 if(*ddt_entry > 0xFFFFFFFFFFFFFFF)
1829 {
1830 FATAL("DDT overflow: media does not fit in big DDT");
1831 TRACE("Exiting set_ddt_tape() = false");
1832 return false;
1833 }
1834
1835 *ddt_entry |= (uint64_t)sector_status << 60;
1836 }
1837
1838 // Create DDT hash entry
1839 TapeDdtHashEntry *new_entry = calloc(1, sizeof(TapeDdtHashEntry));
1840 TapeDdtHashEntry *old_entry = NULL;
1841 if(new_entry == NULL)
1842 {
1843 FATAL("Cannot allocate memory for new tape DDT hash entry.");
1844 TRACE("Exiting set_ddt_tape() = false");
1845 return false;
1846 }
1847
1848 TRACE("Setting tape DDT entry %d to %u", sector_address, (uint32_t)*ddt_entry);
1849
1850 new_entry->key = sector_address;
1851 new_entry->value = *ddt_entry;
1852
1853 // Insert entry into tape DDT
1854 HASH_REPLACE(hh, ctx->tape_ddt, key, sizeof(uint64_t), new_entry, old_entry);
1855 ctx->dirty_tape_ddt = true; // Mark tape DDT as dirty
1856 if(old_entry) free(old_entry);
1857
1858 TRACE("Exiting set_ddt_tape() = true");
1859 return true;
1860}
#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:1800
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:585
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:1110
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:1002
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:728
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:1038
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:511
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
void aaruf_crc64_free(crc64_ctx *ctx)
Frees a CRC64 context.
Definition crc64.c:155
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:148
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:146
@ DeDuplicationTableSAlpha
Block containing a secondary deduplication table (v2) (mistake).
Definition enums.h:147
@ SectorStatusNotDumped
Sector(s) not yet acquired during image dumping.
Definition enums.h:236
@ 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:77
#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:1438
#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
uint64_t crc64
CRC64-ECMA of uncompressed table payload.
Definition ddt.h:162
uint64_t entries
Number of entries contained in (uncompressed) table payload.
Definition ddt.h:158
uint8_t levels
Total number of hierarchy levels (root depth); > 0.
Definition ddt.h:146
uint64_t length
Uncompressed payload size in bytes.
Definition ddt.h:160
uint32_t identifier
Block identifier, must be BlockType::DeDuplicationTable2.
Definition ddt.h:143
uint8_t tableShift
2^tableShift = number of logical sectors per primary entry (multi-level only; 0 for single-level or s...
Definition ddt.h:156
uint64_t blocks
Total internal span (negative + usable + overflow) in logical sectors.
Definition ddt.h:150
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
uint32_t overflow
Trailing dumped sectors beyond user area (overflow range), still mapped with entries.
Definition ddt.h:151
uint32_t negative
Leading negative LBA count; added to external L to build internal index.
Definition ddt.h:149
uint8_t tableLevel
Zero-based level index of this table (0 = root, increases downward).
Definition ddt.h:147
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
uint8_t dataShift
2^dataShift = sectors represented per increment in blockIndex field.
Definition ddt.h:155
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
uint64_t previousLevelOffset
Absolute byte offset of the parent (previous) level table; 0 if root.
Definition ddt.h:148
uint64_t ImageSize
Size of the image payload in bytes (excludes headers/metadata).
Definition aaru.h:935
uint64_t Sectors
Total count of addressable logical sectors/blocks.
Definition aaru.h:936
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
uint16_t dataType
Data classification (value from DataType) or unused for untyped blocks.
Definition index.h:111
uint64_t key
Key: sector address.
Definition context.h:145
uint64_t value
Value: DDT entry.
Definition context.h:146
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:192
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:303
bool dirty_primary_ddt
True if primary DDT table should be written during close.
Definition context.h:318
uint64_t * user_data_ddt2
DDT entries (big variant) primary/secondary current.
Definition context.h:190
uint64_t * sector_suffix_ddt2
CD sector suffix DDT V2.
Definition context.h:189
uint64_t cached_ddt_offset
File offset of currently cached secondary DDT (0=none).
Definition context.h:193
bool is_tape
True if the image is a tape image.
Definition context.h:308
bool in_memory_ddt
True if primary (and possibly secondary) DDT loaded.
Definition context.h:199
bool dirty_index_block
True if index block should be written during close.
Definition context.h:340
TapeDdtHashEntry * tape_ddt
Hash table root for tape DDT entries.
Definition context.h:185
bool dirty_single_level_ddt
True if single-level DDT should be written during close.
Definition context.h:319
int ddt_version
DDT version in use (1=legacy, 2=v2 hierarchical).
Definition context.h:197
uint8_t * writing_buffer
Accumulation buffer for current block data.
Definition context.h:283
uint64_t * sector_prefix_ddt2
CD sector prefix DDT V2.
Definition context.h:188
uint64_t primary_ddt_offset
File offset of the primary DDT v2 table.
Definition context.h:195
uint64_t next_block_position
Absolute file offset where next block will be written.
Definition context.h:285
uint64_t * cached_secondary_ddt2
Cached secondary table (big entries) or NULL.
Definition context.h:191
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:179
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:255
ImageInfo image_info
Exposed high-level image info summary.
Definition context.h:263
bool dirty_secondary_ddt
True if secondary DDT tables should be written during close.
Definition context.h:317
bool dirty_tape_ddt
True if tape DDT should be written during close.
Definition context.h:331
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:301
uint64_t cached_ddt_position
Position index of cached secondary DDT.
Definition context.h:194
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56