libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
erasure.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
23
24#include <stdlib.h>
25#include <string.h>
26
27#include "aaruformat.h"
28#include "aaruformat/context.h"
29#include "aaruformat/consts.h"
30#include "aaruformat/enums.h"
31#include "aaruformat/errors.h"
36#include "internal.h"
37#include "log.h"
38#include "lib/gf256.h"
39#include "lib/reed_solomon.h"
40
41/* =========================================================================
42 * Read-path structures (not in on-disk format, internal only)
43 * ========================================================================= */
44
52
54typedef struct EcBlockLookupEntry
55{
56 uint64_t block_offset;
57 uint32_t stripe_index;
58 uint16_t position;
59 UT_hash_handle hh;
61
62/* UT_array icd for completed stripe descriptors.
63 * Each descriptor is a variable-length blob serialized in-place. We store
64 * them as flat byte buffers since the size per stripe depends on K and M. */
65static UT_icd ec_stripe_icd = {sizeof(uint8_t), NULL, NULL, NULL};
66
67/* Forward declarations */
68void ec_flush_data_stripe(aaruformat_context *ctx, uint32_t slot);
69
82AARU_EXPORT int32_t AARU_CALL aaruf_set_erasure_coding(void *context, uint8_t algorithm, uint16_t K, uint16_t M)
83{
84 TRACE("Entering aaruf_set_erasure_coding(%p, %u, %u, %u)", context, algorithm, K, M);
85
86 if(context == NULL) return AARUF_STATUS_INVALID_CONTEXT;
87
88 aaruformat_context *ctx = (aaruformat_context *)context;
90 if(!ctx->is_writing) return AARUF_READ_ONLY;
91
92 /* Validate parameters */
93 if(K == 0 || M == 0) return AARUF_ERROR_INCORRECT_DATA_SIZE;
94 if((uint32_t)K + M > 255) return AARUF_ERROR_INCORRECT_DATA_SIZE;
95 if(algorithm == kErasureCodingXor && M != 1) return AARUF_ERROR_INCORRECT_DATA_SIZE;
96
97 /* Create RS codec */
98 rs_context *rs = rs_create(K, M);
99 if(!rs) return AARUF_ERROR_NOT_ENOUGH_MEMORY;
100
101 /* Parity buffers are allocated lazily in ec_accumulate_data_block() when the
102 * first block's actual on-disk size is known. This avoids allocating at the
103 * theoretical maximum (which can be gigabytes with large dataShift values). */
104
105 /* Allocate tracking arrays (K entries per slot × K slots) */
106 uint64_t *offsets = (uint64_t *)calloc((size_t)K * K, sizeof(uint64_t));
107 uint32_t *sizes = (uint32_t *)calloc((size_t)K * K, sizeof(uint32_t));
108 uint64_t *crcs = (uint64_t *)calloc((size_t)K * K, sizeof(uint64_t));
109 uint16_t *counts = (uint16_t *)calloc(K, sizeof(uint16_t));
110
111 if(!offsets || !sizes || !crcs || !counts)
112 {
113 free(offsets); free(sizes); free(crcs); free(counts);
114 rs_free(rs);
116 }
117
118 /* Initialize completed stripes array */
119 UT_array *stripes = NULL;
120 utarray_new(stripes, &ec_stripe_icd);
121
122 /* Store in context */
123 ctx->ec_algorithm = algorithm;
124 ctx->ec_K = K;
125 ctx->ec_M = M;
126 ctx->ec_data_shard_size = 0; /* Will be set on first block */
127 /* A resumed image may already have the codec created to read its erasure coding */
128 if(ctx->ec_rs_ctx) rs_free((rs_context *)ctx->ec_rs_ctx);
129
130 ctx->ec_rs_ctx = rs;
131 ctx->ec_data_parity = NULL; /* Allocated lazily */
132 ctx->ec_data_block_offsets = offsets;
133 ctx->ec_data_block_sizes = sizes;
134 ctx->ec_data_shard_crcs = crcs;
135 ctx->ec_data_stripe_counts = counts;
136 ctx->ec_total_data_blocks = 0;
137 ctx->ec_data_stripes = stripes;
138 ctx->ec_enabled = true;
139
140 /* Set feature flag so old readers know parity data exists */
142
143 TRACE("Erasure coding configured: algorithm=%u K=%u M=%u (parity buffers deferred)", algorithm, K, M);
144 TRACE("Exiting aaruf_set_erasure_coding() = 0");
145 return AARUF_STATUS_OK;
146}
147
174AARU_EXPORT int32_t AARU_CALL aaruf_set_erasure_coding_auto(void *context, uint8_t recovery_percent)
175{
176 TRACE("Entering aaruf_set_erasure_coding_auto(%p, %u)", context, recovery_percent);
177
178 if(recovery_percent == 0 || recovery_percent > 100)
180
181 /* M scales with recovery percentage: more recovery = more parity blocks.
182 * Target K ≈ 20 (reasonable stripe size), so M ≈ 20 * percent / 100.
183 * Clamp to [2, 8]: minimum M=2 for RS burst tolerance, max M=8 for memory. */
184 uint16_t M = (uint16_t)((20 * (uint32_t)recovery_percent + 50) / 100); /* rounded */
185 if(M < 2) M = 2;
186 if(M > 8) M = 8;
187
188 uint16_t K = (uint16_t)(M * 100 / recovery_percent);
189 if(K < 1) K = 1;
190 if(K + M > 255) K = 255 - M;
191
192 TRACE("Auto EC: recovery_percent=%u -> K=%u M=%u", recovery_percent, K, M);
194}
195
209void ec_accumulate_data_block(aaruformat_context *ctx, const BlockHeader *block_header, const uint8_t *lzma_props,
210 const uint8_t *payload, uint32_t payload_size, uint64_t file_offset)
211{
212 if(!ctx->ec_enabled) return;
213
214 const uint16_t K = ctx->ec_K;
215 const uint16_t M = ctx->ec_M;
216
217 /* Consecutive stripe assignment: fill slot 0 completely before starting slot 1.
218 * With interleaved assignment (slot = total % K), a small image with N < K*K blocks
219 * produces K partial stripes, each requiring M parity blocks → overhead = M*K/N
220 * instead of the intended M/K. Consecutive assignment produces at most 1 partial
221 * stripe at the end, giving correct M/K overhead regardless of image size. */
222 uint32_t slot = 0; /* Always use slot 0 for consecutive assignment */
223
224 /* Position within this slot's stripe */
225 uint16_t pos = ctx->ec_data_stripe_counts[slot];
226
227 /* Compute actual on-disk size of this block */
228 uint32_t actual_size = (uint32_t)sizeof(BlockHeader);
229 if(block_header->compression == kCompressionLzma && lzma_props)
230 actual_size += LZMA_PROPERTIES_LENGTH;
231 actual_size += payload_size;
232
233 /* Lazy allocation: allocate parity buffers on first use, or grow if this
234 * block is larger than any previously seen. This avoids pre-allocating at
235 * the theoretical maximum (which can be gigabytes with large dataShift). */
236 if(ctx->ec_data_parity == NULL)
237 {
238 /* First block — allocate parity buffers at this block's size + 25% headroom */
239 ctx->ec_data_shard_size = actual_size + actual_size / 4;
240 ctx->ec_data_parity = (uint8_t **)calloc((size_t)K * M, sizeof(uint8_t *));
241 if(!ctx->ec_data_parity) return;
242 for(uint32_t i = 0; i < (uint32_t)K * M; i++)
243 {
244 ctx->ec_data_parity[i] = (uint8_t *)calloc(1, ctx->ec_data_shard_size);
245 if(!ctx->ec_data_parity[i]) return;
246 }
247 TRACE("EC parity buffers allocated: %u × %u bytes", K * M, ctx->ec_data_shard_size);
248 }
249 else if(actual_size > ctx->ec_data_shard_size)
250 {
251 /* This block is larger than current shard size — grow all parity buffers.
252 * Zero-extend the existing data (realloc + memset the new region). */
253 uint32_t old_size = ctx->ec_data_shard_size;
254 uint32_t new_size = actual_size + actual_size / 4;
255 TRACE("EC parity buffers growing: %u → %u bytes", old_size, new_size);
256 for(uint32_t i = 0; i < (uint32_t)K * M; i++)
257 {
258 uint8_t *grown = (uint8_t *)realloc(ctx->ec_data_parity[i], new_size);
259 if(!grown) return;
260 memset(grown + old_size, 0, new_size - old_size);
261 ctx->ec_data_parity[i] = grown;
262 }
263 ctx->ec_data_shard_size = new_size;
264 }
265
266 const uint32_t shard = ctx->ec_data_shard_size;
267
268 /* Build on-disk shard in a temp buffer */
269 uint8_t *shard_buf = (uint8_t *)calloc(1, shard);
270 if(!shard_buf) return; /* Best effort — if OOM, skip parity for this block */
271
272 /* Copy BlockHeader */
273 memcpy(shard_buf, block_header, sizeof(BlockHeader));
274 uint32_t offset = sizeof(BlockHeader);
275
276 /* Copy LZMA properties if applicable */
277 if(block_header->compression == kCompressionLzma && lzma_props)
278 {
279 memcpy(shard_buf + offset, lzma_props, LZMA_PROPERTIES_LENGTH);
280 offset += LZMA_PROPERTIES_LENGTH;
281 }
282
283 /* Copy payload */
284 memcpy(shard_buf + offset, payload, payload_size);
285
286 /* Compute CRC64 of the shard (only actual_size bytes matter — rest is zero) */
287 uint64_t shard_crc = aaruf_crc64_data(shard_buf, actual_size);
288
289 /* Record tracking info */
290 size_t tracking_idx = (size_t)slot * K + pos;
291 ctx->ec_data_block_offsets[tracking_idx] = file_offset;
292 ctx->ec_data_block_sizes[tracking_idx] = actual_size;
293 ctx->ec_data_shard_crcs[tracking_idx] = shard_crc;
294
295 /* Accumulate into parity buffers for this slot.
296 * Only process actual_size bytes — bytes beyond that are zero and
297 * GF_mul(0, coeff) = 0, so they contribute nothing to parity. */
298 for(uint16_t m = 0; m < M; m++)
299 {
300 uint8_t coeff = rs_get_coefficient((rs_context *)ctx->ec_rs_ctx, m, pos);
301 size_t parity_idx = (size_t)slot * M + m;
302 rs_encode_incremental(coeff, shard_buf, ctx->ec_data_parity[parity_idx], actual_size);
303 }
304
305 free(shard_buf);
306
307 ctx->ec_data_stripe_counts[slot]++;
309
310 /* Check if this stripe slot is full → write parity blocks */
311 if(ctx->ec_data_stripe_counts[slot] == K)
312 {
313 ec_flush_data_stripe(ctx, slot);
314 }
315}
316
324{
325 const uint16_t K = ctx->ec_K;
326 const uint16_t M = ctx->ec_M;
327 const uint32_t shard = ctx->ec_data_shard_size;
328 uint16_t actual_k = ctx->ec_data_stripe_counts[slot];
329
330 if(actual_k == 0) return;
331
332 /* Build and serialize stripe descriptor:
333 * [actualK: uint16_t]
334 * [actualK × StripeDataBlockEntry: offset(8) + onDiskSize(4) + shardCrc64(8) = 20 bytes each]
335 * [M × StripeParityBlockEntry: offset(8) = 8 bytes each]
336 */
337 size_t desc_data_size = sizeof(uint16_t) + (size_t)actual_k * sizeof(StripeDataBlockEntry) +
338 (size_t)M * sizeof(StripeParityBlockEntry);
339 uint8_t *desc = (uint8_t *)calloc(1, desc_data_size);
340 if(!desc) return;
341
342 uint8_t *p = desc;
343
344 /* Write actualK */
345 memcpy(p, &actual_k, sizeof(uint16_t)); p += sizeof(uint16_t);
346
347 /* Write data block entries */
348 for(uint16_t k = 0; k < actual_k; k++)
349 {
350 size_t idx = (size_t)slot * K + k;
352 entry.offset = ctx->ec_data_block_offsets[idx];
353 entry.onDiskSize = ctx->ec_data_block_sizes[idx];
354 entry.shardCrc64 = ctx->ec_data_shard_crcs[idx];
355 memcpy(p, &entry, sizeof(StripeDataBlockEntry)); p += sizeof(StripeDataBlockEntry);
356 }
357
358 /* Write M parity blocks to disk. Only write up to max_block_size bytes
359 * of parity — bytes beyond the largest actual block in this stripe are
360 * guaranteed to be zero (RS of zero tails = zero).
361 * This is the key optimization: parity size scales with actual compressed
362 * block size, not with the theoretical maximum uncompressed block size. */
363 uint32_t max_block_size = 0;
364 for(uint16_t k = 0; k < actual_k; k++)
365 {
366 size_t idx = (size_t)slot * K + k;
367 if(ctx->ec_data_block_sizes[idx] > max_block_size)
368 max_block_size = ctx->ec_data_block_sizes[idx];
369 }
370 if(max_block_size == 0) max_block_size = shard; /* safety fallback */
371
372 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
373
374 for(uint16_t m = 0; m < M; m++)
375 {
376 size_t parity_idx = (size_t)slot * M + m;
377 uint8_t *parity_data = ctx->ec_data_parity[parity_idx];
378
379 BlockHeader parity_header;
380 memset(&parity_header, 0, sizeof(BlockHeader));
381 parity_header.identifier = DataBlock;
382 parity_header.type = kDataTypeErasureParity;
383 parity_header.compression = kCompressionNone;
384 parity_header.sectorSize = 0;
385 parity_header.length = max_block_size;
386 parity_header.cmpLength = max_block_size;
387 parity_header.crc64 = aaruf_crc64_data(parity_data, max_block_size);
388 parity_header.cmpCrc64 = parity_header.crc64;
389
390 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
391 uint64_t parity_offset = (uint64_t)aaruf_ftell(ctx->imageStream);
392 parity_offset = (parity_offset + alignment_mask) & ~alignment_mask;
393 aaruf_fseek(ctx->imageStream, (aaru_off_t)parity_offset, SEEK_SET);
394
395 fwrite(&parity_header, sizeof(BlockHeader), 1, ctx->imageStream);
396 fwrite(parity_data, max_block_size, 1, ctx->imageStream);
397
398 /* Record parity offset in descriptor */
400 pentry.offset = parity_offset;
401 memcpy(p, &pentry, sizeof(StripeParityBlockEntry)); p += sizeof(StripeParityBlockEntry);
402
403 /* Add index entry */
404 IndexEntry ie = {.blockType = DataBlock, .dataType = kDataTypeErasureParity, .offset = parity_offset};
405 utarray_push_back(ctx->index_entries, &ie);
406 ctx->dirty_index_block = true;
407
408 /* Update next_block_position */
409 uint64_t total = sizeof(BlockHeader) + parity_header.cmpLength;
410 ctx->next_block_position = (parity_offset + total + alignment_mask) & ~alignment_mask;
411
412 /* Reset parity buffer */
413 memset(parity_data, 0, shard);
414 }
415
416 /* Store the completed stripe descriptor */
417 for(size_t i = 0; i < desc_data_size; i++)
418 utarray_push_back(ctx->ec_data_stripes, &desc[i]);
419
420 free(desc);
421
422 /* Reset stripe slot tracking */
423 for(uint16_t k = 0; k < K; k++)
424 {
425 size_t idx = (size_t)slot * K + k;
426 ctx->ec_data_block_offsets[idx] = 0;
427 ctx->ec_data_block_sizes[idx] = 0;
428 ctx->ec_data_shard_crcs[idx] = 0;
429 }
430 ctx->ec_data_stripe_counts[slot] = 0;
431}
432
440 const uint64_t *offsets, const uint32_t *sizes,
441 uint32_t block_count, uint8_t group_type,
442 uint16_t parity_data_type,
443 uint8_t **out_desc, size_t *out_desc_len,
444 StripeGroupDescriptor *out_group)
445{
446 *out_desc = NULL;
447 *out_desc_len = 0;
448 if(block_count == 0) return;
449
450 const uint16_t M = ctx->ec_M;
451 uint16_t actual_k = (uint16_t)(block_count > (uint32_t)(255 - M) ? 255 - M : block_count);
452
453 uint32_t shard_size = 0;
454 for(uint32_t i = 0; i < actual_k; i++)
455 if(sizes[i] > shard_size) shard_size = sizes[i];
456
457 rs_context *rs = rs_create(actual_k, M);
458 if(!rs) return;
459
460 uint8_t **parity = (uint8_t **)calloc(M, sizeof(uint8_t *));
461 if(!parity) { rs_free(rs); return; }
462 for(uint16_t m = 0; m < M; m++)
463 {
464 parity[m] = (uint8_t *)calloc(1, shard_size);
465 if(!parity[m]) { for(uint16_t j = 0; j < m; j++) free(parity[j]); free(parity); rs_free(rs); return; }
466 }
467
468 size_t desc_size = sizeof(uint16_t) + (size_t)actual_k * sizeof(StripeDataBlockEntry) +
469 (size_t)M * sizeof(StripeParityBlockEntry);
470 uint8_t *desc = (uint8_t *)calloc(1, desc_size);
471 if(!desc) { for(uint16_t m = 0; m < M; m++) free(parity[m]); free(parity); rs_free(rs); return; }
472
473 uint8_t *dp = desc;
474 memcpy(dp, &actual_k, sizeof(uint16_t)); dp += sizeof(uint16_t);
475
476 uint8_t *shard_buf = (uint8_t *)calloc(1, shard_size);
477 if(!shard_buf) { free(desc); for(uint16_t m = 0; m < M; m++) free(parity[m]); free(parity); rs_free(rs); return; }
478
479 for(uint16_t k = 0; k < actual_k; k++)
480 {
481 memset(shard_buf, 0, shard_size);
482 aaruf_fseek(ctx->imageStream, (aaru_off_t)offsets[k], SEEK_SET);
483 uint32_t read_size = sizes[k] > shard_size ? shard_size : sizes[k];
484 fread(shard_buf, read_size, 1, ctx->imageStream);
485
486 uint64_t shard_crc = aaruf_crc64_data(shard_buf, shard_size);
487
489 entry.offset = offsets[k]; entry.onDiskSize = sizes[k]; entry.shardCrc64 = shard_crc;
490 memcpy(dp, &entry, sizeof(StripeDataBlockEntry)); dp += sizeof(StripeDataBlockEntry);
491
492 for(uint16_t m = 0; m < M; m++)
493 {
494 uint8_t coeff = rs_get_coefficient(rs, m, k);
495 rs_encode_incremental(coeff, shard_buf, parity[m], shard_size);
496 }
497 }
498 free(shard_buf);
499
500 /* max_block_size = largest actual on-disk block in this stripe.
501 * Only write this many parity bytes — the rest is guaranteed zero. */
502 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
503 for(uint16_t m = 0; m < M; m++)
504 {
505 BlockHeader ph;
506 memset(&ph, 0, sizeof(ph));
507 ph.identifier = DataBlock; ph.type = parity_data_type;
509 ph.length = shard_size; ph.cmpLength = shard_size;
510 ph.crc64 = aaruf_crc64_data(parity[m], shard_size); ph.cmpCrc64 = ph.crc64;
511
512 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
513 uint64_t po = ((uint64_t)aaruf_ftell(ctx->imageStream) + alignment_mask) & ~alignment_mask;
514 aaruf_fseek(ctx->imageStream, (aaru_off_t)po, SEEK_SET);
515 fwrite(&ph, sizeof(BlockHeader), 1, ctx->imageStream);
516 fwrite(parity[m], shard_size, 1, ctx->imageStream);
517
518 StripeParityBlockEntry pe; pe.offset = po;
519 memcpy(dp, &pe, sizeof(StripeParityBlockEntry)); dp += sizeof(StripeParityBlockEntry);
520
521 IndexEntry ie = {.blockType = DataBlock, .dataType = parity_data_type, .offset = po};
522 utarray_push_back(ctx->index_entries, &ie);
523 ctx->dirty_index_block = true;
524 free(parity[m]);
525 }
526 free(parity); rs_free(rs);
527
528 memset(out_group, 0, sizeof(StripeGroupDescriptor));
529 out_group->groupType = group_type; out_group->K = actual_k; out_group->M = M;
530 out_group->shardSize = shard_size; out_group->stripeCount = 1; out_group->interleaveDepth = 1;
531 *out_desc = desc; *out_desc_len = desc_size;
532}
533
537static void ec_collect_blocks_by_type(aaruformat_context *ctx, uint32_t block_type,
538 uint64_t **out_offsets, uint32_t **out_sizes, uint32_t *out_count)
539{
540 *out_offsets = NULL; *out_sizes = NULL; *out_count = 0;
541 uint32_t n = (uint32_t)utarray_len(ctx->index_entries);
542 if(n == 0) return;
543
544 uint32_t count = 0;
545 for(uint32_t i = 0; i < n; i++)
546 {
547 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
548 if(ie->blockType == block_type) count++;
549 }
550 if(count == 0) return;
551
552 uint64_t *offsets = (uint64_t *)malloc(count * sizeof(uint64_t));
553 uint32_t *sizes = (uint32_t *)malloc(count * sizeof(uint32_t));
554 if(!offsets || !sizes) { free(offsets); free(sizes); return; }
555
556 uint32_t idx = 0;
557 for(uint32_t i = 0; i < n && idx < count; i++)
558 {
559 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
560 if(ie->blockType != block_type) continue;
561 offsets[idx] = ie->offset;
562 aaruf_fseek(ctx->imageStream, (aaru_off_t)ie->offset, SEEK_SET);
563 if(block_type == DeDuplicationTable2 || block_type == DeDuplicationTableSecondary)
564 {
565 DdtHeader2 ddt_hdr;
566 if(fread(&ddt_hdr, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
567 sizes[idx] = (uint32_t)(sizeof(DdtHeader2) + ddt_hdr.cmpLength);
568 else sizes[idx] = sizeof(DdtHeader2);
569 }
570 else
571 {
572 BlockHeader blk_hdr;
573 if(fread(&blk_hdr, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
574 sizes[idx] = (uint32_t)(sizeof(BlockHeader) + blk_hdr.cmpLength);
575 else sizes[idx] = sizeof(BlockHeader);
576 }
577 idx++;
578 }
579 *out_offsets = offsets; *out_sizes = sizes; *out_count = idx;
580}
581
586{
587 if(!ctx->ec_enabled) return;
588
589 const uint16_t K = ctx->ec_K;
590 const uint16_t M = ctx->ec_M;
591
592 /* --- Group 0: Data blocks (flush partial stripes) --- */
593 for(uint16_t slot = 0; slot < K; slot++)
594 if(ctx->ec_data_stripe_counts[slot] > 0)
595 ec_flush_data_stripe(ctx, slot);
596
597 uint32_t data_stripe_count = 0;
598 {
599 size_t total_bytes = utarray_len(ctx->ec_data_stripes);
600 uint8_t *base = (uint8_t *)utarray_front(ctx->ec_data_stripes);
601 size_t pos = 0;
602 while(base && pos + sizeof(uint16_t) <= total_bytes)
603 {
604 uint16_t ak;
605 memcpy(&ak, base + pos, sizeof(uint16_t));
606 pos += sizeof(uint16_t) + (size_t)ak * sizeof(StripeDataBlockEntry) + (size_t)M * sizeof(StripeParityBlockEntry);
607 data_stripe_count++;
608 }
609 }
610
611 StripeGroupDescriptor data_group;
612 memset(&data_group, 0, sizeof(data_group));
613 data_group.groupType = kECGroupData; data_group.K = K; data_group.M = M;
614 data_group.shardSize = ctx->ec_data_shard_size; data_group.stripeCount = data_stripe_count;
615 data_group.interleaveDepth = K;
616 size_t data_stripe_data_len = utarray_len(ctx->ec_data_stripes);
617
618 /* --- Group 1: DDT secondary --- */
619 uint8_t *ddt_sec_desc = NULL; size_t ddt_sec_desc_len = 0;
620 StripeGroupDescriptor ddt_sec_group; memset(&ddt_sec_group, 0, sizeof(ddt_sec_group));
621 {
622 uint64_t *off; uint32_t *sz; uint32_t cnt;
624 if(cnt > 0) { ec_write_batch_parity(ctx, off, sz, cnt, kECGroupDdtSecondary, kDataTypeErasureParityDdt, &ddt_sec_desc, &ddt_sec_desc_len, &ddt_sec_group); free(off); free(sz); }
625 }
626
627 /* --- Group 2: DDT primary --- */
628 uint8_t *ddt_pri_desc = NULL; size_t ddt_pri_desc_len = 0;
629 StripeGroupDescriptor ddt_pri_group; memset(&ddt_pri_group, 0, sizeof(ddt_pri_group));
630 {
631 uint64_t *off; uint32_t *sz; uint32_t cnt;
632 ec_collect_blocks_by_type(ctx, DeDuplicationTable2, &off, &sz, &cnt);
633 if(cnt > 0) { ec_write_batch_parity(ctx, off, sz, cnt, kECGroupDdtPrimary, kDataTypeErasureParityDdtPrimary, &ddt_pri_desc, &ddt_pri_desc_len, &ddt_pri_group); free(off); free(sz); }
634 }
635
636 /* --- Group 3: Metadata — all blocks that are not data/parity, DDT, or index.
637 * These include TracksBlock, ChecksumBlock, GeometryBlock, MetadataBlock, DumpHardwareBlock,
638 * CicmBlock, AaruMetadataJsonBlock, and DataBlock with non-UserData types (media tags,
639 * sector prefix/suffix, subchannel, etc.).
640 *
641 * IMPORTANT: non-DataBlock types have different header layouts. We cannot read their
642 * cmpLength via BlockHeader. Instead, compute on-disk size from index entry offsets:
643 * sort by offset, then size[i] = offset[i+1] - offset[i]. For the last entry, read
644 * from the block's header struct if it's a DataBlock, otherwise use a reasonable bound. */
645 uint8_t *meta_desc = NULL; size_t meta_desc_len = 0;
646 StripeGroupDescriptor meta_group; memset(&meta_group, 0, sizeof(meta_group));
647 {
648 uint32_t n = (uint32_t)utarray_len(ctx->index_entries);
649
650 /* Collect metadata block offsets */
651 uint32_t count = 0;
652 for(uint32_t i = 0; i < n; i++)
653 {
654 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
655 /* Skip user data blocks and parity blocks */
656 if(ie->blockType == DataBlock && (ie->dataType == kDataTypeUserData ||
658 /* Skip DDT blocks */
660 ie->blockType == DeDuplicationTable) continue;
661 /* Skip index blocks */
662 if(ie->blockType == IndexBlock || ie->blockType == IndexBlock2 || ie->blockType == IndexBlock3) continue;
663 count++;
664 }
665
666 if(count > 0 && count <= (uint32_t)(255 - M))
667 {
668 uint64_t *off = (uint64_t *)malloc(count * sizeof(uint64_t));
669 uint32_t *sz = (uint32_t *)calloc(count, sizeof(uint32_t));
670 uint32_t *bt = (uint32_t *)malloc(count * sizeof(uint32_t));
671 if(off && sz && bt)
672 {
673 /* Collect offsets and block types */
674 uint32_t idx = 0;
675 for(uint32_t i = 0; i < n && idx < count; i++)
676 {
677 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
678 if(ie->blockType == DataBlock && (ie->dataType == kDataTypeUserData ||
681 ie->blockType == DeDuplicationTable) continue;
682 if(ie->blockType == IndexBlock || ie->blockType == IndexBlock2 || ie->blockType == IndexBlock3) continue;
683 off[idx] = ie->offset;
684 bt[idx] = ie->blockType;
685 idx++;
686 }
687
688 /* Compute on-disk sizes. For DataBlock types, read cmpLength from header.
689 * For other block types (TracksBlock, ChecksumBlock, etc.), read their
690 * specific header to get the payload length. Use a generic approach:
691 * read the entire block up to the next block's offset. */
692 for(uint32_t i = 0; i < idx; i++)
693 {
694 if(bt[i] == DataBlock)
695 {
696 /* DataBlock: cmpLength at offset 12 in header */
697 aaruf_fseek(ctx->imageStream, (aaru_off_t)off[i], SEEK_SET);
698 BlockHeader bh;
699 if(fread(&bh, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
700 sz[i] = (uint32_t)(sizeof(BlockHeader) + bh.cmpLength);
701 else
702 sz[i] = sizeof(BlockHeader);
703 }
704 else
705 {
706 /* Non-DataBlock: determine size by finding the next index entry
707 * at a higher offset. This gives the gap between this block and
708 * the next, which is the on-disk size (aligned). */
709 uint64_t next_offset = UINT64_MAX;
710 for(uint32_t j = 0; j < n; j++)
711 {
712 IndexEntry *je = (IndexEntry *)utarray_eltptr(ctx->index_entries, j);
713 if(je->offset > off[i] && je->offset < next_offset)
714 next_offset = je->offset;
715 }
716 if(next_offset != UINT64_MAX)
717 sz[i] = (uint32_t)(next_offset - off[i]);
718 else
719 {
720 /* Last block before index — use index offset as bound */
721 if(ctx->header.indexOffset > off[i])
722 sz[i] = (uint32_t)(ctx->header.indexOffset - off[i]);
723 else
724 sz[i] = 4096; /* safe fallback */
725 }
726 }
727 }
728
730 &meta_desc, &meta_desc_len, &meta_group);
731 }
732 free(off); free(sz); free(bt);
733 }
734 }
735
736 /* --- Group 4: Index (K=1, M replicas) --- */
737 uint8_t *idx_desc = NULL; size_t idx_desc_len = 0;
738 StripeGroupDescriptor idx_group; memset(&idx_group, 0, sizeof(idx_group));
739 if(ctx->header.indexOffset > 0)
740 {
741 uint64_t io = ctx->header.indexOffset;
742 aaruf_fseek(ctx->imageStream, (aaru_off_t)io, SEEK_SET);
743 IndexHeader3 ih;
744 if(fread(&ih, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
745 {
746 uint32_t isz = (uint32_t)(sizeof(IndexHeader3) + ih.entries * sizeof(IndexEntry));
747 ec_write_batch_parity(ctx, &io, &isz, 1, kECGroupIndex, kDataTypeErasureParityIndex, &idx_desc, &idx_desc_len, &idx_group);
748 }
749 }
750
751 /* --- Build ECMB with all groups --- */
752 uint8_t group_count = 1; /* data always present */
753 if(ddt_sec_desc) group_count++;
754 if(ddt_pri_desc) group_count++;
755 if(meta_desc) group_count++;
756 if(idx_desc) group_count++;
757
758 size_t payload_len = sizeof(StripeGroupDescriptor) + data_stripe_data_len;
759 if(ddt_sec_desc) payload_len += sizeof(StripeGroupDescriptor) + ddt_sec_desc_len;
760 if(ddt_pri_desc) payload_len += sizeof(StripeGroupDescriptor) + ddt_pri_desc_len;
761 if(meta_desc) payload_len += sizeof(StripeGroupDescriptor) + meta_desc_len;
762 if(idx_desc) payload_len += sizeof(StripeGroupDescriptor) + idx_desc_len;
763
764 uint8_t *payload = (uint8_t *)malloc(payload_len);
765 if(!payload) { free(ddt_sec_desc); free(ddt_pri_desc); free(meta_desc); free(idx_desc); return; }
766
767 uint8_t *wp = payload;
768 /* Data group */
769 memcpy(wp, &data_group, sizeof(StripeGroupDescriptor)); wp += sizeof(StripeGroupDescriptor);
770 if(data_stripe_data_len > 0) { uint8_t *b = (uint8_t *)utarray_front(ctx->ec_data_stripes); if(b) { memcpy(wp, b, data_stripe_data_len); wp += data_stripe_data_len; } }
771
772#define WRITE_GROUP(desc, desc_len, grp) \
773 if(desc) { memcpy(wp, &(grp), sizeof(StripeGroupDescriptor)); wp += sizeof(StripeGroupDescriptor); \
774 memcpy(wp, (desc), (desc_len)); wp += (desc_len); }
775
776 WRITE_GROUP(ddt_sec_desc, ddt_sec_desc_len, ddt_sec_group)
777 WRITE_GROUP(ddt_pri_desc, ddt_pri_desc_len, ddt_pri_group)
778 WRITE_GROUP(meta_desc, meta_desc_len, meta_group)
779 WRITE_GROUP(idx_desc, idx_desc_len, idx_group)
780#undef WRITE_GROUP
781
782 free(ddt_sec_desc); free(ddt_pri_desc); free(meta_desc); free(idx_desc);
783
784 uint64_t payload_crc = aaruf_crc64_data(payload, (uint32_t)payload_len);
785
787 memset(&ecmb, 0, sizeof(ecmb));
789 ecmb.stripeGroupCount = group_count; ecmb.compression = kCompressionNone;
790 ecmb.length = payload_len; ecmb.cmpLength = payload_len;
791 ecmb.crc64 = payload_crc; ecmb.cmpCrc64 = payload_crc;
792
793 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
794 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
795 uint64_t ecmb_offset = ((uint64_t)aaruf_ftell(ctx->imageStream) + alignment_mask) & ~alignment_mask;
796 aaruf_fseek(ctx->imageStream, (aaru_off_t)ecmb_offset, SEEK_SET);
797 fwrite(&ecmb, sizeof(ErasureCodingMapHeader), 1, ctx->imageStream);
798 fwrite(payload, payload_len, 1, ctx->imageStream);
799 uint64_t ecmb_total = sizeof(ErasureCodingMapHeader) + payload_len;
800
801 /* Duplicate ECMB */
802 uint64_t ecmb2_offset = (ecmb_offset + ecmb_total + alignment_mask) & ~alignment_mask;
803 aaruf_fseek(ctx->imageStream, (aaru_off_t)ecmb2_offset, SEEK_SET);
804 fwrite(&ecmb, sizeof(ErasureCodingMapHeader), 1, ctx->imageStream);
805 fwrite(payload, payload_len, 1, ctx->imageStream);
806 free(payload);
807
808 /* Recovery footer */
809 AaruRecoveryFooter footer;
810 memset(&footer, 0, sizeof(footer));
811 footer.ecmbOffset = ecmb_offset; footer.ecmbLength = ecmb_total;
812 footer.headerCrc64 = aaruf_crc64_data((const uint8_t *)&ctx->header, sizeof(AaruHeaderV2));
813 memcpy(&footer.backupHeader, &ctx->header, sizeof(AaruHeaderV2));
815
816 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
817 fwrite(&footer, sizeof(AaruRecoveryFooter), 1, ctx->imageStream);
818
819 TRACE("Wrote ECMB at offset %" PRIu64 " (%u groups, %" PRIu64 " bytes), footer at EOF",
820 ecmb_offset, group_count, ecmb_total);
821}
822
823/* =========================================================================
824 * Generic block recovery (any group)
825 * ========================================================================= */
826
827int32_t ec_recover_raw_block(aaruformat_context *ctx, uint64_t block_offset,
828 uint8_t **recovered_data, uint32_t *recovered_size,
829 void *stripes_ptr, uint32_t stripe_count, void *lookup_ptr,
830 uint16_t group_K, uint16_t group_M, uint32_t group_shard_size)
831{
832 *recovered_data = NULL;
833 *recovered_size = 0;
834
836
837 EcBlockLookupEntry *le = NULL;
838 HASH_FIND(hh, (EcBlockLookupEntry *)lookup_ptr, &block_offset, sizeof(uint64_t), le);
839 if(!le) return AARUF_ERROR_CANNOT_READ_BLOCK;
840
841 ctx->ec_recovery_in_progress = true;
842
843 EcReadStripe *stripes = (EcReadStripe *)stripes_ptr;
844 EcReadStripe *stripe = &stripes[le->stripe_index];
845 uint16_t total_shards = group_K + group_M;
846
847 uint8_t **shards = (uint8_t **)calloc(total_shards, sizeof(uint8_t *));
848 uint8_t *present = (uint8_t *)calloc(total_shards, 1);
849 if(!shards || !present) { free(shards); free(present); ctx->ec_recovery_in_progress = false; return AARUF_ERROR_NOT_ENOUGH_MEMORY; }
850
851 for(uint16_t i = 0; i < total_shards; i++)
852 {
853 shards[i] = (uint8_t *)calloc(1, group_shard_size);
854 if(!shards[i]) { for(uint16_t j = 0; j < i; j++) free(shards[j]); free(shards); free(present); ctx->ec_recovery_in_progress = false; return AARUF_ERROR_NOT_ENOUGH_MEMORY; }
855 }
856
857 /* Read data shards, verify CRC */
858 for(uint16_t k = 0; k < stripe->actual_k; k++)
859 {
860 StripeDataBlockEntry *de = &stripe->data_entries[k];
861 uint32_t read_size = de->onDiskSize > group_shard_size ? group_shard_size : de->onDiskSize;
862 aaruf_fseek(ctx->imageStream, (aaru_off_t)de->offset, SEEK_SET);
863 if(fread(shards[k], read_size, 1, ctx->imageStream) != 1) { present[k] = 0; continue; }
864 uint64_t crc = aaruf_crc64_data(shards[k], de->onDiskSize);
865 present[k] = (crc == de->shardCrc64) ? 1 : 0;
866 }
867
868 for(uint16_t k = stripe->actual_k; k < group_K; k++)
869 present[k] = 1;
870
871 /* Read parity shards */
872 for(uint16_t m = 0; m < group_M; m++)
873 {
874 uint16_t si = group_K + m;
875 aaruf_fseek(ctx->imageStream, (aaru_off_t)stripe->parity_offsets[m], SEEK_SET);
876 BlockHeader ph;
877 if(fread(&ph, sizeof(BlockHeader), 1, ctx->imageStream) != 1) { present[si] = 0; continue; }
878 uint32_t to_read = ph.length > group_shard_size ? group_shard_size : ph.length;
879 if(fread(shards[si], to_read, 1, ctx->imageStream) != 1) { present[si] = 0; continue; }
880 present[si] = 1;
881 }
882
883 rs_context *rs = rs_create(group_K, group_M);
884 if(!rs) { for(uint16_t i = 0; i < total_shards; i++) free(shards[i]); free(shards); free(present); ctx->ec_recovery_in_progress = false; return AARUF_ERROR_NOT_ENOUGH_MEMORY; }
885
886 int rc = rs_decode(rs, shards, present, group_shard_size);
887 rs_free(rs);
888
889 int32_t result = AARUF_ERROR_CANNOT_READ_BLOCK;
890 if(rc == 0)
891 {
892 uint16_t pos = le->position;
893 uint32_t sz = stripe->data_entries[pos].onDiskSize;
894 *recovered_data = (uint8_t *)malloc(sz);
895 if(*recovered_data)
896 {
897 memcpy(*recovered_data, shards[pos], sz);
898 *recovered_size = sz;
899 result = AARUF_STATUS_OK;
900 }
901 }
902
903 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
904 free(shards); free(present);
905 ctx->ec_recovery_in_progress = false;
906 return result;
907}
908
909int32_t ec_recover_meta_block(aaruformat_context *ctx, uint64_t block_offset,
910 uint8_t **recovered_data, uint32_t *recovered_size)
911{
913 return ec_recover_raw_block(ctx, block_offset, recovered_data, recovered_size,
915 ctx->ec_meta_K, ctx->ec_meta_M, ctx->ec_meta_shard_size);
916}
917
918int32_t ec_recover_ddt_block(aaruformat_context *ctx, uint64_t block_offset,
919 uint8_t **recovered_data, uint32_t *recovered_size)
920{
922 return ec_recover_raw_block(ctx, block_offset, recovered_data, recovered_size,
924 ctx->ec_ddt_K, ctx->ec_ddt_M, ctx->ec_ddt_shard_size);
925}
926
935{
936 /* The Reed-Solomon context is used by both the write and the read paths */
937 if(ctx->ec_rs_ctx)
938 {
940 ctx->ec_rs_ctx = NULL;
941 }
942
943 /* Free write-path state, only present when erasure coding was enabled for writing */
944 if(ctx->ec_enabled)
945 {
946 if(ctx->ec_data_parity)
947 {
948 for(uint32_t i = 0; i < (uint32_t)ctx->ec_K * ctx->ec_M; i++)
949 free(ctx->ec_data_parity[i]);
950 free(ctx->ec_data_parity);
951 ctx->ec_data_parity = NULL;
952 }
953
954 free(ctx->ec_data_block_offsets); ctx->ec_data_block_offsets = NULL;
955 free(ctx->ec_data_block_sizes); ctx->ec_data_block_sizes = NULL;
956 free(ctx->ec_data_shard_crcs); ctx->ec_data_shard_crcs = NULL;
957 free(ctx->ec_data_stripe_counts); ctx->ec_data_stripe_counts = NULL;
958
959 if(ctx->ec_data_stripes)
960 {
961 utarray_free(ctx->ec_data_stripes);
962 ctx->ec_data_stripes = NULL;
963 }
964
965 ctx->ec_enabled = false;
966 }
967
968 /* Free read-path state */
969 if(ctx->ec_read_stripes)
970 {
971 EcReadStripe *stripes = (EcReadStripe *)ctx->ec_read_stripes;
972 for(uint32_t i = 0; i < ctx->ec_read_stripe_count; i++)
973 {
974 free(stripes[i].data_entries);
975 free(stripes[i].parity_offsets);
976 }
977 free(stripes);
978 ctx->ec_read_stripes = NULL;
979 }
980 ctx->ec_read_stripe_count = 0;
981
982 /* Free block lookup hashmap */
983 if(ctx->ec_block_lookup)
984 {
986 EcBlockLookupEntry *entry, *tmp;
987 HASH_ITER(hh, root, entry, tmp)
988 {
989 HASH_DEL(root, entry);
990 free(entry);
991 }
992 ctx->ec_block_lookup = NULL;
993 }
994
995 /* Free metadata group read-path state */
996 if(ctx->ec_meta_stripes)
997 {
998 EcReadStripe *stripes = (EcReadStripe *)ctx->ec_meta_stripes;
999 for(uint32_t i = 0; i < ctx->ec_meta_stripe_count; i++)
1000 {
1001 free(stripes[i].data_entries);
1002 free(stripes[i].parity_offsets);
1003 }
1004 free(stripes);
1005 ctx->ec_meta_stripes = NULL;
1006 }
1007 ctx->ec_meta_stripe_count = 0;
1008
1009 if(ctx->ec_meta_block_lookup)
1010 {
1012 EcBlockLookupEntry *entry, *tmp;
1013 HASH_ITER(hh, root, entry, tmp) { HASH_DEL(root, entry); free(entry); }
1014 ctx->ec_meta_block_lookup = NULL;
1015 }
1016
1017 /* Free DDT-secondary group read-path state */
1018 if(ctx->ec_ddt_stripes)
1019 {
1020 EcReadStripe *stripes = (EcReadStripe *)ctx->ec_ddt_stripes;
1021 for(uint32_t i = 0; i < ctx->ec_ddt_stripe_count; i++)
1022 {
1023 free(stripes[i].data_entries);
1024 free(stripes[i].parity_offsets);
1025 }
1026 free(stripes);
1027 ctx->ec_ddt_stripes = NULL;
1028 }
1029 ctx->ec_ddt_stripe_count = 0;
1030
1031 if(ctx->ec_ddt_block_lookup)
1032 {
1034 EcBlockLookupEntry *entry, *tmp;
1035 HASH_ITER(hh, root, entry, tmp) { HASH_DEL(root, entry); free(entry); }
1036 ctx->ec_ddt_block_lookup = NULL;
1037 }
1038
1039 ctx->ec_recovery_available = false;
1040}
1041
1042/* =========================================================================
1043 * ECMB loading (read path)
1044 * ========================================================================= */
1045
1047{
1048 TRACE("Entering ec_load_ecmb(%p)", (void *)ctx);
1049
1050 /* Read recovery footer from last sizeof(AaruRecoveryFooter) bytes of file */
1051 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
1052 int64_t file_size = aaruf_ftell(ctx->imageStream);
1053 if(file_size < (int64_t)sizeof(AaruRecoveryFooter))
1054 {
1055 TRACE("File too small for recovery footer");
1056 return;
1057 }
1058
1059 aaruf_fseek(ctx->imageStream, (aaru_off_t)(file_size - (int64_t)sizeof(AaruRecoveryFooter)), SEEK_SET);
1060
1061 AaruRecoveryFooter footer;
1062 if(fread(&footer, sizeof(AaruRecoveryFooter), 1, ctx->imageStream) != 1)
1063 {
1064 TRACE("Cannot read recovery footer");
1065 return;
1066 }
1067
1069 {
1070 TRACE("Recovery footer magic mismatch: 0x%016" PRIx64, footer.footerMagic);
1071 return;
1072 }
1073
1074 /* Read ECMB header */
1075 aaruf_fseek(ctx->imageStream, (aaru_off_t)footer.ecmbOffset, SEEK_SET);
1076
1078 if(fread(&ecmb, sizeof(ErasureCodingMapHeader), 1, ctx->imageStream) != 1)
1079 {
1080 TRACE("Cannot read ECMB header");
1081 return;
1082 }
1083
1085 {
1086 TRACE("ECMB identifier mismatch");
1087 return;
1088 }
1089
1090 /* Read payload (uncompressed only for now) */
1091 if(ecmb.length == 0 || ecmb.length > 256 * 1024 * 1024) return; /* sanity limit */
1092
1093 uint8_t *payload = (uint8_t *)malloc((size_t)ecmb.length);
1094 if(!payload) return;
1095
1096 if(ecmb.compression == kCompressionNone)
1097 {
1098 if(fread(payload, (size_t)ecmb.cmpLength, 1, ctx->imageStream) != 1)
1099 {
1100 free(payload);
1101 return;
1102 }
1103 }
1104 else
1105 {
1106 /* Compressed ECMB payload — read compressed, decompress */
1107 uint8_t *cmp = (uint8_t *)malloc((size_t)ecmb.cmpLength);
1108 if(!cmp) { free(payload); return; }
1109 if(fread(cmp, (size_t)ecmb.cmpLength, 1, ctx->imageStream) != 1) { free(cmp); free(payload); return; }
1110
1111 if(ecmb.compression == kCompressionLzma)
1112 {
1113 size_t out_size = (size_t)ecmb.length;
1114 size_t lzma_src_size = (size_t)ecmb.cmpLength - LZMA_PROPERTIES_LENGTH;
1115 aaruf_lzma_decode_buffer(payload, &out_size, cmp + LZMA_PROPERTIES_LENGTH,
1116 &lzma_src_size, cmp, LZMA_PROPERTIES_LENGTH);
1117 }
1118 else if(ecmb.compression == kCompressionZstd)
1119 {
1120 aaruf_zstd_decode_buffer(payload, (size_t)ecmb.length, cmp, (size_t)ecmb.cmpLength);
1121 }
1122 free(cmp);
1123 }
1124
1125 /* Verify CRC64 */
1126 uint64_t computed_crc = aaruf_crc64_data(payload, (uint32_t)ecmb.length);
1127 if(computed_crc != ecmb.crc64)
1128 {
1129 TRACE("ECMB payload CRC64 mismatch");
1130 free(payload);
1131 return;
1132 }
1133
1134 ctx->ec_algorithm = ecmb.algorithm;
1135
1136 /* Parse all stripe groups */
1137 uint8_t *p = payload;
1138 size_t remaining = (size_t)ecmb.length;
1139
1140 EcBlockLookupEntry *lookup_root = NULL;
1141 EcReadStripe *data_stripes = NULL;
1142 uint32_t data_stripe_count = 0;
1143
1144 for(uint8_t g = 0; g < ecmb.stripeGroupCount && remaining >= sizeof(StripeGroupDescriptor); g++)
1145 {
1147 memcpy(&group, p, sizeof(StripeGroupDescriptor));
1148 p += sizeof(StripeGroupDescriptor);
1149 remaining -= sizeof(StripeGroupDescriptor);
1150
1151 /* Parse stripes for this group */
1152 EcReadStripe *grp_stripes = NULL;
1153 if(group.stripeCount > 0)
1154 {
1155 grp_stripes = (EcReadStripe *)calloc(group.stripeCount, sizeof(EcReadStripe));
1156 if(!grp_stripes) break;
1157 }
1158
1159 for(uint32_t s = 0; s < group.stripeCount; s++)
1160 {
1161 if(remaining < sizeof(uint16_t)) break;
1162 uint16_t ak;
1163 memcpy(&ak, p, sizeof(uint16_t)); p += sizeof(uint16_t); remaining -= sizeof(uint16_t);
1164 if(grp_stripes) grp_stripes[s].actual_k = ak;
1165
1166 size_t data_bytes = (size_t)ak * sizeof(StripeDataBlockEntry);
1167 size_t parity_bytes = (size_t)group.M * sizeof(StripeParityBlockEntry);
1168 if(remaining < data_bytes + parity_bytes) break;
1169
1170 if(grp_stripes)
1171 {
1172 grp_stripes[s].data_entries = (StripeDataBlockEntry *)malloc(data_bytes);
1173 if(grp_stripes[s].data_entries)
1174 memcpy(grp_stripes[s].data_entries, p, data_bytes);
1175
1176 grp_stripes[s].parity_offsets = (uint64_t *)malloc((size_t)group.M * sizeof(uint64_t));
1177 }
1178
1179 p += data_bytes; remaining -= data_bytes;
1180
1181 for(uint16_t m = 0; m < group.M; m++)
1182 {
1184 memcpy(&pe, p, sizeof(StripeParityBlockEntry));
1185 p += sizeof(StripeParityBlockEntry); remaining -= sizeof(StripeParityBlockEntry);
1186 if(grp_stripes && grp_stripes[s].parity_offsets)
1187 grp_stripes[s].parity_offsets[m] = pe.offset;
1188 }
1189
1190 /* Build lookup hashmap for data group blocks */
1191 if(group.groupType == kECGroupData && grp_stripes && grp_stripes[s].data_entries)
1192 {
1193 for(uint16_t k = 0; k < ak; k++)
1194 {
1195 EcBlockLookupEntry *le = (EcBlockLookupEntry *)calloc(1, sizeof(EcBlockLookupEntry));
1196 if(!le) break;
1197 le->block_offset = grp_stripes[s].data_entries[k].offset;
1198 le->stripe_index = s;
1199 le->position = k;
1200 HASH_ADD(hh, lookup_root, block_offset, sizeof(uint64_t), le);
1201 }
1202 }
1203 }
1204
1205 /* Store parsed data based on group type */
1206 if(group.groupType == kECGroupData)
1207 {
1208 ctx->ec_K = group.K;
1209 ctx->ec_M = group.M;
1210 ctx->ec_data_shard_size = group.shardSize;
1211 data_stripes = grp_stripes;
1212 data_stripe_count = group.stripeCount;
1213 }
1214 else if(group.groupType == kECGroupDdtSecondary)
1215 {
1216 /* Store DDT-secondary group for recovery of secondary DDT subtables */
1218 if(grp_stripes)
1219 {
1220 for(uint32_t s = 0; s < group.stripeCount; s++)
1221 {
1222 if(!grp_stripes[s].data_entries) continue;
1223 for(uint16_t k = 0; k < grp_stripes[s].actual_k; k++)
1224 {
1225 EcBlockLookupEntry *le = (EcBlockLookupEntry *)calloc(1, sizeof(EcBlockLookupEntry));
1226 if(!le) break;
1227 le->block_offset = grp_stripes[s].data_entries[k].offset;
1228 le->stripe_index = s;
1229 le->position = k;
1230 HASH_ADD(hh, ddt_lookup_root, block_offset, sizeof(uint64_t), le);
1231 }
1232 }
1233 }
1234 ctx->ec_ddt_stripes = grp_stripes;
1235 ctx->ec_ddt_stripe_count = group.stripeCount;
1236 ctx->ec_ddt_K = group.K;
1237 ctx->ec_ddt_M = group.M;
1238 ctx->ec_ddt_shard_size = group.shardSize;
1239 ctx->ec_ddt_block_lookup = ddt_lookup_root;
1240 }
1241 else if(group.groupType == kECGroupMetadata)
1242 {
1243 /* Store metadata group for recovery of media tags, tracks, checksums, etc. */
1245 if(grp_stripes)
1246 {
1247 for(uint32_t s = 0; s < group.stripeCount; s++)
1248 {
1249 if(!grp_stripes[s].data_entries) continue;
1250 for(uint16_t k = 0; k < grp_stripes[s].actual_k; k++)
1251 {
1252 EcBlockLookupEntry *le = (EcBlockLookupEntry *)calloc(1, sizeof(EcBlockLookupEntry));
1253 if(!le) break;
1254 le->block_offset = grp_stripes[s].data_entries[k].offset;
1255 le->stripe_index = s;
1256 le->position = k;
1257 HASH_ADD(hh, meta_lookup_root, block_offset, sizeof(uint64_t), le);
1258 }
1259 }
1260 }
1261 ctx->ec_meta_stripes = grp_stripes;
1262 ctx->ec_meta_stripe_count = group.stripeCount;
1263 ctx->ec_meta_K = group.K;
1264 ctx->ec_meta_M = group.M;
1265 ctx->ec_meta_shard_size = group.shardSize;
1266 ctx->ec_meta_block_lookup = meta_lookup_root;
1267 }
1268 else
1269 {
1270 /* For DDT/index groups, free for now (recovery handled by replicas). */
1271 if(grp_stripes)
1272 {
1273 for(uint32_t s = 0; s < group.stripeCount; s++)
1274 {
1275 free(grp_stripes[s].data_entries);
1276 free(grp_stripes[s].parity_offsets);
1277 }
1278 free(grp_stripes);
1279 }
1280 }
1281 }
1282
1283 free(payload);
1284
1285 ctx->ec_read_stripes = data_stripes;
1286 ctx->ec_read_stripe_count = data_stripe_count;
1287 ctx->ec_block_lookup = lookup_root;
1288 ctx->ec_recovery_available = (data_stripes != NULL && data_stripe_count > 0) ||
1289 (ctx->ec_meta_stripes != NULL && ctx->ec_meta_stripe_count > 0);
1290
1291 /* Create RS codec for decoding */
1292 if(ctx->ec_recovery_available && !ctx->ec_rs_ctx)
1293 ctx->ec_rs_ctx = rs_create(ctx->ec_K, ctx->ec_M);
1294
1295 TRACE("ECMB loaded: %u groups, K=%u M=%u shard_size=%u data_stripes=%u",
1296 ecmb.stripeGroupCount, ctx->ec_K, ctx->ec_M, ctx->ec_data_shard_size, data_stripe_count);
1297}
1298
1299/* =========================================================================
1300 * Data block recovery (read path)
1301 * ========================================================================= */
1302
1303int32_t ec_recover_data_block(aaruformat_context *ctx, uint64_t block_offset, uint64_t offset,
1304 uint8_t *data, uint32_t *length, uint8_t sector_status)
1305{
1307
1308 ctx->ec_recovery_in_progress = true;
1309
1310 /* Look up which stripe this block belongs to */
1311 EcBlockLookupEntry *le = NULL;
1312 HASH_FIND(hh, (EcBlockLookupEntry *)ctx->ec_block_lookup, &block_offset, sizeof(uint64_t), le);
1313 if(!le) { ctx->ec_recovery_in_progress = false; return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK; }
1314
1315 uint32_t si = le->stripe_index;
1316 EcReadStripe *stripes = (EcReadStripe *)ctx->ec_read_stripes;
1317 EcReadStripe *stripe = &stripes[si];
1318 uint16_t K = ctx->ec_K;
1319 uint16_t M = ctx->ec_M;
1320 uint32_t shard_size = ctx->ec_data_shard_size;
1321
1322 /* Always use K+M shards for RS. For partial stripes (actual_k < K),
1323 * positions actual_k..K-1 are all-zero (calloc'd) and marked present.
1324 * This works because the encoding used the K-size generator matrix
1325 * and zero-contributions for unused positions. */
1326 uint16_t total_shards = K + M;
1327
1328 /* Allocate shard pointers and present flags */
1329 uint8_t **shards = (uint8_t **)calloc(total_shards, sizeof(uint8_t *));
1330 uint8_t *present = (uint8_t *)calloc(total_shards, 1);
1331 if(!shards || !present) { free(shards); free(present); ctx->ec_recovery_in_progress = false; return AARUF_ERROR_NOT_ENOUGH_MEMORY; }
1332
1333 for(uint16_t i = 0; i < total_shards; i++)
1334 {
1335 shards[i] = (uint8_t *)calloc(1, shard_size);
1336 if(!shards[i])
1337 {
1338 for(uint16_t j = 0; j < i; j++) free(shards[j]);
1339 free(shards); free(present);
1340 ctx->ec_recovery_in_progress = false;
1342 }
1343 }
1344
1345 /* Read data shards from file and verify each one's CRC64 against ECMB */
1346 for(uint16_t k = 0; k < stripe->actual_k; k++)
1347 {
1348 StripeDataBlockEntry *de = &stripe->data_entries[k];
1349 uint32_t read_size = de->onDiskSize;
1350 if(read_size > shard_size) read_size = shard_size;
1351
1352 aaruf_fseek(ctx->imageStream, (aaru_off_t)de->offset, SEEK_SET);
1353 if(fread(shards[k], read_size, 1, ctx->imageStream) != 1)
1354 {
1355 present[k] = 0;
1356 continue;
1357 }
1358
1359 /* Verify CRC64 over actual on-disk size (not shard_size) */
1360 uint64_t crc = aaruf_crc64_data(shards[k], de->onDiskSize);
1361 present[k] = (crc == de->shardCrc64) ? 1 : 0;
1362 }
1363
1364 /* Positions actual_k..K-1 are all-zero and present (unused stripe positions) */
1365 for(uint16_t k = stripe->actual_k; k < K; k++)
1366 present[k] = 1; /* All-zero shards, implicitly correct */
1367
1368 /* Read parity shards */
1369 for(uint16_t m = 0; m < M; m++)
1370 {
1371 uint16_t shard_idx = K + m;
1372 uint64_t parity_offset = stripe->parity_offsets[m];
1373
1374 aaruf_fseek(ctx->imageStream, (aaru_off_t)parity_offset, SEEK_SET);
1375
1376 /* Read parity block header */
1377 BlockHeader parity_header;
1378 if(fread(&parity_header, sizeof(BlockHeader), 1, ctx->imageStream) != 1)
1379 {
1380 present[shard_idx] = 0;
1381 continue;
1382 }
1383
1384 /* Read and decompress parity payload */
1385 if(parity_header.compression == kCompressionNone)
1386 {
1387 uint32_t to_read = parity_header.length;
1388 if(to_read > shard_size) to_read = shard_size;
1389 if(fread(shards[shard_idx], to_read, 1, ctx->imageStream) != 1)
1390 {
1391 present[shard_idx] = 0;
1392 continue;
1393 }
1394 }
1395 else if(parity_header.compression == kCompressionLzma)
1396 {
1397 uint32_t cmp_data_len = parity_header.cmpLength - LZMA_PROPERTIES_LENGTH;
1398 uint8_t lzma_props[LZMA_PROPERTIES_LENGTH];
1399 if(fread(lzma_props, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream) != 1 )
1400 {
1401 present[shard_idx] = 0;
1402 continue;
1403 }
1404 uint8_t *cmp = (uint8_t *)malloc(cmp_data_len);
1405 if(!cmp) { present[shard_idx] = 0; continue; }
1406 if(fread(cmp, cmp_data_len, 1, ctx->imageStream) != 1) { free(cmp); present[shard_idx] = 0; continue; }
1407
1408 size_t out_size = shard_size;
1409 size_t lzma_src = (size_t)cmp_data_len;
1410 aaruf_lzma_decode_buffer(shards[shard_idx], &out_size, cmp, &lzma_src, lzma_props, LZMA_PROPERTIES_LENGTH);
1411 free(cmp);
1412 }
1413 else if(parity_header.compression == kCompressionZstd)
1414 {
1415 uint8_t *cmp = (uint8_t *)malloc(parity_header.cmpLength);
1416 if(!cmp) { present[shard_idx] = 0; continue; }
1417 if(fread(cmp, parity_header.cmpLength, 1, ctx->imageStream) != 1) { free(cmp); present[shard_idx] = 0; continue; }
1418
1419 aaruf_zstd_decode_buffer(shards[shard_idx], shard_size, cmp, parity_header.cmpLength);
1420 free(cmp);
1421 }
1422 else
1423 {
1424 present[shard_idx] = 0;
1425 continue;
1426 }
1427 present[shard_idx] = 1;
1428 }
1429
1430 /* Always use the original RS(K,M) codec — partial stripes have zero-padded unused positions */
1431 rs_context *rs = (rs_context *)ctx->ec_rs_ctx;
1432 if(!rs)
1433 {
1434 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
1435 free(shards); free(present);
1436 ctx->ec_recovery_in_progress = false;
1438 }
1439
1440 /* RS decode */
1441 int rc = rs_decode(rs, shards, present, shard_size);
1442
1443 if(rc != 0)
1444 {
1445 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
1446 free(shards); free(present);
1447 ctx->ec_recovery_in_progress = false;
1449 }
1450
1451 /* Find the shard corresponding to our corrupted block */
1452 uint16_t our_pos = le->position;
1453 uint8_t *recovered_shard = shards[our_pos];
1454
1455 /* Parse the recovered BlockHeader */
1456 BlockHeader recovered_header;
1457 memcpy(&recovered_header, recovered_shard, sizeof(BlockHeader));
1458
1459 /* Decompress the recovered payload */
1460 uint32_t hdr_size = sizeof(BlockHeader);
1461 uint8_t *recovered_payload = recovered_shard + hdr_size;
1462 uint32_t payload_len = recovered_header.cmpLength;
1463
1464 uint8_t *block = NULL;
1465
1466 if(recovered_header.compression == kCompressionNone)
1467 {
1468 block = (uint8_t *)malloc(recovered_header.length);
1469 if(block) memcpy(block, recovered_payload, recovered_header.length);
1470 }
1471 else if(recovered_header.compression == kCompressionLzma)
1472 {
1473 uint8_t *lzma_props = recovered_payload;
1474 uint8_t *lzma_data = recovered_payload + LZMA_PROPERTIES_LENGTH;
1475 uint32_t lzma_data_len = payload_len - LZMA_PROPERTIES_LENGTH;
1476
1477 block = (uint8_t *)malloc(recovered_header.length);
1478 if(block)
1479 {
1480 size_t out_size = recovered_header.length;
1481 size_t lzma_src2 = (size_t)lzma_data_len;
1482 aaruf_lzma_decode_buffer(block, &out_size, lzma_data, &lzma_src2, lzma_props, LZMA_PROPERTIES_LENGTH);
1483 }
1484 }
1485 else if(recovered_header.compression == kCompressionZstd)
1486 {
1487 block = (uint8_t *)malloc(recovered_header.length);
1488 if(block)
1489 aaruf_zstd_decode_buffer(block, recovered_header.length, recovered_payload, payload_len);
1490 }
1491 else if(recovered_header.compression == kCompressionFlac)
1492 {
1493 block = (uint8_t *)malloc(recovered_header.length);
1494 if(block)
1495 aaruf_flac_decode_redbook_buffer(block, recovered_header.length, recovered_payload, payload_len);
1496 }
1497
1498 int32_t result = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
1499
1500 if(block)
1501 {
1502 /* Verify recovered uncompressed data CRC64 */
1503 uint64_t block_crc = aaruf_crc64_data(block, recovered_header.length);
1504 if(block_crc == recovered_header.crc64)
1505 {
1506 /* Extract the requested sector */
1507 uint32_t sector_size = recovered_header.sectorSize;
1508 if(sector_size > 0 && offset * sector_size + sector_size <= recovered_header.length)
1509 {
1510 memcpy(data, block + offset * sector_size, sector_size);
1511 *length = sector_size;
1512 result = AARUF_STATUS_OK;
1513
1514 /* Cache the recovered block so subsequent sector reads from the same
1515 * block don't re-trigger recovery (this is the critical optimization). */
1516 add_to_cache_uint64(&ctx->block_cache, block_offset, block, recovered_header.length);
1517
1518 /* Also cache the recovered BlockHeader */
1519 BlockHeader *cached_hdr = (BlockHeader *)malloc(sizeof(BlockHeader));
1520 if(cached_hdr)
1521 {
1522 memcpy(cached_hdr, &recovered_header, sizeof(BlockHeader));
1523 add_to_cache_uint64(&ctx->block_header_cache, block_offset, cached_hdr, sizeof(BlockHeader));
1524 }
1525
1526 block = NULL; /* Ownership transferred to cache — don't free */
1527 }
1528 }
1529 free(block); /* Only frees if not transferred to cache */
1530 }
1531
1532 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
1533 free(shards);
1534 free(present);
1535
1536 ctx->ec_recovery_in_progress = false;
1537 return result;
1538}
Core public constants and compile‑time limits for the Aaru container format implementation.
#define AARU_RECOVERY_FOOTER_MAGIC
Magic number at the end of the recovery footer: "AVRECMFR" in ASCII little-endian.
Definition consts.h:119
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:86
#define AARU_MAGIC
Magic identifier for AaruFormat container (ASCII "AARUFRMT").
Definition consts.h:64
Central runtime context structures for libaaruformat (image state, caches, checksum buffers).
On-disk layout structures for data-bearing and geometry blocks.
On-disk headers for Deduplication Data Tables (DDT) versions 1 and 2.
#define AARU_CALL
Definition decls.h:46
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
int32_t aaruf_set_erasure_coding_auto(void *context, uint8_t recovery_percent)
Configure erasure coding from a desired recovery percentage.
Definition erasure.c:174
int32_t aaruf_set_erasure_coding(void *context, uint8_t algorithm, uint16_t K, uint16_t M)
Configure erasure coding for a newly created image.
Definition erasure.c:82
#define AARU_EXPORT
Definition decls.h:55
size_t aaruf_zstd_decode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, size_t src_size)
Decodes a Zstandard-compressed buffer.
Definition zstd.c:34
size_t aaruf_flac_decode_redbook_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, size_t src_size)
Decodes a FLAC-compressed Red Book audio buffer.
Definition flac.c:48
@ kECGroupMetadata
Metadata/media tag blocks.
Definition enums.h:341
@ kECGroupIndex
Index block (K=1, M replicas).
Definition enums.h:342
@ kECGroupDdtPrimary
Primary DDT (K=1, M replicas).
Definition enums.h:340
@ kECGroupData
User data blocks (DBLK).
Definition enums.h:338
@ kECGroupDdtSecondary
Secondary DDT subtables.
Definition enums.h:339
@ IndexBlock3
Block containing the index v3.
Definition enums.h:171
@ DataBlock
Block containing data.
Definition enums.h:164
@ IndexBlock2
Block containing the index v2.
Definition enums.h:170
@ DeDuplicationTableSecondary
Block containing a secondary deduplication table (v2).
Definition enums.h:168
@ IndexBlock
Block containing the index (v1).
Definition enums.h:169
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:166
@ DeDuplicationTable
Block containing a deduplication table (v1).
Definition enums.h:165
@ ErasureCodingMapBlock
Block containing erasure coding stripe map and recovery metadata.
Definition enums.h:187
@ AARU_FEATURE_ROCOMPAT_ERASURE
Image contains erasure coding parity blocks and recovery metadata.
Definition enums.h:319
@ kErasureCodingRsVandermonde
Reed-Solomon with Vandermonde generator matrix over GF(2^8).
Definition enums.h:329
@ kErasureCodingXor
Simple XOR parity (M must be 1).
Definition enums.h:328
@ kDataTypeErasureParityDdt
Erasure coding parity shard for DDT secondary blocks.
Definition enums.h:152
@ kDataTypeErasureParityDdtPrimary
Erasure coding parity replica for DDT primary block.
Definition enums.h:153
@ kDataTypeErasureParityIndex
Erasure coding parity replica for index block.
Definition enums.h:155
@ kDataTypeErasureParityMeta
Erasure coding parity shard for metadata blocks.
Definition enums.h:154
@ kDataTypeUserData
User (main) data.
Definition enums.h:48
@ kDataTypeErasureParity
Erasure coding parity shard for data blocks.
Definition enums.h:151
@ AARUF_STATUS_INVALID_CONTEXT
Provided context/handle is invalid.
Definition enums.h:237
@ kCompressionLzma
LZMA compression.
Definition enums.h:34
@ kCompressionNone
Not compressed.
Definition enums.h:33
@ kCompressionZstd
Zstandard compression.
Definition enums.h:37
@ kCompressionFlac
FLAC compression.
Definition enums.h:35
static void ec_collect_blocks_by_type(aaruformat_context *ctx, uint32_t block_type, uint64_t **out_offsets, uint32_t **out_sizes, uint32_t *out_count)
Collect file offsets and on-disk sizes for index entries matching a block type.
Definition erasure.c:537
static UT_icd ec_stripe_icd
Definition erasure.c:65
#define WRITE_GROUP(desc, desc_len, grp)
static void ec_write_batch_parity(aaruformat_context *ctx, const uint64_t *offsets, const uint32_t *sizes, uint32_t block_count, uint8_t group_type, uint16_t parity_data_type, uint8_t **out_desc, size_t *out_desc_len, StripeGroupDescriptor *out_group)
Compute and write batch parity for a set of blocks already on disk.
Definition erasure.c:439
On-disk structures for erasure coding recovery data.
void ec_load_ecmb(aaruformat_context *ctx)
Try to load the ECMB from the recovery footer at EOF.
Definition erasure.c:1046
void ec_finalize(aaruformat_context *ctx)
Flush all partial data stripes and write parity for all groups + ECMB + recovery footer.
Definition erasure.c:585
int32_t ec_recover_ddt_block(aaruformat_context *ctx, uint64_t block_offset, uint8_t **recovered_data, uint32_t *recovered_size)
Attempt to recover a DDT secondary block at a given file offset.
Definition erasure.c:918
void ec_accumulate_data_block(aaruformat_context *ctx, const BlockHeader *block_header, const uint8_t *lzma_props, const uint8_t *payload, uint32_t payload_size, uint64_t file_offset)
Accumulate parity for a data block that was just written to disk.
Definition erasure.c:209
int32_t ec_recover_data_block(aaruformat_context *ctx, uint64_t block_offset, uint64_t offset, uint8_t *data, uint32_t *length, uint8_t sector_status)
Attempt to recover a data block that failed decompression or CRC verification.
Definition erasure.c:1303
int32_t ec_recover_meta_block(aaruformat_context *ctx, uint64_t block_offset, uint8_t **recovered_data, uint32_t *recovered_size)
Attempt to recover a metadata/media-tag block at a given file offset.
Definition erasure.c:909
void ec_flush_data_stripe(aaruformat_context *ctx, uint32_t slot)
Write M parity blocks for a completed data stripe slot and record the stripe descriptor.
Definition erasure.c:323
void ec_free(aaruformat_context *ctx)
Free all erasure coding state from the context.
Definition erasure.c:934
int32_t ec_recover_raw_block(aaruformat_context *ctx, uint64_t block_offset, uint8_t **recovered_data, uint32_t *recovered_size, void *stripes, uint32_t stripe_count, void *lookup, uint16_t group_K, uint16_t group_M, uint32_t group_shard_size)
Attempt to recover a raw block at a given file offset using a specified stripe group.
Definition erasure.c:827
Public error and status code definitions for libaaruformat.
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:82
#define AARUF_READ_ONLY
Operation requires write mode but context is read-only.
Definition errors.h:61
#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_INCORRECT_DATA_SIZE
Data size does not match expected size.
Definition errors.h:65
#define AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK
Decompression routine failed or size mismatch.
Definition errors.h:56
On‑disk index block header and entry structures (versions 1, 2 and 3).
static int aaruf_fseek(FILE *stream, aaru_off_t offset, int origin)
Definition internal.h:46
static aaru_off_t aaruf_ftell(FILE *stream)
Definition internal.h:52
int64_t aaru_off_t
Definition internal.h:42
#define TRACE(fmt,...)
Definition log.h:25
void add_to_cache_uint64(struct CacheHeader *cache, uint64_t key, void *value, size_t size)
Adds a value to the cache with a uint64_t key, evicting LRU entries if over budget.
Definition lru.c:68
void rs_free(rs_context *ctx)
Free a Reed-Solomon codec context.
rs_context * rs_create(uint16_t K, uint16_t M)
Create a Reed-Solomon codec for RS(K, M) over GF(2^8).
void rs_encode_incremental(uint8_t coeff, const uint8_t *data, uint8_t *parity, size_t shard_size)
Incrementally accumulate one data shard's contribution to one parity shard.
uint8_t rs_get_coefficient(const rs_context *ctx, uint16_t m, uint16_t k)
Get the generator matrix coefficient for parity shard m, data shard k.
int rs_decode(const rs_context *ctx, uint8_t **shards, const uint8_t *present, size_t shard_size)
Decode (reconstruct) erased shards.
Version 2 container header with GUID, alignment shifts, and feature negotiation bitmaps.
Definition header.h:107
uint64_t indexOffset
Absolute byte offset to primary index block (MUST be > 0; 0 => corrupt/unreadable).
Definition header.h:115
uint64_t featureCompatibleRo
Feature bits: unimplemented -> degrade to read-only access.
Definition header.h:122
Recovery footer written at the very end of the file (last 160 bytes).
Definition erasure.h:117
uint64_t headerCrc64
CRC64-ECMA of the original AaruHeaderV2 (147 bytes at offset 0).
Definition erasure.h:120
uint64_t ecmbOffset
Absolute file offset of the primary ECMB.
Definition erasure.h:118
uint64_t footerMagic
Must be AARU_RECOVERY_FOOTER_MAGIC (0x52464D4345525641).
Definition erasure.h:122
uint64_t ecmbLength
Total on-disk size of the ECMB (header + payload).
Definition erasure.h:119
AaruHeaderV2 backupHeader
Complete copy of AaruHeaderV2 from file offset 0.
Definition erasure.h:121
Header preceding the compressed data payload of a data block (BlockType::DataBlock).
Definition data.h:71
uint32_t cmpLength
Size in bytes of the compressed payload immediately following this header.
Definition data.h:76
uint32_t length
Size in bytes of the uncompressed payload resulting after decompression.
Definition data.h:77
uint32_t identifier
Block identifier, must be BlockType::DataBlock.
Definition data.h:72
uint32_t sectorSize
Size in bytes of each logical sector represented in this block.
Definition data.h:75
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload (cmpLength bytes).
Definition data.h:78
uint64_t crc64
CRC64-ECMA of the uncompressed payload (length bytes).
Definition data.h:79
uint16_t type
Logical data classification (value from DataType).
Definition data.h:73
uint16_t compression
Compression algorithm used (value from CompressionType).
Definition data.h:74
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
uint8_t blockAlignmentShift
2^blockAlignmentShift = block alignment boundary in bytes.
Definition ddt.h:154
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
Hash table entry mapping block file offset -> stripe index + position.
Definition erasure.c:55
uint64_t block_offset
Key: file offset of the data block.
Definition erasure.c:56
uint16_t position
Position within the stripe (0..actual_k-1).
Definition erasure.c:58
UT_hash_handle hh
Definition erasure.c:59
uint32_t stripe_index
Index into ec_read_stripes array.
Definition erasure.c:57
In-memory representation of one data stripe (parsed from ECMB).
Definition erasure.c:47
StripeDataBlockEntry * data_entries
Array of actual_k entries.
Definition erasure.c:49
uint16_t actual_k
Number of data blocks in this stripe.
Definition erasure.c:48
uint64_t * parity_offsets
Array of M parity block file offsets.
Definition erasure.c:50
Header for the Erasure Coding Map Block (BlockType::ErasureCodingMapBlock).
Definition erasure.h:50
uint8_t algorithm
Erasure coding algorithm (ErasureCodingAlgorithm).
Definition erasure.h:52
uint64_t cmpLength
Size in bytes of the compressed mapping payload.
Definition erasure.h:55
uint64_t cmpCrc64
CRC64-ECMA of the compressed mapping payload.
Definition erasure.h:57
uint64_t crc64
CRC64-ECMA of the uncompressed mapping payload.
Definition erasure.h:58
uint16_t compression
Compression algorithm for the mapping payload (CompressionType).
Definition erasure.h:54
uint32_t identifier
Block identifier, must be BlockType::ErasureCodingMapBlock (0x424D4345).
Definition erasure.h:51
uint8_t stripeGroupCount
Number of stripe groups in payload (typically 5).
Definition erasure.h:53
uint64_t length
Size in bytes of the uncompressed mapping payload.
Definition erasure.h:56
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
uint16_t dataType
Data classification (value from DataType) or unused for untyped blocks.
Definition index.h:111
Index header (version 3) adding hierarchical chaining (identifier == IndexBlock3).
Definition index.h:93
uint64_t entries
Number of IndexEntry records that follow in this (sub)index block.
Definition index.h:95
Per-data-block metadata within a stripe descriptor.
Definition erasure.h:88
uint32_t onDiskSize
Actual on-disk bytes (sizeof(header) + cmpLength).
Definition erasure.h:90
uint64_t offset
Absolute file offset of the block.
Definition erasure.h:89
uint64_t shardCrc64
CRC64-ECMA of on-disk bytes zero-padded to shardSize.
Definition erasure.h:91
Describes one protection group within the ECMB payload.
Definition erasure.h:69
uint32_t shardSize
Fixed shard size in bytes (max possible on-disk block size for this group).
Definition erasure.h:73
uint16_t K
Number of data blocks per stripe.
Definition erasure.h:71
uint8_t groupType
Protection group type (ErasureCodingGroupType).
Definition erasure.h:70
uint16_t interleaveDepth
Interleave depth D (K for full interleave, 1 for consecutive).
Definition erasure.h:75
uint32_t stripeCount
Number of stripes in this group.
Definition erasure.h:74
uint16_t M
Number of parity blocks per stripe.
Definition erasure.h:72
Per-parity-block metadata within a stripe descriptor.
Definition erasure.h:99
uint64_t offset
Absolute file offset of the parity DBLK.
Definition erasure.h:100
Master context representing an open or in‑creation Aaru image.
Definition context.h:178
uint64_t * ec_data_shard_crcs
Array of K * K CRC64 values for blocks in active stripes.
Definition context.h:396
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:195
uint32_t * ec_data_block_sizes
Array of K * K actual on-disk sizes for blocks in active stripes.
Definition context.h:395
void * ec_rs_ctx
rs_context* (opaque RS codec), NULL if EC disabled.
Definition context.h:392
uint32_t ec_meta_stripe_count
Number of metadata stripes.
Definition context.h:407
uint8_t ec_algorithm
ErasureCodingAlgorithm (0=XOR, 1=RS-Vandermonde).
Definition context.h:388
bool ec_recovery_in_progress
Recursion guard for recovery (prevents infinite loops).
Definition context.h:419
struct CacheHeader block_header_cache
LRU/Cache header for block headers.
Definition context.h:266
uint32_t ec_data_shard_size
Max on-disk block size for data blocks (fixed at creation).
Definition context.h:391
uint32_t ec_read_stripe_count
Number of data stripes parsed from ECMB.
Definition context.h:404
uint32_t ec_ddt_stripe_count
Number of DDT-secondary stripes.
Definition context.h:413
struct CacheHeader block_cache
LRU/Cache header for block payloads.
Definition context.h:267
uint32_t ec_meta_shard_size
Shard size for metadata group.
Definition context.h:410
void * ec_ddt_block_lookup
uthash: block file offset → stripe index + position (DDT group).
Definition context.h:417
bool dirty_index_block
True if index block should be written during close.
Definition context.h:354
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:181
UT_array * ec_data_stripes
Completed data stripe descriptors (serialized to ECMB).
Definition context.h:399
bool is_writing
True if context opened/created for writing.
Definition context.h:302
uint16_t ec_ddt_M
M for DDT-secondary group.
Definition context.h:415
uint64_t magic
File magic (AARU_MAGIC) post-open.
Definition context.h:180
uint16_t ec_meta_M
M for metadata group.
Definition context.h:409
uint64_t next_block_position
Absolute file offset where next block will be written.
Definition context.h:292
uint64_t * ec_data_block_offsets
Array of K * K file offsets for blocks in active stripes.
Definition context.h:394
uint32_t ec_total_data_blocks
Total data blocks written (counter for round-robin assignment).
Definition context.h:398
uint16_t ec_M
Parity blocks per stripe.
Definition context.h:390
uint16_t * ec_data_stripe_counts
Array of K: blocks accumulated per stripe slot.
Definition context.h:397
void * ec_read_stripes
Parsed EcReadStripe array for data group, NULL if no ECMB.
Definition context.h:403
void * ec_block_lookup
uthash: block file offset → stripe index + position (data group).
Definition context.h:405
void * ec_ddt_stripes
Parsed EcReadStripe array for DDT-secondary group.
Definition context.h:412
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:182
void * ec_meta_block_lookup
uthash: block file offset → stripe index + position (metadata group).
Definition context.h:411
UT_array * index_entries
Flattened index entries (UT_array of IndexEntry).
Definition context.h:262
uint32_t ec_ddt_shard_size
Shard size for DDT-secondary group.
Definition context.h:416
bool ec_enabled
True if erasure coding is active.
Definition context.h:400
uint8_t ** ec_data_parity
Array of K * M parity buffers (interleaved stripe slots).
Definition context.h:393
uint16_t ec_ddt_K
K for DDT-secondary group.
Definition context.h:414
void * ec_meta_stripes
Parsed EcReadStripe array for metadata group.
Definition context.h:406
uint16_t ec_meta_K
K for metadata group.
Definition context.h:408
uint16_t ec_K
Data blocks per stripe.
Definition context.h:389
bool ec_recovery_available
True if ECMB loaded and recovery is possible.
Definition context.h:418