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 /* Compute data shard size: max possible on-disk block size.
98 * = sizeof(BlockHeader) + LZMA_PROPERTIES_LENGTH + (1 << dataShift) * sectorSize
99 * This is the worst case: uncompressed block + LZMA properties header. */
100 uint32_t sectors_per_block = 1U << ctx->user_data_ddt_header.dataShift;
101 uint32_t max_payload = sectors_per_block * ctx->current_block_header.sectorSize;
102 if(max_payload == 0) max_payload = sectors_per_block * 512; /* fallback if sectorSize not yet set */
103 uint32_t shard_size = (uint32_t)sizeof(BlockHeader) + LZMA_PROPERTIES_LENGTH + max_payload;
104
105 /* Create RS codec */
106 rs_context *rs = rs_create(K, M);
107 if(!rs) return AARUF_ERROR_NOT_ENOUGH_MEMORY;
108
109 /* Allocate K stripe slots × M parity buffers */
110 uint8_t **parity = (uint8_t **)calloc((size_t)K * M, sizeof(uint8_t *));
111 if(!parity) { rs_free(rs); return AARUF_ERROR_NOT_ENOUGH_MEMORY; }
112
113 for(uint32_t i = 0; i < (uint32_t)K * M; i++)
114 {
115 parity[i] = (uint8_t *)calloc(1, shard_size);
116 if(!parity[i])
117 {
118 for(uint32_t j = 0; j < i; j++) free(parity[j]);
119 free(parity);
120 rs_free(rs);
122 }
123 }
124
125 /* Allocate tracking arrays (K entries per slot × K slots) */
126 uint64_t *offsets = (uint64_t *)calloc((size_t)K * K, sizeof(uint64_t));
127 uint32_t *sizes = (uint32_t *)calloc((size_t)K * K, sizeof(uint32_t));
128 uint64_t *crcs = (uint64_t *)calloc((size_t)K * K, sizeof(uint64_t));
129 uint16_t *counts = (uint16_t *)calloc(K, sizeof(uint16_t));
130
131 if(!offsets || !sizes || !crcs || !counts)
132 {
133 free(offsets); free(sizes); free(crcs); free(counts);
134 for(uint32_t i = 0; i < (uint32_t)K * M; i++) free(parity[i]);
135 free(parity);
136 rs_free(rs);
138 }
139
140 /* Initialize completed stripes array */
141 UT_array *stripes = NULL;
142 utarray_new(stripes, &ec_stripe_icd);
143
144 /* Store in context */
145 ctx->ec_algorithm = algorithm;
146 ctx->ec_K = K;
147 ctx->ec_M = M;
148 ctx->ec_data_shard_size = shard_size;
149 ctx->ec_rs_ctx = rs;
150 ctx->ec_data_parity = parity;
151 ctx->ec_data_block_offsets = offsets;
152 ctx->ec_data_block_sizes = sizes;
153 ctx->ec_data_shard_crcs = crcs;
154 ctx->ec_data_stripe_counts = counts;
155 ctx->ec_total_data_blocks = 0;
156 ctx->ec_data_stripes = stripes;
157 ctx->ec_enabled = true;
158
159 /* Set feature flag so old readers know parity data exists */
161
162 TRACE("Erasure coding configured: algorithm=%u K=%u M=%u shard_size=%u", algorithm, K, M, shard_size);
163 TRACE("Exiting aaruf_set_erasure_coding() = 0");
164 return AARUF_STATUS_OK;
165}
166
193AARU_EXPORT int32_t AARU_CALL aaruf_set_erasure_coding_auto(void *context, uint8_t recovery_percent)
194{
195 TRACE("Entering aaruf_set_erasure_coding_auto(%p, %u)", context, recovery_percent);
196
197 if(recovery_percent == 0 || recovery_percent > 100)
199
200 /* M scales with recovery percentage: more recovery = more parity blocks.
201 * Target K ≈ 20 (reasonable stripe size), so M ≈ 20 * percent / 100.
202 * Clamp to [2, 8]: minimum M=2 for RS burst tolerance, max M=8 for memory. */
203 uint16_t M = (uint16_t)((20 * (uint32_t)recovery_percent + 50) / 100); /* rounded */
204 if(M < 2) M = 2;
205 if(M > 8) M = 8;
206
207 uint16_t K = (uint16_t)(M * 100 / recovery_percent);
208 if(K < 1) K = 1;
209 if(K + M > 255) K = 255 - M;
210
211 TRACE("Auto EC: recovery_percent=%u -> K=%u M=%u", recovery_percent, K, M);
213}
214
228void ec_accumulate_data_block(aaruformat_context *ctx, const BlockHeader *block_header, const uint8_t *lzma_props,
229 const uint8_t *payload, uint32_t payload_size, uint64_t file_offset)
230{
231 if(!ctx->ec_enabled) return;
232
233 const uint16_t K = ctx->ec_K;
234 const uint16_t M = ctx->ec_M;
235 const uint32_t shard = ctx->ec_data_shard_size;
236
237 /* Determine which stripe slot this block goes to (interleaved round-robin) */
238 uint32_t slot = ctx->ec_total_data_blocks % K;
239
240 /* Position within this slot's stripe */
241 uint16_t pos = ctx->ec_data_stripe_counts[slot];
242
243 /* Build on-disk shard in a temp buffer:
244 * [BlockHeader] [LZMA props if LZMA] [payload]
245 * Remaining bytes to shard_size are implicitly zero (parity buffers were calloc'd) */
246 uint32_t actual_size = (uint32_t)sizeof(BlockHeader);
247 if(block_header->compression == kCompressionLzma && lzma_props)
248 actual_size += LZMA_PROPERTIES_LENGTH;
249 actual_size += payload_size;
250
251 /* We need a temporary flat copy of the on-disk representation for CRC64 and parity accumulation */
252 uint8_t *shard_buf = (uint8_t *)calloc(1, shard);
253 if(!shard_buf) return; /* Best effort — if OOM, skip parity for this block */
254
255 /* Copy BlockHeader */
256 memcpy(shard_buf, block_header, sizeof(BlockHeader));
257 uint32_t offset = sizeof(BlockHeader);
258
259 /* Copy LZMA properties if applicable */
260 if(block_header->compression == kCompressionLzma && lzma_props)
261 {
262 memcpy(shard_buf + offset, lzma_props, LZMA_PROPERTIES_LENGTH);
263 offset += LZMA_PROPERTIES_LENGTH;
264 }
265
266 /* Copy payload */
267 memcpy(shard_buf + offset, payload, payload_size);
268
269 /* Compute CRC64 of the zero-padded shard */
270 uint64_t shard_crc = aaruf_crc64_data(shard_buf, shard);
271
272 /* Record tracking info */
273 size_t tracking_idx = (size_t)slot * K + pos;
274 ctx->ec_data_block_offsets[tracking_idx] = file_offset;
275 ctx->ec_data_block_sizes[tracking_idx] = actual_size;
276 ctx->ec_data_shard_crcs[tracking_idx] = shard_crc;
277
278 /* Accumulate into parity buffers for this slot */
279 for(uint16_t m = 0; m < M; m++)
280 {
281 uint8_t coeff = rs_get_coefficient((rs_context *)ctx->ec_rs_ctx, m, pos);
282 size_t parity_idx = (size_t)slot * M + m;
283 rs_encode_incremental(coeff, shard_buf, ctx->ec_data_parity[parity_idx], shard);
284 }
285
286 free(shard_buf);
287
288 ctx->ec_data_stripe_counts[slot]++;
290
291 /* Check if this stripe slot is full → write parity blocks */
292 if(ctx->ec_data_stripe_counts[slot] == K)
293 {
294 ec_flush_data_stripe(ctx, slot);
295 }
296}
297
305{
306 const uint16_t K = ctx->ec_K;
307 const uint16_t M = ctx->ec_M;
308 const uint32_t shard = ctx->ec_data_shard_size;
309 uint16_t actual_k = ctx->ec_data_stripe_counts[slot];
310
311 if(actual_k == 0) return;
312
313 /* Build and serialize stripe descriptor:
314 * [actualK: uint16_t]
315 * [actualK × StripeDataBlockEntry: offset(8) + onDiskSize(4) + shardCrc64(8) = 20 bytes each]
316 * [M × StripeParityBlockEntry: offset(8) = 8 bytes each]
317 */
318 size_t desc_data_size = sizeof(uint16_t) + (size_t)actual_k * sizeof(StripeDataBlockEntry) +
319 (size_t)M * sizeof(StripeParityBlockEntry);
320 uint8_t *desc = (uint8_t *)calloc(1, desc_data_size);
321 if(!desc) return;
322
323 uint8_t *p = desc;
324
325 /* Write actualK */
326 memcpy(p, &actual_k, sizeof(uint16_t)); p += sizeof(uint16_t);
327
328 /* Write data block entries */
329 for(uint16_t k = 0; k < actual_k; k++)
330 {
331 size_t idx = (size_t)slot * K + k;
333 entry.offset = ctx->ec_data_block_offsets[idx];
334 entry.onDiskSize = ctx->ec_data_block_sizes[idx];
335 entry.shardCrc64 = ctx->ec_data_shard_crcs[idx];
336 memcpy(p, &entry, sizeof(StripeDataBlockEntry)); p += sizeof(StripeDataBlockEntry);
337 }
338
339 /* Write M parity blocks to disk */
340 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
341
342 for(uint16_t m = 0; m < M; m++)
343 {
344 size_t parity_idx = (size_t)slot * M + m;
345 uint8_t *parity_data = ctx->ec_data_parity[parity_idx];
346
347 /* Compress the parity shard using the same settings as data blocks */
348 BlockHeader parity_header;
349 memset(&parity_header, 0, sizeof(BlockHeader));
350 parity_header.identifier = DataBlock;
351 parity_header.type = kDataTypeErasureParity;
352 parity_header.compression = kCompressionNone;
353 parity_header.sectorSize = 0;
354 parity_header.length = shard;
355 parity_header.cmpLength = shard;
356 parity_header.crc64 = aaruf_crc64_data(parity_data, shard);
357 parity_header.cmpCrc64 = parity_header.crc64;
358
359 /* Try compression */
360 uint8_t *cmp_buf = NULL;
361 size_t cmp_size = 0;
362
363 if(ctx->compression_enabled)
364 {
365 cmp_buf = (uint8_t *)malloc((size_t)shard * 2);
366 if(cmp_buf)
367 {
368 if(ctx->use_zstd)
369 {
370 cmp_size = aaruf_zstd_encode_buffer(cmp_buf, (size_t)shard * 2, parity_data, shard,
371 ctx->zstd_level, ctx->num_threads);
372 if(cmp_size > 0 && cmp_size < shard)
373 {
374 parity_header.compression = kCompressionZstd;
375 parity_header.cmpLength = (uint32_t)cmp_size;
376 parity_header.cmpCrc64 = aaruf_crc64_data(cmp_buf, (uint32_t)cmp_size);
377 ctx->has_zstd_blocks = true;
378 }
379 else
380 {
381 free(cmp_buf);
382 cmp_buf = NULL;
383 }
384 }
385 else
386 {
387 size_t dst_size = (size_t)shard * 2;
388 size_t props_size = LZMA_PROPERTIES_LENGTH;
389 uint8_t lzma_props[LZMA_PROPERTIES_LENGTH] = {0};
390 aaruf_lzma_encode_buffer(cmp_buf, &dst_size, parity_data, shard, lzma_props, &props_size, 9,
391 ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
392 if(dst_size + LZMA_PROPERTIES_LENGTH < shard)
393 {
394 parity_header.compression = kCompressionLzma;
395 parity_header.cmpLength = (uint32_t)(dst_size + LZMA_PROPERTIES_LENGTH);
396 parity_header.cmpCrc64 = aaruf_crc64_data(cmp_buf, (uint32_t)dst_size);
397
398 /* Write: header + lzma_props + compressed data */
399 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
400 uint64_t parity_offset = (uint64_t)aaruf_ftell(ctx->imageStream);
401 parity_offset = (parity_offset + alignment_mask) & ~alignment_mask;
402 aaruf_fseek(ctx->imageStream, (aaru_off_t)parity_offset, SEEK_SET);
403
404 fwrite(&parity_header, sizeof(BlockHeader), 1, ctx->imageStream);
405 fwrite(lzma_props, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
406 fwrite(cmp_buf, dst_size, 1, ctx->imageStream);
407 free(cmp_buf);
408
409 /* Record parity offset in descriptor */
411 pentry.offset = parity_offset;
412 memcpy(p, &pentry, sizeof(StripeParityBlockEntry)); p += sizeof(StripeParityBlockEntry);
413
414 /* Add index entry */
415 IndexEntry ie = {.blockType = DataBlock, .dataType = kDataTypeErasureParity,
416 .offset = parity_offset};
417 utarray_push_back(ctx->index_entries, &ie);
418 ctx->dirty_index_block = true;
419
420 /* Update next_block_position */
421 uint64_t total = sizeof(BlockHeader) + parity_header.cmpLength;
422 ctx->next_block_position = (parity_offset + total + alignment_mask) & ~alignment_mask;
423
424 /* Reset parity buffer */
425 memset(parity_data, 0, shard);
426 continue;
427 }
428 else
429 {
430 free(cmp_buf);
431 cmp_buf = NULL;
432 }
433 }
434 }
435 }
436
437 /* Write uncompressed (or compressed non-LZMA) parity */
438 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
439 uint64_t parity_offset = (uint64_t)aaruf_ftell(ctx->imageStream);
440 parity_offset = (parity_offset + alignment_mask) & ~alignment_mask;
441 aaruf_fseek(ctx->imageStream, (aaru_off_t)parity_offset, SEEK_SET);
442
443 fwrite(&parity_header, sizeof(BlockHeader), 1, ctx->imageStream);
444 if(cmp_buf)
445 {
446 fwrite(cmp_buf, cmp_size, 1, ctx->imageStream);
447 free(cmp_buf);
448 }
449 else
450 {
451 fwrite(parity_data, shard, 1, ctx->imageStream);
452 }
453
454 /* Record parity offset in descriptor */
456 pentry.offset = parity_offset;
457 memcpy(p, &pentry, sizeof(StripeParityBlockEntry)); p += sizeof(StripeParityBlockEntry);
458
459 /* Add index entry */
460 IndexEntry ie = {.blockType = DataBlock, .dataType = kDataTypeErasureParity, .offset = parity_offset};
461 utarray_push_back(ctx->index_entries, &ie);
462 ctx->dirty_index_block = true;
463
464 /* Update next_block_position */
465 uint64_t total = sizeof(BlockHeader) + parity_header.cmpLength;
466 ctx->next_block_position = (parity_offset + total + alignment_mask) & ~alignment_mask;
467
468 /* Reset parity buffer */
469 memset(parity_data, 0, shard);
470 }
471
472 /* Store the completed stripe descriptor */
473 for(size_t i = 0; i < desc_data_size; i++)
474 utarray_push_back(ctx->ec_data_stripes, &desc[i]);
475
476 free(desc);
477
478 /* Reset stripe slot tracking */
479 for(uint16_t k = 0; k < K; k++)
480 {
481 size_t idx = (size_t)slot * K + k;
482 ctx->ec_data_block_offsets[idx] = 0;
483 ctx->ec_data_block_sizes[idx] = 0;
484 ctx->ec_data_shard_crcs[idx] = 0;
485 }
486 ctx->ec_data_stripe_counts[slot] = 0;
487}
488
496 const uint64_t *offsets, const uint32_t *sizes,
497 uint32_t block_count, uint8_t group_type,
498 uint16_t parity_data_type,
499 uint8_t **out_desc, size_t *out_desc_len,
500 StripeGroupDescriptor *out_group)
501{
502 *out_desc = NULL;
503 *out_desc_len = 0;
504 if(block_count == 0) return;
505
506 const uint16_t M = ctx->ec_M;
507 uint16_t actual_k = (uint16_t)(block_count > (uint32_t)(255 - M) ? 255 - M : block_count);
508
509 uint32_t shard_size = 0;
510 for(uint32_t i = 0; i < actual_k; i++)
511 if(sizes[i] > shard_size) shard_size = sizes[i];
512
513 rs_context *rs = rs_create(actual_k, M);
514 if(!rs) return;
515
516 uint8_t **parity = (uint8_t **)calloc(M, sizeof(uint8_t *));
517 if(!parity) { rs_free(rs); return; }
518 for(uint16_t m = 0; m < M; m++)
519 {
520 parity[m] = (uint8_t *)calloc(1, shard_size);
521 if(!parity[m]) { for(uint16_t j = 0; j < m; j++) free(parity[j]); free(parity); rs_free(rs); return; }
522 }
523
524 size_t desc_size = sizeof(uint16_t) + (size_t)actual_k * sizeof(StripeDataBlockEntry) +
525 (size_t)M * sizeof(StripeParityBlockEntry);
526 uint8_t *desc = (uint8_t *)calloc(1, desc_size);
527 if(!desc) { for(uint16_t m = 0; m < M; m++) free(parity[m]); free(parity); rs_free(rs); return; }
528
529 uint8_t *dp = desc;
530 memcpy(dp, &actual_k, sizeof(uint16_t)); dp += sizeof(uint16_t);
531
532 uint8_t *shard_buf = (uint8_t *)calloc(1, shard_size);
533 if(!shard_buf) { free(desc); for(uint16_t m = 0; m < M; m++) free(parity[m]); free(parity); rs_free(rs); return; }
534
535 for(uint16_t k = 0; k < actual_k; k++)
536 {
537 memset(shard_buf, 0, shard_size);
538 aaruf_fseek(ctx->imageStream, (aaru_off_t)offsets[k], SEEK_SET);
539 uint32_t read_size = sizes[k] > shard_size ? shard_size : sizes[k];
540 fread(shard_buf, read_size, 1, ctx->imageStream);
541
542 uint64_t shard_crc = aaruf_crc64_data(shard_buf, shard_size);
543
545 entry.offset = offsets[k]; entry.onDiskSize = sizes[k]; entry.shardCrc64 = shard_crc;
546 memcpy(dp, &entry, sizeof(StripeDataBlockEntry)); dp += sizeof(StripeDataBlockEntry);
547
548 for(uint16_t m = 0; m < M; m++)
549 {
550 uint8_t coeff = rs_get_coefficient(rs, m, k);
551 rs_encode_incremental(coeff, shard_buf, parity[m], shard_size);
552 }
553 }
554 free(shard_buf);
555
556 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
557 for(uint16_t m = 0; m < M; m++)
558 {
559 BlockHeader ph;
560 memset(&ph, 0, sizeof(ph));
561 ph.identifier = DataBlock; ph.type = parity_data_type;
563 ph.length = shard_size; ph.cmpLength = shard_size;
564 ph.crc64 = aaruf_crc64_data(parity[m], shard_size); ph.cmpCrc64 = ph.crc64;
565
566 uint8_t *write_data = parity[m];
567 uint32_t write_size = shard_size;
568 uint8_t *cmp_buf = NULL;
569 uint8_t lzma_props_buf[LZMA_PROPERTIES_LENGTH] = {0};
570 bool used_lzma = false;
571
572 /* Try compression */
573 if(ctx->compression_enabled)
574 {
575 cmp_buf = (uint8_t *)malloc((size_t)shard_size * 2);
576 if(cmp_buf)
577 {
578 if(ctx->use_zstd)
579 {
580 size_t cmp_size = aaruf_zstd_encode_buffer(cmp_buf, (size_t)shard_size * 2,
581 parity[m], shard_size,
582 ctx->zstd_level, ctx->num_threads);
583 if(cmp_size > 0 && cmp_size < shard_size)
584 {
586 ph.cmpLength = (uint32_t)cmp_size;
587 ph.cmpCrc64 = aaruf_crc64_data(cmp_buf, (uint32_t)cmp_size);
588 ctx->has_zstd_blocks = true;
589 write_data = cmp_buf;
590 write_size = (uint32_t)cmp_size;
591 }
592 else { free(cmp_buf); cmp_buf = NULL; }
593 }
594 else
595 {
596 size_t dst_size = (size_t)shard_size * 2;
597 size_t props_size = LZMA_PROPERTIES_LENGTH;
598 aaruf_lzma_encode_buffer(cmp_buf, &dst_size, parity[m], shard_size,
599 lzma_props_buf, &props_size, 9,
600 ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
601 if(dst_size + LZMA_PROPERTIES_LENGTH < shard_size)
602 {
604 ph.cmpLength = (uint32_t)(dst_size + LZMA_PROPERTIES_LENGTH);
605 ph.cmpCrc64 = aaruf_crc64_data(cmp_buf, (uint32_t)dst_size);
606 write_data = cmp_buf;
607 write_size = (uint32_t)dst_size;
608 used_lzma = true;
609 }
610 else { free(cmp_buf); cmp_buf = NULL; }
611 }
612 }
613 }
614
615 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
616 uint64_t po = ((uint64_t)aaruf_ftell(ctx->imageStream) + alignment_mask) & ~alignment_mask;
617 aaruf_fseek(ctx->imageStream, (aaru_off_t)po, SEEK_SET);
618 fwrite(&ph, sizeof(BlockHeader), 1, ctx->imageStream);
619 if(used_lzma) fwrite(lzma_props_buf, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
620 fwrite(write_data, write_size, 1, ctx->imageStream);
621 free(cmp_buf);
622
623 StripeParityBlockEntry pe; pe.offset = po;
624 memcpy(dp, &pe, sizeof(StripeParityBlockEntry)); dp += sizeof(StripeParityBlockEntry);
625
626 IndexEntry ie = {.blockType = DataBlock, .dataType = parity_data_type, .offset = po};
627 utarray_push_back(ctx->index_entries, &ie);
628 ctx->dirty_index_block = true;
629 free(parity[m]);
630 }
631 free(parity); rs_free(rs);
632
633 memset(out_group, 0, sizeof(StripeGroupDescriptor));
634 out_group->groupType = group_type; out_group->K = actual_k; out_group->M = M;
635 out_group->shardSize = shard_size; out_group->stripeCount = 1; out_group->interleaveDepth = 1;
636 *out_desc = desc; *out_desc_len = desc_size;
637}
638
642static void ec_collect_blocks_by_type(aaruformat_context *ctx, uint32_t block_type,
643 uint64_t **out_offsets, uint32_t **out_sizes, uint32_t *out_count)
644{
645 *out_offsets = NULL; *out_sizes = NULL; *out_count = 0;
646 uint32_t n = (uint32_t)utarray_len(ctx->index_entries);
647 if(n == 0) return;
648
649 uint32_t count = 0;
650 for(uint32_t i = 0; i < n; i++)
651 {
652 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
653 if(ie->blockType == block_type) count++;
654 }
655 if(count == 0) return;
656
657 uint64_t *offsets = (uint64_t *)malloc(count * sizeof(uint64_t));
658 uint32_t *sizes = (uint32_t *)malloc(count * sizeof(uint32_t));
659 if(!offsets || !sizes) { free(offsets); free(sizes); return; }
660
661 uint32_t idx = 0;
662 for(uint32_t i = 0; i < n && idx < count; i++)
663 {
664 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
665 if(ie->blockType != block_type) continue;
666 offsets[idx] = ie->offset;
667 aaruf_fseek(ctx->imageStream, (aaru_off_t)ie->offset, SEEK_SET);
668 if(block_type == DeDuplicationTable2 || block_type == DeDuplicationTableSecondary)
669 {
670 DdtHeader2 ddt_hdr;
671 if(fread(&ddt_hdr, sizeof(DdtHeader2), 1, ctx->imageStream) == 1)
672 sizes[idx] = (uint32_t)(sizeof(DdtHeader2) + ddt_hdr.cmpLength);
673 else sizes[idx] = sizeof(DdtHeader2);
674 }
675 else
676 {
677 BlockHeader blk_hdr;
678 if(fread(&blk_hdr, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
679 sizes[idx] = (uint32_t)(sizeof(BlockHeader) + blk_hdr.cmpLength);
680 else sizes[idx] = sizeof(BlockHeader);
681 }
682 idx++;
683 }
684 *out_offsets = offsets; *out_sizes = sizes; *out_count = idx;
685}
686
691{
692 if(!ctx->ec_enabled) return;
693
694 const uint16_t K = ctx->ec_K;
695 const uint16_t M = ctx->ec_M;
696
697 /* --- Group 0: Data blocks (flush partial stripes) --- */
698 for(uint16_t slot = 0; slot < K; slot++)
699 if(ctx->ec_data_stripe_counts[slot] > 0)
700 ec_flush_data_stripe(ctx, slot);
701
702 uint32_t data_stripe_count = 0;
703 {
704 size_t total_bytes = utarray_len(ctx->ec_data_stripes);
705 uint8_t *base = (uint8_t *)utarray_front(ctx->ec_data_stripes);
706 size_t pos = 0;
707 while(base && pos + sizeof(uint16_t) <= total_bytes)
708 {
709 uint16_t ak;
710 memcpy(&ak, base + pos, sizeof(uint16_t));
711 pos += sizeof(uint16_t) + (size_t)ak * sizeof(StripeDataBlockEntry) + (size_t)M * sizeof(StripeParityBlockEntry);
712 data_stripe_count++;
713 }
714 }
715
716 StripeGroupDescriptor data_group;
717 memset(&data_group, 0, sizeof(data_group));
718 data_group.groupType = kECGroupData; data_group.K = K; data_group.M = M;
719 data_group.shardSize = ctx->ec_data_shard_size; data_group.stripeCount = data_stripe_count;
720 data_group.interleaveDepth = K;
721 size_t data_stripe_data_len = utarray_len(ctx->ec_data_stripes);
722
723 /* --- Group 1: DDT secondary --- */
724 uint8_t *ddt_sec_desc = NULL; size_t ddt_sec_desc_len = 0;
725 StripeGroupDescriptor ddt_sec_group; memset(&ddt_sec_group, 0, sizeof(ddt_sec_group));
726 {
727 uint64_t *off; uint32_t *sz; uint32_t cnt;
729 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); }
730 }
731
732 /* --- Group 2: DDT primary --- */
733 uint8_t *ddt_pri_desc = NULL; size_t ddt_pri_desc_len = 0;
734 StripeGroupDescriptor ddt_pri_group; memset(&ddt_pri_group, 0, sizeof(ddt_pri_group));
735 {
736 uint64_t *off; uint32_t *sz; uint32_t cnt;
737 ec_collect_blocks_by_type(ctx, DeDuplicationTable2, &off, &sz, &cnt);
738 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); }
739 }
740
741 /* --- Group 3: Metadata (non-DDT, non-data, non-index, non-parity) --- */
742 uint8_t *meta_desc = NULL; size_t meta_desc_len = 0;
743 StripeGroupDescriptor meta_group; memset(&meta_group, 0, sizeof(meta_group));
744 {
745 uint32_t n = (uint32_t)utarray_len(ctx->index_entries);
746 uint32_t count = 0;
747 for(uint32_t i = 0; i < n; i++)
748 {
749 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
750 if(ie->blockType == DataBlock && (ie->dataType == kDataTypeUserData ||
753 if(ie->blockType == IndexBlock || ie->blockType == IndexBlock2 || ie->blockType == IndexBlock3) continue;
754 count++;
755 }
756 if(count > 0 && count <= (uint32_t)(255 - M))
757 {
758 uint64_t *off = (uint64_t *)malloc(count * sizeof(uint64_t));
759 uint32_t *sz = (uint32_t *)malloc(count * sizeof(uint32_t));
760 if(off && sz)
761 {
762 uint32_t idx = 0;
763 for(uint32_t i = 0; i < n && idx < count; i++)
764 {
765 IndexEntry *ie = (IndexEntry *)utarray_eltptr(ctx->index_entries, i);
766 if(ie->blockType == DataBlock && (ie->dataType == kDataTypeUserData ||
769 if(ie->blockType == IndexBlock || ie->blockType == IndexBlock2 || ie->blockType == IndexBlock3) continue;
770 off[idx] = ie->offset;
771 aaruf_fseek(ctx->imageStream, (aaru_off_t)ie->offset, SEEK_SET);
772 BlockHeader bh; if(fread(&bh, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
773 sz[idx] = (uint32_t)(sizeof(BlockHeader) + bh.cmpLength); else sz[idx] = sizeof(BlockHeader);
774 idx++;
775 }
776 ec_write_batch_parity(ctx, off, sz, idx, kECGroupMetadata, kDataTypeErasureParityMeta, &meta_desc, &meta_desc_len, &meta_group);
777 }
778 free(off); free(sz);
779 }
780 }
781
782 /* --- Group 4: Index (K=1, M replicas) --- */
783 uint8_t *idx_desc = NULL; size_t idx_desc_len = 0;
784 StripeGroupDescriptor idx_group; memset(&idx_group, 0, sizeof(idx_group));
785 if(ctx->header.indexOffset > 0)
786 {
787 uint64_t io = ctx->header.indexOffset;
788 aaruf_fseek(ctx->imageStream, (aaru_off_t)io, SEEK_SET);
789 IndexHeader3 ih;
790 if(fread(&ih, sizeof(IndexHeader3), 1, ctx->imageStream) == 1)
791 {
792 uint32_t isz = (uint32_t)(sizeof(IndexHeader3) + ih.entries * sizeof(IndexEntry));
793 ec_write_batch_parity(ctx, &io, &isz, 1, kECGroupIndex, kDataTypeErasureParityIndex, &idx_desc, &idx_desc_len, &idx_group);
794 }
795 }
796
797 /* --- Build ECMB with all groups --- */
798 uint8_t group_count = 1; /* data always present */
799 if(ddt_sec_desc) group_count++;
800 if(ddt_pri_desc) group_count++;
801 if(meta_desc) group_count++;
802 if(idx_desc) group_count++;
803
804 size_t payload_len = sizeof(StripeGroupDescriptor) + data_stripe_data_len;
805 if(ddt_sec_desc) payload_len += sizeof(StripeGroupDescriptor) + ddt_sec_desc_len;
806 if(ddt_pri_desc) payload_len += sizeof(StripeGroupDescriptor) + ddt_pri_desc_len;
807 if(meta_desc) payload_len += sizeof(StripeGroupDescriptor) + meta_desc_len;
808 if(idx_desc) payload_len += sizeof(StripeGroupDescriptor) + idx_desc_len;
809
810 uint8_t *payload = (uint8_t *)malloc(payload_len);
811 if(!payload) { free(ddt_sec_desc); free(ddt_pri_desc); free(meta_desc); free(idx_desc); return; }
812
813 uint8_t *wp = payload;
814 /* Data group */
815 memcpy(wp, &data_group, sizeof(StripeGroupDescriptor)); wp += sizeof(StripeGroupDescriptor);
816 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; } }
817
818#define WRITE_GROUP(desc, desc_len, grp) \
819 if(desc) { memcpy(wp, &(grp), sizeof(StripeGroupDescriptor)); wp += sizeof(StripeGroupDescriptor); \
820 memcpy(wp, (desc), (desc_len)); wp += (desc_len); }
821
822 WRITE_GROUP(ddt_sec_desc, ddt_sec_desc_len, ddt_sec_group)
823 WRITE_GROUP(ddt_pri_desc, ddt_pri_desc_len, ddt_pri_group)
824 WRITE_GROUP(meta_desc, meta_desc_len, meta_group)
825 WRITE_GROUP(idx_desc, idx_desc_len, idx_group)
826#undef WRITE_GROUP
827
828 free(ddt_sec_desc); free(ddt_pri_desc); free(meta_desc); free(idx_desc);
829
830 uint64_t payload_crc = aaruf_crc64_data(payload, (uint32_t)payload_len);
831
833 memset(&ecmb, 0, sizeof(ecmb));
835 ecmb.stripeGroupCount = group_count; ecmb.compression = kCompressionNone;
836 ecmb.length = payload_len; ecmb.cmpLength = payload_len;
837 ecmb.crc64 = payload_crc; ecmb.cmpCrc64 = payload_crc;
838
839 uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
840 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
841 uint64_t ecmb_offset = ((uint64_t)aaruf_ftell(ctx->imageStream) + alignment_mask) & ~alignment_mask;
842 aaruf_fseek(ctx->imageStream, (aaru_off_t)ecmb_offset, SEEK_SET);
843 fwrite(&ecmb, sizeof(ErasureCodingMapHeader), 1, ctx->imageStream);
844 fwrite(payload, payload_len, 1, ctx->imageStream);
845 uint64_t ecmb_total = sizeof(ErasureCodingMapHeader) + payload_len;
846
847 /* Duplicate ECMB */
848 uint64_t ecmb2_offset = (ecmb_offset + ecmb_total + alignment_mask) & ~alignment_mask;
849 aaruf_fseek(ctx->imageStream, (aaru_off_t)ecmb2_offset, SEEK_SET);
850 fwrite(&ecmb, sizeof(ErasureCodingMapHeader), 1, ctx->imageStream);
851 fwrite(payload, payload_len, 1, ctx->imageStream);
852 free(payload);
853
854 /* Recovery footer */
855 AaruRecoveryFooter footer;
856 memset(&footer, 0, sizeof(footer));
857 footer.ecmbOffset = ecmb_offset; footer.ecmbLength = ecmb_total;
858 footer.headerCrc64 = aaruf_crc64_data((const uint8_t *)&ctx->header, sizeof(AaruHeaderV2));
859 memcpy(&footer.backupHeader, &ctx->header, sizeof(AaruHeaderV2));
861
862 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
863 fwrite(&footer, sizeof(AaruRecoveryFooter), 1, ctx->imageStream);
864
865 TRACE("Wrote ECMB at offset %" PRIu64 " (%u groups, %" PRIu64 " bytes), footer at EOF",
866 ecmb_offset, group_count, ecmb_total);
867}
868
877{
878 if(!ctx->ec_enabled) return;
879
880 if(ctx->ec_rs_ctx)
881 {
883 ctx->ec_rs_ctx = NULL;
884 }
885
886 if(ctx->ec_data_parity)
887 {
888 for(uint32_t i = 0; i < (uint32_t)ctx->ec_K * ctx->ec_M; i++)
889 free(ctx->ec_data_parity[i]);
890 free(ctx->ec_data_parity);
891 ctx->ec_data_parity = NULL;
892 }
893
894 free(ctx->ec_data_block_offsets); ctx->ec_data_block_offsets = NULL;
895 free(ctx->ec_data_block_sizes); ctx->ec_data_block_sizes = NULL;
896 free(ctx->ec_data_shard_crcs); ctx->ec_data_shard_crcs = NULL;
897 free(ctx->ec_data_stripe_counts); ctx->ec_data_stripe_counts = NULL;
898
899 if(ctx->ec_data_stripes)
900 {
901 utarray_free(ctx->ec_data_stripes);
902 ctx->ec_data_stripes = NULL;
903 }
904
905 ctx->ec_enabled = false;
906
907 /* Free read-path state */
908 if(ctx->ec_read_stripes)
909 {
910 EcReadStripe *stripes = (EcReadStripe *)ctx->ec_read_stripes;
911 for(uint32_t i = 0; i < ctx->ec_read_stripe_count; i++)
912 {
913 free(stripes[i].data_entries);
914 free(stripes[i].parity_offsets);
915 }
916 free(stripes);
917 ctx->ec_read_stripes = NULL;
918 }
919 ctx->ec_read_stripe_count = 0;
920
921 /* Free block lookup hashmap */
922 if(ctx->ec_block_lookup)
923 {
925 EcBlockLookupEntry *entry, *tmp;
926 HASH_ITER(hh, root, entry, tmp)
927 {
928 HASH_DEL(root, entry);
929 free(entry);
930 }
931 ctx->ec_block_lookup = NULL;
932 }
933
934 ctx->ec_recovery_available = false;
935}
936
937/* =========================================================================
938 * ECMB loading (read path)
939 * ========================================================================= */
940
942{
943 TRACE("Entering ec_load_ecmb(%p)", (void *)ctx);
944
945 /* Read recovery footer from last 160 bytes of file */
946 aaruf_fseek(ctx->imageStream, 0, SEEK_END);
947 int64_t file_size = aaruf_ftell(ctx->imageStream);
948 if(file_size < (int64_t)sizeof(AaruRecoveryFooter))
949 {
950 TRACE("File too small for recovery footer");
951 return;
952 }
953
954 aaruf_fseek(ctx->imageStream, (aaru_off_t)(file_size - (int64_t)sizeof(AaruRecoveryFooter)), SEEK_SET);
955
956 AaruRecoveryFooter footer;
957 if(fread(&footer, sizeof(AaruRecoveryFooter), 1, ctx->imageStream) != 1)
958 {
959 TRACE("Cannot read recovery footer");
960 return;
961 }
962
964 {
965 TRACE("Recovery footer magic mismatch: 0x%016" PRIx64, footer.footerMagic);
966 return;
967 }
968
969 /* Read ECMB header */
970 aaruf_fseek(ctx->imageStream, (aaru_off_t)footer.ecmbOffset, SEEK_SET);
971
973 if(fread(&ecmb, sizeof(ErasureCodingMapHeader), 1, ctx->imageStream) != 1)
974 {
975 TRACE("Cannot read ECMB header");
976 return;
977 }
978
980 {
981 TRACE("ECMB identifier mismatch");
982 return;
983 }
984
985 /* Read payload (uncompressed only for now) */
986 if(ecmb.length == 0 || ecmb.length > 256 * 1024 * 1024) return; /* sanity limit */
987
988 uint8_t *payload = (uint8_t *)malloc((size_t)ecmb.length);
989 if(!payload) return;
990
991 if(ecmb.compression == kCompressionNone)
992 {
993 if(fread(payload, (size_t)ecmb.cmpLength, 1, ctx->imageStream) != 1)
994 {
995 free(payload);
996 return;
997 }
998 }
999 else
1000 {
1001 /* Compressed ECMB payload — read compressed, decompress */
1002 uint8_t *cmp = (uint8_t *)malloc((size_t)ecmb.cmpLength);
1003 if(!cmp) { free(payload); return; }
1004 if(fread(cmp, (size_t)ecmb.cmpLength, 1, ctx->imageStream) != 1) { free(cmp); free(payload); return; }
1005
1006 if(ecmb.compression == kCompressionLzma)
1007 {
1008 size_t out_size = (size_t)ecmb.length;
1009 size_t lzma_src_size = (size_t)ecmb.cmpLength - LZMA_PROPERTIES_LENGTH;
1010 aaruf_lzma_decode_buffer(payload, &out_size, cmp + LZMA_PROPERTIES_LENGTH,
1011 &lzma_src_size, cmp, LZMA_PROPERTIES_LENGTH);
1012 }
1013 else if(ecmb.compression == kCompressionZstd)
1014 {
1015 aaruf_zstd_decode_buffer(payload, (size_t)ecmb.length, cmp, (size_t)ecmb.cmpLength);
1016 }
1017 free(cmp);
1018 }
1019
1020 /* Verify CRC64 */
1021 uint64_t computed_crc = aaruf_crc64_data(payload, (uint32_t)ecmb.length);
1022 if(computed_crc != ecmb.crc64)
1023 {
1024 TRACE("ECMB payload CRC64 mismatch");
1025 free(payload);
1026 return;
1027 }
1028
1029 ctx->ec_algorithm = ecmb.algorithm;
1030
1031 /* Parse all stripe groups */
1032 uint8_t *p = payload;
1033 size_t remaining = (size_t)ecmb.length;
1034
1035 EcBlockLookupEntry *lookup_root = NULL;
1036 EcReadStripe *data_stripes = NULL;
1037 uint32_t data_stripe_count = 0;
1038
1039 for(uint8_t g = 0; g < ecmb.stripeGroupCount && remaining >= sizeof(StripeGroupDescriptor); g++)
1040 {
1042 memcpy(&group, p, sizeof(StripeGroupDescriptor));
1043 p += sizeof(StripeGroupDescriptor);
1044 remaining -= sizeof(StripeGroupDescriptor);
1045
1046 /* Parse stripes for this group */
1047 EcReadStripe *grp_stripes = NULL;
1048 if(group.stripeCount > 0)
1049 {
1050 grp_stripes = (EcReadStripe *)calloc(group.stripeCount, sizeof(EcReadStripe));
1051 if(!grp_stripes) break;
1052 }
1053
1054 for(uint32_t s = 0; s < group.stripeCount; s++)
1055 {
1056 if(remaining < sizeof(uint16_t)) break;
1057 uint16_t ak;
1058 memcpy(&ak, p, sizeof(uint16_t)); p += sizeof(uint16_t); remaining -= sizeof(uint16_t);
1059 if(grp_stripes) grp_stripes[s].actual_k = ak;
1060
1061 size_t data_bytes = (size_t)ak * sizeof(StripeDataBlockEntry);
1062 size_t parity_bytes = (size_t)group.M * sizeof(StripeParityBlockEntry);
1063 if(remaining < data_bytes + parity_bytes) break;
1064
1065 if(grp_stripes)
1066 {
1067 grp_stripes[s].data_entries = (StripeDataBlockEntry *)malloc(data_bytes);
1068 if(grp_stripes[s].data_entries)
1069 memcpy(grp_stripes[s].data_entries, p, data_bytes);
1070
1071 grp_stripes[s].parity_offsets = (uint64_t *)malloc((size_t)group.M * sizeof(uint64_t));
1072 }
1073
1074 p += data_bytes; remaining -= data_bytes;
1075
1076 for(uint16_t m = 0; m < group.M; m++)
1077 {
1079 memcpy(&pe, p, sizeof(StripeParityBlockEntry));
1080 p += sizeof(StripeParityBlockEntry); remaining -= sizeof(StripeParityBlockEntry);
1081 if(grp_stripes && grp_stripes[s].parity_offsets)
1082 grp_stripes[s].parity_offsets[m] = pe.offset;
1083 }
1084
1085 /* Build lookup hashmap for data group blocks */
1086 if(group.groupType == kECGroupData && grp_stripes && grp_stripes[s].data_entries)
1087 {
1088 for(uint16_t k = 0; k < ak; k++)
1089 {
1090 EcBlockLookupEntry *le = (EcBlockLookupEntry *)calloc(1, sizeof(EcBlockLookupEntry));
1091 if(!le) break;
1092 le->block_offset = grp_stripes[s].data_entries[k].offset;
1093 le->stripe_index = s;
1094 le->position = k;
1095 HASH_ADD(hh, lookup_root, block_offset, sizeof(uint64_t), le);
1096 }
1097 }
1098 }
1099
1100 /* Store parsed data based on group type */
1101 if(group.groupType == kECGroupData)
1102 {
1103 ctx->ec_K = group.K;
1104 ctx->ec_M = group.M;
1105 ctx->ec_data_shard_size = group.shardSize;
1106 data_stripes = grp_stripes;
1107 data_stripe_count = group.stripeCount;
1108 }
1109 else
1110 {
1111 /* For non-data groups, free the parsed stripes for now.
1112 * Recovery for DDT/metadata/index groups would use these,
1113 * but the current read path only recovers data blocks. */
1114 if(grp_stripes)
1115 {
1116 for(uint32_t s = 0; s < group.stripeCount; s++)
1117 {
1118 free(grp_stripes[s].data_entries);
1119 free(grp_stripes[s].parity_offsets);
1120 }
1121 free(grp_stripes);
1122 }
1123 }
1124 }
1125
1126 free(payload);
1127
1128 ctx->ec_read_stripes = data_stripes;
1129 ctx->ec_read_stripe_count = data_stripe_count;
1130 ctx->ec_block_lookup = lookup_root;
1131 ctx->ec_recovery_available = (data_stripes != NULL && data_stripe_count > 0);
1132
1133 /* Create RS codec for decoding */
1134 if(ctx->ec_recovery_available && !ctx->ec_rs_ctx)
1135 ctx->ec_rs_ctx = rs_create(ctx->ec_K, ctx->ec_M);
1136
1137 TRACE("ECMB loaded: %u groups, K=%u M=%u shard_size=%u data_stripes=%u",
1138 ecmb.stripeGroupCount, ctx->ec_K, ctx->ec_M, ctx->ec_data_shard_size, data_stripe_count);
1139}
1140
1141/* =========================================================================
1142 * Data block recovery (read path)
1143 * ========================================================================= */
1144
1145int32_t ec_recover_data_block(aaruformat_context *ctx, uint64_t block_offset, uint64_t offset,
1146 uint8_t *data, uint32_t *length, uint8_t sector_status)
1147{
1149
1150 ctx->ec_recovery_in_progress = true;
1151
1152 /* Look up which stripe this block belongs to */
1153 EcBlockLookupEntry *le = NULL;
1154 HASH_FIND(hh, (EcBlockLookupEntry *)ctx->ec_block_lookup, &block_offset, sizeof(uint64_t), le);
1155 if(!le) { ctx->ec_recovery_in_progress = false; return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK; }
1156
1157 uint32_t si = le->stripe_index;
1158 EcReadStripe *stripes = (EcReadStripe *)ctx->ec_read_stripes;
1159 EcReadStripe *stripe = &stripes[si];
1160 uint16_t K = ctx->ec_K;
1161 uint16_t M = ctx->ec_M;
1162 uint32_t shard_size = ctx->ec_data_shard_size;
1163
1164 /* Always use K+M shards for RS. For partial stripes (actual_k < K),
1165 * positions actual_k..K-1 are all-zero (calloc'd) and marked present.
1166 * This works because the encoding used the K-size generator matrix
1167 * and zero-contributions for unused positions. */
1168 uint16_t total_shards = K + M;
1169
1170 /* Allocate shard pointers and present flags */
1171 uint8_t **shards = (uint8_t **)calloc(total_shards, sizeof(uint8_t *));
1172 uint8_t *present = (uint8_t *)calloc(total_shards, 1);
1173 if(!shards || !present) { free(shards); free(present); ctx->ec_recovery_in_progress = false; return AARUF_ERROR_NOT_ENOUGH_MEMORY; }
1174
1175 for(uint16_t i = 0; i < total_shards; i++)
1176 {
1177 shards[i] = (uint8_t *)calloc(1, shard_size);
1178 if(!shards[i])
1179 {
1180 for(uint16_t j = 0; j < i; j++) free(shards[j]);
1181 free(shards); free(present);
1182 ctx->ec_recovery_in_progress = false;
1184 }
1185 }
1186
1187 /* Read data shards from file and verify each one's CRC64 against ECMB */
1188 for(uint16_t k = 0; k < stripe->actual_k; k++)
1189 {
1190 StripeDataBlockEntry *de = &stripe->data_entries[k];
1191 uint32_t read_size = de->onDiskSize;
1192 if(read_size > shard_size) read_size = shard_size;
1193
1194 aaruf_fseek(ctx->imageStream, (aaru_off_t)de->offset, SEEK_SET);
1195 if(fread(shards[k], read_size, 1, ctx->imageStream) != 1)
1196 {
1197 present[k] = 0;
1198 continue;
1199 }
1200
1201 /* Verify CRC64 (zero-padded to shard_size via calloc) */
1202 uint64_t crc = aaruf_crc64_data(shards[k], shard_size);
1203 present[k] = (crc == de->shardCrc64) ? 1 : 0;
1204 }
1205
1206 /* Positions actual_k..K-1 are all-zero and present (unused stripe positions) */
1207 for(uint16_t k = stripe->actual_k; k < K; k++)
1208 present[k] = 1; /* All-zero shards, implicitly correct */
1209
1210 /* Read parity shards */
1211 for(uint16_t m = 0; m < M; m++)
1212 {
1213 uint16_t shard_idx = K + m;
1214 uint64_t parity_offset = stripe->parity_offsets[m];
1215
1216 aaruf_fseek(ctx->imageStream, (aaru_off_t)parity_offset, SEEK_SET);
1217
1218 /* Read parity block header */
1219 BlockHeader parity_header;
1220 if(fread(&parity_header, sizeof(BlockHeader), 1, ctx->imageStream) != 1)
1221 {
1222 present[shard_idx] = 0;
1223 continue;
1224 }
1225
1226 /* Read and decompress parity payload */
1227 if(parity_header.compression == kCompressionNone)
1228 {
1229 uint32_t to_read = parity_header.length;
1230 if(to_read > shard_size) to_read = shard_size;
1231 if(fread(shards[shard_idx], to_read, 1, ctx->imageStream) != 1)
1232 {
1233 present[shard_idx] = 0;
1234 continue;
1235 }
1236 }
1237 else if(parity_header.compression == kCompressionLzma)
1238 {
1239 uint32_t cmp_data_len = parity_header.cmpLength - LZMA_PROPERTIES_LENGTH;
1240 uint8_t lzma_props[LZMA_PROPERTIES_LENGTH];
1241 if(fread(lzma_props, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream) != 1 )
1242 {
1243 present[shard_idx] = 0;
1244 continue;
1245 }
1246 uint8_t *cmp = (uint8_t *)malloc(cmp_data_len);
1247 if(!cmp) { present[shard_idx] = 0; continue; }
1248 if(fread(cmp, cmp_data_len, 1, ctx->imageStream) != 1) { free(cmp); present[shard_idx] = 0; continue; }
1249
1250 size_t out_size = shard_size;
1251 size_t lzma_src = (size_t)cmp_data_len;
1252 aaruf_lzma_decode_buffer(shards[shard_idx], &out_size, cmp, &lzma_src, lzma_props, LZMA_PROPERTIES_LENGTH);
1253 free(cmp);
1254 }
1255 else if(parity_header.compression == kCompressionZstd)
1256 {
1257 uint8_t *cmp = (uint8_t *)malloc(parity_header.cmpLength);
1258 if(!cmp) { present[shard_idx] = 0; continue; }
1259 if(fread(cmp, parity_header.cmpLength, 1, ctx->imageStream) != 1) { free(cmp); present[shard_idx] = 0; continue; }
1260
1261 aaruf_zstd_decode_buffer(shards[shard_idx], shard_size, cmp, parity_header.cmpLength);
1262 free(cmp);
1263 }
1264 else
1265 {
1266 present[shard_idx] = 0;
1267 continue;
1268 }
1269 present[shard_idx] = 1;
1270 }
1271
1272 /* Always use the original RS(K,M) codec — partial stripes have zero-padded unused positions */
1273 rs_context *rs = (rs_context *)ctx->ec_rs_ctx;
1274 if(!rs)
1275 {
1276 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
1277 free(shards); free(present);
1278 ctx->ec_recovery_in_progress = false;
1280 }
1281
1282 /* RS decode */
1283 int rc = rs_decode(rs, shards, present, shard_size);
1284
1285 if(rc != 0)
1286 {
1287 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
1288 free(shards); free(present);
1289 ctx->ec_recovery_in_progress = false;
1291 }
1292
1293 /* Find the shard corresponding to our corrupted block */
1294 uint16_t our_pos = le->position;
1295 uint8_t *recovered_shard = shards[our_pos];
1296
1297 /* Parse the recovered BlockHeader */
1298 BlockHeader recovered_header;
1299 memcpy(&recovered_header, recovered_shard, sizeof(BlockHeader));
1300
1301 /* Decompress the recovered payload */
1302 uint32_t hdr_size = sizeof(BlockHeader);
1303 uint8_t *recovered_payload = recovered_shard + hdr_size;
1304 uint32_t payload_len = recovered_header.cmpLength;
1305
1306 uint8_t *block = NULL;
1307
1308 if(recovered_header.compression == kCompressionNone)
1309 {
1310 block = (uint8_t *)malloc(recovered_header.length);
1311 if(block) memcpy(block, recovered_payload, recovered_header.length);
1312 }
1313 else if(recovered_header.compression == kCompressionLzma)
1314 {
1315 uint8_t *lzma_props = recovered_payload;
1316 uint8_t *lzma_data = recovered_payload + LZMA_PROPERTIES_LENGTH;
1317 uint32_t lzma_data_len = payload_len - LZMA_PROPERTIES_LENGTH;
1318
1319 block = (uint8_t *)malloc(recovered_header.length);
1320 if(block)
1321 {
1322 size_t out_size = recovered_header.length;
1323 size_t lzma_src2 = (size_t)lzma_data_len;
1324 aaruf_lzma_decode_buffer(block, &out_size, lzma_data, &lzma_src2, lzma_props, LZMA_PROPERTIES_LENGTH);
1325 }
1326 }
1327 else if(recovered_header.compression == kCompressionZstd)
1328 {
1329 block = (uint8_t *)malloc(recovered_header.length);
1330 if(block)
1331 aaruf_zstd_decode_buffer(block, recovered_header.length, recovered_payload, payload_len);
1332 }
1333 else if(recovered_header.compression == kCompressionFlac)
1334 {
1335 block = (uint8_t *)malloc(recovered_header.length);
1336 if(block)
1337 aaruf_flac_decode_redbook_buffer(block, recovered_header.length, recovered_payload, payload_len);
1338 }
1339
1340 int32_t result = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
1341
1342 if(block)
1343 {
1344 /* Verify recovered uncompressed data CRC64 */
1345 uint64_t block_crc = aaruf_crc64_data(block, recovered_header.length);
1346 if(block_crc == recovered_header.crc64)
1347 {
1348 /* Extract the requested sector */
1349 uint32_t sector_size = recovered_header.sectorSize;
1350 if(sector_size > 0 && offset * sector_size + sector_size <= recovered_header.length)
1351 {
1352 memcpy(data, block + offset * sector_size, sector_size);
1353 *length = sector_size;
1354 result = AARUF_STATUS_OK;
1355
1356 /* Cache the recovered block so subsequent sector reads from the same
1357 * block don't re-trigger recovery (this is the critical optimization). */
1358 add_to_cache_uint64(&ctx->block_cache, block_offset, block);
1359
1360 /* Also cache the recovered BlockHeader */
1361 BlockHeader *cached_hdr = (BlockHeader *)malloc(sizeof(BlockHeader));
1362 if(cached_hdr)
1363 {
1364 memcpy(cached_hdr, &recovered_header, sizeof(BlockHeader));
1365 add_to_cache_uint64(&ctx->block_header_cache, block_offset, cached_hdr);
1366 }
1367
1368 block = NULL; /* Ownership transferred to cache — don't free */
1369 }
1370 }
1371 free(block); /* Only frees if not transferred to cache */
1372 }
1373
1374 for(uint16_t i = 0; i < total_shards; i++) free(shards[i]);
1375 free(shards);
1376 free(present);
1377
1378 ctx->ec_recovery_in_progress = false;
1379 return result;
1380}
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:115
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
#define AARU_MAGIC
Magic identifier for AaruFormat container (ASCII "AARUFRMT").
Definition consts.h:64
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
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
size_t aaruf_zstd_encode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, size_t src_size, int level, int num_threads)
Encodes a buffer using Zstandard compression.
Definition zstd.c:59
#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:642
static UT_icd ec_stripe_icd
Definition erasure.c:65
void ec_load_ecmb(aaruformat_context *ctx)
Try to load the ECMB from the recovery footer at EOF.
Definition erasure.c:941
void ec_finalize(aaruformat_context *ctx)
Flush all partial data stripes and write parity for all groups + ECMB + recovery footer.
Definition erasure.c:690
#define WRITE_GROUP(desc, desc_len, grp)
int32_t aaruf_set_erasure_coding_auto(void *context, uint8_t recovery_percent)
Configure erasure coding from a desired recovery percentage.
Definition erasure.c:193
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:228
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:1145
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
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:304
void ec_free(aaruformat_context *ctx)
Free all erasure coding state from the context.
Definition erasure.c:876
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:495
On-disk structures for erasure coding recovery data.
Public error and status code definitions for libaaruformat.
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:81
#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_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
#define LZMA_THREADS(ctx)
Clamp num_threads to LZMA's valid range [1, 2].
Definition internal.h:23
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)
Adds a value to the cache with a uint64_t key, evicting LRU if full.
Definition lru.c:48
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.
static const uint32_t K[64]
Definition sha256.c:31
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 (128 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
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
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:175
uint64_t * ec_data_shard_crcs
Array of K * K CRC64 values for blocks in active stripes.
Definition context.h:387
DdtHeader2 user_data_ddt_header
Active user data DDT v2 header (primary table meta).
Definition context.h:192
uint32_t * ec_data_block_sizes
Array of K * K actual on-disk sizes for blocks in active stripes.
Definition context.h:386
bool compression_enabled
True if block compression enabled (writing path).
Definition context.h:304
void * ec_rs_ctx
rs_context* (opaque RS codec), NULL if EC disabled.
Definition context.h:383
uint8_t ec_algorithm
ErasureCodingAlgorithm (0=XOR, 1=RS-Vandermonde).
Definition context.h:379
bool ec_recovery_in_progress
Recursion guard for recovery (prevents infinite loops).
Definition context.h:398
struct CacheHeader block_header_cache
LRU/Cache header for block headers.
Definition context.h:259
uint32_t ec_data_shard_size
Max on-disk block size for data blocks (fixed at creation).
Definition context.h:382
uint32_t ec_read_stripe_count
Number of data stripes parsed from ECMB.
Definition context.h:395
struct CacheHeader block_cache
LRU/Cache header for block payloads.
Definition context.h:260
bool dirty_index_block
True if index block should be written during close.
Definition context.h:345
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:178
UT_array * ec_data_stripes
Completed data stripe descriptors (serialized to ECMB).
Definition context.h:390
bool is_writing
True if context opened/created for writing.
Definition context.h:295
BlockHeader current_block_header
Header for block currently being assembled (write path).
Definition context.h:284
uint64_t magic
File magic (AARU_MAGIC) post-open.
Definition context.h:177
uint64_t next_block_position
Absolute file offset where next block will be written.
Definition context.h:285
uint64_t * ec_data_block_offsets
Array of K * K file offsets for blocks in active stripes.
Definition context.h:385
uint32_t ec_total_data_blocks
Total data blocks written (counter for round-robin assignment).
Definition context.h:389
uint16_t ec_M
Parity blocks per stripe.
Definition context.h:381
bool has_zstd_blocks
True if any block was actually written with Zstandard compression.
Definition context.h:306
int zstd_level
Zstandard compression level (writing path, default 19).
Definition context.h:307
uint16_t * ec_data_stripe_counts
Array of K: blocks accumulated per stripe slot.
Definition context.h:388
void * ec_read_stripes
Parsed EcReadStripe array for data group, NULL if no ECMB.
Definition context.h:394
void * ec_block_lookup
uthash: block file offset → stripe index + position.
Definition context.h:396
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
int num_threads
Compression worker threads (1 = single-threaded, default).
Definition context.h:308
bool ec_enabled
True if erasure coding is active.
Definition context.h:391
uint8_t ** ec_data_parity
Array of K * M parity buffers (interleaved stripe slots).
Definition context.h:384
bool use_zstd
Use Zstandard instead of LZMA for data blocks.
Definition context.h:305
uint32_t lzma_dict_size
LZMA dictionary size (writing path).
Definition context.h:302
uint16_t ec_K
Data blocks per stripe.
Definition context.h:380
bool ec_recovery_available
True if ECMB loaded and recovery is possible.
Definition context.h:397