ZXC 0.15.0
High-performance asymmetric lossless compression library
Loading...
Searching...
No Matches
zxc_seekable.c
Go to the documentation of this file.
1// SPDX-License-Identifier: BSD-3-Clause
2/*
3 * ZXC - High-performance lossless compression
4 *
5 * Copyright (c) Bertrand Lebonnois and contributors.
6 */
7
40#include "../../include/zxc_seekable.h"
41
42#include "../../include/zxc_dict.h"
43#include "../../include/zxc_error.h"
44#include "zxc_internal.h"
45#include "zxc_threads.h"
46
47// =========================================================================
48// Seekable Reader (Opaque Handle)
49// =========================================================================
50
55typedef struct {
56 uint64_t base; /* archive offset of its file header */
57 uint64_t decomp_base; /* decompressed offset of its first byte */
58 uint64_t block_base; /* archive-wide index of its first block */
59 uint64_t num_blocks; /* ceil(total_decomp / block_size) */
60 uint64_t total_decomp; /* from its footer */
61 uint64_t table_off; /* first table entry, absolute */
62 uint64_t eof_off; /* EOF block header, relative to base like the anchors */
63 uint64_t entry_max; /* largest legal block on disk: header + block_size + checksum */
64 uint32_t block_size; /* a power of 2 in [4 KB, 2 MB] */
65 uint32_t dict_id; /* 0 for none */
66 int has_checksums;
67} zxc_seek_frame_t;
68
70#define ZXC_SEEK_MAX_FRAMES (1U << 20)
71
73#define ZXC_SEEK_TAIL_HDRS (2 * ZXC_BLOCK_HEADER_SIZE)
74
76typedef struct {
77 uint8_t* data;
78 size_t size;
79 uint32_t id;
80 int has_huf;
81 uint8_t huf[ZXC_HUF_TABLE_SIZE]; /* shared literal table, when has_huf */
82} zxc_seek_dict_t;
83
84struct zxc_seekable_s {
85 // Source - exactly one of {src, reader.read_at} is set. The FILE* variant
86 // wraps pread() in its own reader ctx, indistinguishable from here.
87 const uint8_t* src;
88 uint64_t src_size;
89 zxc_reader_t reader; /* user-supplied callback reader; read_at == NULL when unused */
90
91 // Reader context owned by the handle and freed in zxc_seekable_free, set by
92 // the thin wrappers. NULL when the caller owns reader.ctx itself.
93 void* owned_reader_ctx;
94
95 // Frames in archive order; their tables stay on disk.
96 zxc_seek_frame_t* frames;
97 size_t num_frames;
98 uint64_t num_blocks; /* over every frame */
99 uint64_t total_decomp; /* over every frame */
100 uint32_t max_block_size; /* sizes the decoding contexts */
101
102 int verify_checksums; /* caller's switch; needs the frame's checksums too */
103
104 // Reusable decompression context and compressed-block scratch. Both belong
105 // to the single-threaded path, which is already not reentrant per handle;
106 // the multi-threaded path gives each worker its own.
107 zxc_cctx_t dctx;
108 int dctx_initialized;
109 const zxc_seek_dict_t* dctx_dict; /* the dictionary dctx holds, NULL for none */
110 uint8_t* read_buf;
111 size_t read_buf_cap;
112
113 // Dictionaries installed by zxc_seekable_set_dict, one per id.
114 zxc_seek_dict_t* dicts;
115 size_t num_dicts;
116 size_t max_dict_size; /* sizes the contexts' [dict | decode] buffers */
117};
118
128typedef struct {
129 const uint8_t* data; /* in-memory archive, NULL in callback mode */
130 const zxc_reader_t* rdr; /* callback mode, NULL in buffer mode */
131 uint64_t size; /* archive size, both modes */
132} zxc_seek_source_t;
133
140static int zxc_seek_source_read(const zxc_seek_source_t* src, void* dst, const size_t len,
141 const uint64_t off) {
142 if (UNLIKELY(off > src->size || (uint64_t)len > src->size - off))
144 if (src->data) {
145 ZXC_MEMCPY(dst, src->data + off, len);
146 return ZXC_OK;
147 }
148 const int64_t r = src->rdr->read_at(src->rdr->ctx, dst, len, off);
149 // A code outside int would truncate, possibly to ZXC_OK.
150 if (UNLIKELY(r != (int64_t)len)) return (r < 0 && r >= INT_MIN) ? (int)r : ZXC_ERROR_IO;
151 return ZXC_OK;
152}
153
157static zxc_seek_source_t zxc_seek_source_of(const zxc_seekable* s) {
158 const zxc_seek_source_t src = {s->src, s->src ? NULL : &s->reader, s->src_size};
159 return src;
160}
161
163static int zxc_seek_scan_read(const zxc_scan_src_t* scan, const uint64_t off, void* dst,
164 const size_t len) {
165 return zxc_seek_source_read((const zxc_seek_source_t*)scan->ctx, dst, len, off);
166}
167
178static int zxc_seek_parse_frame(zxc_seek_source_t* src, const uint64_t end, zxc_seek_frame_t* f) {
179 const zxc_scan_src_t scan = {zxc_seek_scan_read, NULL, src, end};
181 uint64_t start = 0;
182 size_t footer_len = 0;
183 if (UNLIKELY(zxc_scan_frame(&scan, end, &start, &fi, &footer_len) != ZXC_OK ||
184 !fi.has_seek_table))
185 return 0;
186 const uint64_t frame = end - start;
187 const uint32_t block_size = (uint32_t)fi.block_size;
188 const uint64_t num_blocks = zxc_seek_block_count(fi.decompressed_size, block_size);
189
190 // Layout: [header 16][data blocks][EOF 8][SEK block][footer], in 64 bits:
191 // the SEK header's field only holds the table size modulo 2^32.
192 const uint64_t seek_block_total = ZXC_BLOCK_HEADER_SIZE + zxc_seek_table_bytes(num_blocks);
193 if (UNLIKELY(seek_block_total + footer_len >
195 return 0;
196 const uint64_t eof_off = frame - footer_len - seek_block_total - ZXC_BLOCK_HEADER_SIZE;
197
198 // Geometry only, no table read: num_blocks blocks of [header, entry_max] bytes
199 // must fit the data area. Groups are checked on access.
200 const uint64_t entry_max = (uint64_t)ZXC_BLOCK_HEADER_SIZE + block_size +
202 const uint64_t data_area = eof_off - ZXC_FILE_HEADER_SIZE;
203 const uint64_t min_span = num_blocks * ZXC_BLOCK_HEADER_SIZE;
204 const uint64_t max_span =
205 num_blocks > UINT64_MAX / entry_max ? UINT64_MAX : num_blocks * entry_max;
206 if (UNLIKELY(data_area < min_span || data_area > max_span)) return 0;
207
208 f->base = start;
209 f->num_blocks = num_blocks;
210 f->total_decomp = fi.decompressed_size;
211 f->table_off = start + eof_off + 2 * ZXC_BLOCK_HEADER_SIZE;
212 f->eof_off = eof_off;
213 f->entry_max = entry_max;
214 f->block_size = block_size;
215 f->has_checksums = fi.has_checksum;
216 f->dict_id = fi.dict_id;
217 return 1;
218}
219
224static zxc_seekable* zxc_seekable_parse(const zxc_seek_source_t* source) {
225 zxc_seek_source_t src = *source;
226 zxc_seek_frame_t* frames = NULL;
227 size_t n = 0;
228 size_t cap = 0;
229 uint64_t end = src.size;
230 do {
231 if (UNLIKELY(n == ZXC_SEEK_MAX_FRAMES)) goto fail;
232 if (n == cap) {
233 cap = cap ? 2 * cap : 4;
234 zxc_seek_frame_t* const grown =
235 (zxc_seek_frame_t*)ZXC_REALLOC(frames, cap * sizeof(*frames));
236 if (UNLIKELY(!grown)) goto fail; // LCOV_EXCL_LINE
237 frames = grown;
238 }
239 if (UNLIKELY(!zxc_seek_parse_frame(&src, end, &frames[n]))) goto fail;
240 // The last frame's EOF/SEK headers (a plain archive's only frame) now: a
241 // lying seek flag fails the open.
242 if (n == 0) {
243 uint8_t hdrs[ZXC_SEEK_TAIL_HDRS];
244 const zxc_seek_frame_t* const f = &frames[0];
245 if (UNLIKELY(zxc_seek_source_read(&src, hdrs, sizeof(hdrs),
246 f->table_off - ZXC_SEEK_TAIL_HDRS) != ZXC_OK ||
247 zxc_check_eof_header(hdrs) != ZXC_OK ||
249 f->total_decomp, f->block_size, NULL) != ZXC_OK))
250 goto fail;
251 }
252 end = frames[n++].base;
253 } while (end > 0);
254
255 // Walked back: archive order, then running bytes and blocks.
256 uint64_t decomp = 0;
257 uint64_t blocks = 0;
258 uint32_t max_block_size = 0;
259 for (size_t i = 0; i < n / 2; i++) {
260 const zxc_seek_frame_t t = frames[i];
261 frames[i] = frames[n - 1 - i];
262 frames[n - 1 - i] = t;
263 }
264 for (size_t i = 0; i < n; i++) {
265 zxc_seek_frame_t* const f = &frames[i];
266 if (UNLIKELY(f->total_decomp > UINT64_MAX - decomp)) goto fail;
267 f->decomp_base = decomp;
268 f->block_base = blocks;
269 decomp += f->total_decomp;
270 blocks += f->num_blocks; // no wrap: each frame holds at least 8 bytes per block
271 if (f->block_size > max_block_size) max_block_size = f->block_size;
272 }
273
274 zxc_seekable* const s = (zxc_seekable*)ZXC_CALLOC(1, sizeof(zxc_seekable));
275 if (UNLIKELY(!s)) goto fail; // LCOV_EXCL_LINE
276 if (src.rdr) s->reader = *src.rdr;
277 s->src = src.data;
278 s->src_size = src.size;
279 s->frames = frames;
280 s->num_frames = n;
281 s->num_blocks = blocks;
282 s->total_decomp = decomp;
283 s->max_block_size = max_block_size;
284 s->verify_checksums = 0; /* opt-in, see zxc_seekable_set_checksum */
285 return s;
286
287fail:
288 ZXC_FREE(frames);
289 return NULL;
290}
291
293static const zxc_seek_frame_t* zxc_seek_frame_of_block(const zxc_seekable* s, const uint64_t idx) {
294 size_t lo = 0;
295 size_t hi = s->num_frames;
296 while (lo < hi) { // the first frame starting past idx; the one before holds it
297 const size_t mid = lo + (hi - lo) / 2;
298 if (s->frames[mid].block_base <= idx)
299 lo = mid + 1;
300 else
301 hi = mid;
302 }
303 return &s->frames[lo - 1];
304}
305
307static uint64_t zxc_seek_block_of_offset(const zxc_seekable* s, const uint64_t offset) {
308 size_t lo = 0;
309 size_t hi = s->num_frames;
310 while (lo < hi) {
311 const size_t mid = lo + (hi - lo) / 2;
312 if (s->frames[mid].decomp_base <= offset)
313 lo = mid + 1;
314 else
315 hi = mid;
316 }
317 const zxc_seek_frame_t* const f = &s->frames[lo - 1];
318 return f->block_base + ((offset - f->decomp_base) >> zxc_ctz32(f->block_size));
319}
320
323static size_t zxc_seek_spans_raw_max(const uint64_t first, const uint32_t n) {
324 const uint64_t groups = (first + n - 1) / ZXC_SEEK_GROUP - first / ZXC_SEEK_GROUP + 1;
325 return ZXC_SEEK_TAIL_HDRS + (size_t)groups * ZXC_SEEK_GROUP_BYTES;
326}
327
349static int zxc_seek_load_spans(const zxc_seekable* s, const zxc_seek_frame_t* f,
350 const uint64_t first, const uint32_t n, uint64_t* RESTRICT starts,
351 uint32_t* RESTRICT sizes, uint8_t* RESTRICT raw) {
352 const uint64_t last_group = zxc_seek_group_count(f->num_blocks) - 1;
353 const uint64_t g0 = first / ZXC_SEEK_GROUP;
354 const uint64_t g1 = (first + n - 1) / ZXC_SEEK_GROUP;
355
356 size_t need = 0;
357 for (uint64_t g = g0; g <= g1; g++)
358 need += ZXC_SEEK_ANCHOR_SIZE +
359 (size_t)zxc_seek_group_len(f->num_blocks, g) * ZXC_SEEK_SIZE_ENTRY;
360 // The EOF/SEK headers, checked on each access rather than kept as state; read
361 // with group 0 when it is wanted.
362 const zxc_seek_source_t src = zxc_seek_source_of(s);
363 const uint64_t at = f->table_off + g0 * ZXC_SEEK_GROUP_BYTES;
364 int rc = g0 == 0 ? zxc_seek_source_read(&src, raw, ZXC_SEEK_TAIL_HDRS + need,
367 f->table_off - ZXC_SEEK_TAIL_HDRS);
368 if (rc == ZXC_OK && g0 != 0)
369 rc = zxc_seek_source_read(&src, raw + ZXC_SEEK_TAIL_HDRS, need, at);
370 if (UNLIKELY(rc != ZXC_OK)) return rc;
371 if (UNLIKELY(zxc_check_eof_header(raw) != ZXC_OK ||
373 f->total_decomp, f->block_size, NULL) != ZXC_OK))
375
376 const uint8_t* p = raw + ZXC_SEEK_TAIL_HDRS;
377 for (uint64_t g = g0; g <= g1; g++) {
378 uint64_t pos = zxc_le64(p);
380 if (UNLIKELY(g == 0 ? pos != ZXC_FILE_HEADER_SIZE
381 : pos < ZXC_FILE_HEADER_SIZE || pos > f->eof_off))
383
384 const uint32_t cnt = zxc_seek_group_len(f->num_blocks, g);
385 for (uint32_t k = 0; k < cnt; k++, p += ZXC_SEEK_SIZE_ENTRY) {
386 const uint32_t sz = zxc_le32(p);
387 if (UNLIKELY(sz < ZXC_BLOCK_HEADER_SIZE || sz > f->entry_max))
389 const uint64_t idx = g * ZXC_SEEK_GROUP + k;
390 if (idx >= first && idx < first + n) {
391 starts[idx - first] = f->base + pos;
392 sizes[idx - first] = sz;
393 }
394 pos += sz; // no wrap: at most ZXC_SEEK_GROUP sizes of at most entry_max
395 }
396 // Within the data area; the last group, exactly on the EOF block.
397 if (UNLIKELY(pos > f->eof_off || (g == last_group && pos != f->eof_off)))
399 }
400 return ZXC_OK;
401}
402
409zxc_seekable* zxc_seekable_open(const void* src, const size_t src_size) {
410 if (UNLIKELY(!src || src_size == 0)) return NULL;
411 const zxc_seek_source_t source = {(const uint8_t*)src, NULL, (uint64_t)src_size};
412 return zxc_seekable_parse(&source);
413}
414
415// zxc_seekable_open_file lives elsewhere: it builds a zxc_reader_t over pread()
416// and delegates below, keeping this TU free of <stdio.h>.
417
427 if (UNLIKELY(!r || !r->read_at || r->size == 0)) return NULL;
428 const zxc_seek_source_t source = {NULL, r, r->size};
429 return zxc_seekable_parse(&source);
430}
431
435uint64_t zxc_seekable_get_num_blocks(const zxc_seekable* s) { return s ? s->num_blocks : 0; }
436
441 return s ? s->total_decomp : 0;
442}
443
447uint32_t zxc_seekable_get_block_comp_size(const zxc_seekable* s, const uint64_t block_idx) {
448 if (UNLIKELY(!s || block_idx >= s->num_blocks)) return 0;
449 const zxc_seek_frame_t* const f = zxc_seek_frame_of_block(s, block_idx);
450 uint64_t start = 0;
451 uint32_t size = 0;
453 if (UNLIKELY(zxc_seek_load_spans(s, f, block_idx - f->block_base, 1, &start, &size, raw) !=
454 ZXC_OK))
455 return 0;
456 return size;
457}
458
470static uint32_t zxc_seek_decomp_size(const uint32_t block_size, const uint64_t total_decomp,
471 const uint64_t idx) {
472 const uint64_t start = idx * (uint64_t)block_size;
473 const uint64_t remaining = total_decomp - start;
474 return (remaining >= (uint64_t)block_size) ? block_size : (uint32_t)remaining;
475}
476
478uint32_t zxc_seekable_get_block_decomp_size(const zxc_seekable* s, const uint64_t block_idx) {
479 if (UNLIKELY(!s || block_idx >= s->num_blocks)) return 0;
480 const zxc_seek_frame_t* const f = zxc_seek_frame_of_block(s, block_idx);
481 return zxc_seek_decomp_size(f->block_size, f->total_decomp, block_idx - f->block_base);
482}
483
484// =========================================================================
485// Random-Access Decompression
486// =========================================================================
487
494static int zxc_seek_use_dict(const zxc_seekable* s, zxc_cctx_t* ctx, const zxc_seek_dict_t** held,
495 const zxc_seek_frame_t* f) {
496 const zxc_seek_dict_t* d = NULL;
497 for (size_t i = 0; f->dict_id && i < s->num_dicts && !d; i++)
498 if (s->dicts[i].id == f->dict_id) d = &s->dicts[i];
499 if (UNLIKELY(f->dict_id && !d)) return ZXC_ERROR_DICT_REQUIRED;
500 if (d != *held) {
501 if (d) ZXC_MEMCPY(ctx->dict_buffer, d->data, d->size);
502 if (UNLIKELY(zxc_cctx_attach_dict_huf(ctx, d && d->has_huf ? d->huf : NULL) != ZXC_OK))
503 return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
504 *held = d;
505 }
506 ctx->dict_size = d ? d->size : 0;
507 return ZXC_OK;
508}
509
528static int zxc_seek_read_block(const zxc_seekable* s, const uint64_t off, const uint32_t csz,
529 const int has_cs, uint8_t* buf, const size_t buf_cap) {
530 if (UNLIKELY(csz > buf_cap)) return ZXC_ERROR_DST_TOO_SMALL;
531 const zxc_seek_source_t src = zxc_seek_source_of(s);
532 const int rc = zxc_seek_source_read(&src, buf, csz, off);
533 if (UNLIKELY(rc != ZXC_OK)) return rc;
534
536 const int hdr_res = zxc_read_block_header(buf, csz, &bh);
537 if (UNLIKELY(hdr_res != ZXC_OK)) return hdr_res;
538 const uint64_t on_disk =
539 (uint64_t)ZXC_BLOCK_HEADER_SIZE + bh.comp_size + (has_cs ? ZXC_BLOCK_CHECKSUM_SIZE : 0U);
540 if (UNLIKELY(on_disk != csz)) return ZXC_ERROR_CORRUPT_DATA;
541 return (int)csz;
542}
543
553int64_t zxc_seekable_decompress_range(zxc_seekable* s, void* dst, const size_t dst_capacity,
554 const uint64_t offset, const size_t len) {
555 if (UNLIKELY(len == 0)) return 0;
556 if (UNLIKELY(!s || !dst)) return ZXC_ERROR_NULL_INPUT;
557 if (UNLIKELY(dst_capacity < len)) return ZXC_ERROR_DST_TOO_SMALL;
558 if (UNLIKELY(offset > s->total_decomp || len > s->total_decomp - offset))
560
561 // Initialize decompression context on first use.
562 if (!s->dctx_initialized) {
563 if (UNLIKELY(zxc_cctx_init(&s->dctx, (size_t)s->max_block_size, 0, 0, 0,
564 s->max_dict_size) != ZXC_OK))
565 return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
566 s->dctx_initialized = 1;
567 s->dctx_dict = NULL;
568 }
569
570 const uint64_t blk_start = zxc_seek_block_of_offset(s, offset);
571 const uint64_t blk_end = zxc_seek_block_of_offset(s, offset + len - 1);
572 const zxc_seek_frame_t* f = zxc_seek_frame_of_block(s, blk_start);
573
574 uint8_t* out = (uint8_t*)dst;
575 size_t remaining = len;
576
577 // One slice, and one read, per table group of a frame.
578 uint64_t starts[ZXC_SEEK_GROUP] = {0};
579 uint32_t sizes[ZXC_SEEK_GROUP] = {0};
581 uint64_t bi = blk_start;
582 while (bi <= blk_end) {
583 while (bi >= f->block_base + f->num_blocks) f++; // past this frame, empty ones too
584 const uint64_t local = bi - f->block_base;
585 const uint64_t left = blk_end - bi + 1;
586 const uint64_t in_frame = f->num_blocks - local;
587 const uint32_t to_group_end = ZXC_SEEK_GROUP - (uint32_t)(local % ZXC_SEEK_GROUP);
588 uint32_t n = left < to_group_end ? (uint32_t)left : to_group_end;
589 if (in_frame < n) n = (uint32_t)in_frame;
590 const int span_res = zxc_seek_load_spans(s, f, local, n, starts, sizes, raw);
591 if (UNLIKELY(span_res < 0)) return span_res;
592
593 // Block scratch kept on the handle, sized to the slice's largest block, not entry_max.
594 uint64_t need = 0;
595 for (uint32_t k = 0; k < n; k++)
596 if (sizes[k] > need) need = sizes[k];
597 need += ZXC_PAD_SIZE;
598 if (s->read_buf_cap < need) {
599 uint8_t* const nb = (uint8_t*)ZXC_REALLOC(s->read_buf, (size_t)need);
600 if (UNLIKELY(!nb)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
601 s->read_buf = nb;
602 s->read_buf_cap = (size_t)need;
603 }
604 uint8_t* const read_buf = s->read_buf;
605
606 // The frame's geometry and dictionary: the context is sized for the largest.
607 const int dict_res = zxc_seek_use_dict(s, &s->dctx, &s->dctx_dict, f);
608 if (UNLIKELY(dict_res != ZXC_OK)) return dict_res;
609 s->dctx.chunk_size = f->block_size;
610 s->dctx.checksum_enabled = f->has_checksums && s->verify_checksums;
611 const size_t work_sz = (size_t)f->block_size + ZXC_DECOMPRESS_TAIL_PAD;
612
613 for (uint32_t k = 0; k < n; k++, bi++) {
614 const uint64_t lb = local + k; // position in the frame: the checksum seed
615 const int read_res = zxc_seek_read_block(s, starts[k], sizes[k], f->has_checksums,
616 read_buf, s->read_buf_cap);
617 if (UNLIKELY(read_res < 0)) return read_res;
618
619 // Decompress the block: when a dictionary is active, decode into the
620 // cctx-owned dict_buffer (which has dict content prepended) so that
621 // match copies referencing dictionary bytes resolve naturally.
622 uint8_t* dec_dst =
623 s->dctx.dict_buffer ? s->dctx.dict_buffer + s->dctx.dict_size : s->dctx.work_buf;
624 const int dec_res = zxc_decompress_chunk_wrapper(&s->dctx, read_buf, (size_t)read_res,
625 dec_dst, work_sz, lb);
626 if (UNLIKELY(dec_res < 0)) return dec_res;
627 if (UNLIKELY((uint32_t)dec_res !=
628 zxc_seek_decomp_size(f->block_size, f->total_decomp, lb)))
630
631 // Calculate which portion of this block's decompressed data we need
632 const uint64_t blk_decomp_start = f->decomp_base + lb * f->block_size;
633 const size_t skip =
634 (offset > blk_decomp_start) ? (size_t)(offset - blk_decomp_start) : 0;
635 const size_t avail = (size_t)dec_res - skip;
636 const size_t copy = (avail < remaining) ? avail : remaining;
637
638 ZXC_MEMCPY(out, dec_dst + skip, copy);
639 out += copy;
640 remaining -= copy;
641 }
642 }
643
644 if (UNLIKELY(remaining != 0)) return ZXC_ERROR_CORRUPT_DATA;
645 return (int64_t)len;
646}
647
648// =========================================================================
649// Multi-Threaded Random-Access Decompression (Fork-Join)
650// =========================================================================
651
659typedef struct {
660 uint64_t block_idx; /* position in its frame: the checksum seed */
661 uint64_t off; /* where it starts in the archive (validated span) */
662 size_t dst_off; /* output offset from the caller's buffer start */
663 size_t skip; /* bytes to skip at start of decompressed block */
664 size_t copy_len; /* bytes to copy out */
665 uint32_t csz; /* its on-disk size */
666 uint32_t block_size; /* its frame's */
667 const zxc_seek_frame_t* frame;
668 uint32_t decomp_sz; /* what it decodes to */
669 int has_cs; /* its frame carries block checksums */
670 int result; /* 0 = OK, < 0 = error; starts negative, see the launch loop */
671} zxc_seek_mt_job_t;
672
687typedef struct {
688 const zxc_seekable* s; /* handle being read (read-only) */
689 zxc_seek_mt_job_t* jobs; /* job array; each worker touches only its stripe */
690 uint8_t* dst_base; /* start of the caller's output buffer */
691 uint32_t num_jobs; /* job count (stripe iteration bound) */
692 uint32_t stride; /* worker count attempted; stripes partition by it */
693 uint32_t next; /* next stripe index to hand out, under lock */
694 pthread_mutex_t lock; /* guards next */
695} zxc_seek_mt_shared_t;
696
704static void zxc_seek_mt_fail_stripe(const zxc_seek_mt_shared_t* sh, const uint32_t first,
705 const int code) {
706 for (uint32_t i = first; i < sh->num_jobs; i += sh->stride) sh->jobs[i].result = code;
707}
708
723static void* zxc_seek_mt_worker(void* arg) {
724 zxc_seek_mt_shared_t* const sh = (zxc_seek_mt_shared_t*)arg;
725 zxc_seek_mt_job_t* const jobs = sh->jobs;
726 const zxc_seekable* const s = sh->s;
727
728 // Claim a stripe: the main thread hands every worker the same argument.
729 pthread_mutex_lock(&sh->lock);
730 const uint32_t first = sh->next++;
731 pthread_mutex_unlock(&sh->lock);
732
733 // Thread-local decompression context (mode=0 for decompress-only)
734 zxc_cctx_t dctx;
735 if (UNLIKELY(zxc_cctx_init(&dctx, (size_t)s->max_block_size, 0, 0, 0, s->max_dict_size) !=
736 ZXC_OK)) {
737 // LCOV_EXCL_START
739 return NULL;
740 // LCOV_EXCL_STOP
741 }
742 const zxc_seek_dict_t* held = NULL;
743
744 // Read buffer sized for the largest compressed block of the stripe.
745 size_t max_csz = 0;
746 for (uint32_t i = first; i < sh->num_jobs; i += sh->stride) {
747 if (jobs[i].csz > max_csz) max_csz = jobs[i].csz;
748 }
749 uint8_t* const read_buf = (uint8_t*)ZXC_MALLOC(max_csz + ZXC_PAD_SIZE);
750 if (UNLIKELY(!read_buf)) {
751 // LCOV_EXCL_START
752 zxc_cctx_free(&dctx);
754 return NULL;
755 // LCOV_EXCL_STOP
756 }
757
758 for (uint32_t i = first; i < sh->num_jobs; i += sh->stride) {
759 zxc_seek_mt_job_t* const job = &jobs[i];
760
761 const int read_res = zxc_seek_read_block(s, job->off, job->csz, job->has_cs, read_buf,
762 max_csz + ZXC_PAD_SIZE);
763 if (UNLIKELY(read_res < 0)) {
764 job->result = read_res;
765 break;
766 }
767
768 const int dict_res = zxc_seek_use_dict(s, &dctx, &held, job->frame);
769 if (UNLIKELY(dict_res != ZXC_OK)) {
770 job->result = dict_res;
771 break;
772 }
773 uint8_t* dec_dst = dctx.dict_buffer ? dctx.dict_buffer + dctx.dict_size : dctx.work_buf;
774 dctx.chunk_size = job->block_size;
775 dctx.checksum_enabled = job->has_cs && s->verify_checksums;
776 const int dec_res = zxc_decompress_chunk_wrapper(
777 &dctx, read_buf, (size_t)read_res, dec_dst,
778 (size_t)job->block_size + ZXC_DECOMPRESS_TAIL_PAD, job->block_idx);
779
780 if (UNLIKELY(dec_res < 0)) {
781 job->result = dec_res;
782 break;
783 }
784 if (UNLIKELY((uint32_t)dec_res != job->decomp_sz)) {
785 job->result = ZXC_ERROR_CORRUPT_DATA;
786 break;
787 }
788
789 // Copy the requested portion directly into the caller's output buffer
790 ZXC_MEMCPY(sh->dst_base + job->dst_off, dec_dst + job->skip, job->copy_len);
791 job->result = 0;
792 }
793
794 ZXC_FREE(read_buf);
795 zxc_cctx_free(&dctx);
796 return NULL;
797}
798
807int64_t zxc_seekable_decompress_range_mt(zxc_seekable* s, void* dst, const size_t dst_capacity,
808 const uint64_t offset, const size_t len, int n_threads) {
809 if (UNLIKELY(len == 0)) return 0;
810 if (UNLIKELY(!s || !dst)) return ZXC_ERROR_NULL_INPUT;
811 if (UNLIKELY(dst_capacity < len)) return ZXC_ERROR_DST_TOO_SMALL;
812 if (UNLIKELY(offset > s->total_decomp || len > s->total_decomp - offset))
814
815 const uint64_t blk_start = zxc_seek_block_of_offset(s, offset);
816 const uint64_t blk_end = zxc_seek_block_of_offset(s, offset + len - 1);
817 const uint64_t jobs_64 = blk_end - blk_start + 1;
818
819 // Auto-detect thread count (0 = use all available cores)
820 if (n_threads == 0) n_threads = zxc_num_procs();
821
822 // Fallback to single-threaded path for trivial cases, and for a range holding
823 // more blocks than one job table can index (4 G blocks is 16 TiB of dst).
824 if (n_threads <= 1 || jobs_64 <= 1 || jobs_64 > UINT32_MAX) {
825 return zxc_seekable_decompress_range(s, dst, dst_capacity, offset, len);
826 }
827 const uint32_t num_jobs = (uint32_t)jobs_64;
828
829 // Cap threads to number of blocks and max limit
830 if ((uint32_t)n_threads > num_jobs) n_threads = (int)num_jobs;
831 if (n_threads > ZXC_MAX_THREADS) n_threads = ZXC_MAX_THREADS;
832
833 zxc_seek_mt_job_t* const jobs =
834 (zxc_seek_mt_job_t*)ZXC_CALLOC(num_jobs, sizeof(zxc_seek_mt_job_t));
835 uint64_t* const starts = (uint64_t*)ZXC_CALLOC(num_jobs, sizeof(uint64_t));
836 uint32_t* const sizes = (uint32_t*)ZXC_CALLOC(num_jobs, sizeof(uint32_t));
837 if (UNLIKELY(!jobs || !starts || !sizes)) {
838 // LCOV_EXCL_START
839 ZXC_FREE(jobs);
840 ZXC_FREE(starts);
841 ZXC_FREE(sizes);
842 return ZXC_ERROR_MEMORY;
843 // LCOV_EXCL_STOP
844 }
845
846 // Plan jobs frame by frame, each frame's table groups in one read.
847 const zxc_seek_frame_t* f = zxc_seek_frame_of_block(s, blk_start);
848 size_t out_off = 0;
849 size_t remaining = len;
850 int plan_res = ZXC_OK;
851 for (uint32_t i = 0; i < num_jobs && plan_res == ZXC_OK;) {
852 const uint64_t bi = blk_start + i;
853 while (bi >= f->block_base + f->num_blocks) f++; // past this frame, empty ones too
854 const uint64_t local = bi - f->block_base;
855 const uint64_t in_frame = f->num_blocks - local;
856 const uint32_t n = in_frame < num_jobs - i ? (uint32_t)in_frame : num_jobs - i;
857 uint8_t* const raw = (uint8_t*)ZXC_MALLOC(zxc_seek_spans_raw_max(local, n));
858 plan_res = raw ? zxc_seek_load_spans(s, f, local, n, starts + i, sizes + i, raw)
859 : ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
860 ZXC_FREE(raw);
861 for (uint32_t k = 0; k < n && plan_res == ZXC_OK; k++, i++) {
862 const uint64_t lb = local + k;
863 const uint64_t blk_decomp_start = f->decomp_base + lb * f->block_size;
864 const size_t skip =
865 (offset > blk_decomp_start) ? (size_t)(offset - blk_decomp_start) : 0;
866 const uint32_t blk_decomp_sz = zxc_seek_decomp_size(f->block_size, f->total_decomp, lb);
867 const size_t avail = blk_decomp_sz - skip;
868 const size_t copy = (avail < remaining) ? avail : remaining;
869
870 jobs[i].block_idx = lb;
871 jobs[i].off = starts[i];
872 jobs[i].csz = sizes[i];
873 jobs[i].block_size = f->block_size;
874 jobs[i].frame = f;
875 jobs[i].decomp_sz = blk_decomp_sz;
876 jobs[i].has_cs = f->has_checksums;
877 jobs[i].dst_off = out_off;
878 jobs[i].skip = skip;
879 jobs[i].copy_len = copy;
880 // Negative until a worker completes it: a stripe whose thread failed to
881 // start is then reported without anyone having to know which one.
882 jobs[i].result = ZXC_ERROR_MEMORY;
883
884 out_off += copy;
885 remaining -= copy;
886 }
887 }
888 ZXC_FREE(starts);
889 ZXC_FREE(sizes);
890 if (UNLIKELY(plan_res != ZXC_OK)) {
891 ZXC_FREE(jobs);
892 return plan_res;
893 }
894
895 // Launch one persistent worker per thread
896 pthread_t* const threads = (pthread_t*)ZXC_MALLOC((size_t)n_threads * sizeof(pthread_t));
897 if (UNLIKELY(!threads)) {
898 // LCOV_EXCL_START
899 ZXC_FREE(jobs);
900 return ZXC_ERROR_MEMORY;
901 // LCOV_EXCL_STOP
902 }
903
904 zxc_seek_mt_shared_t sh;
905 sh.s = s;
906 sh.jobs = jobs;
907 sh.dst_base = (uint8_t*)dst;
908 sh.num_jobs = num_jobs;
909 sh.stride = (uint32_t)n_threads;
910 sh.next = 0;
911 pthread_mutex_init(&sh.lock, NULL);
912
913 int launched = 0;
914 for (int t = 0; t < n_threads; t++) {
915 // A failed start leaves one stripe unclaimed; its jobs keep their
916 // negative result and the read reports it after the join.
917 if (UNLIKELY(pthread_create(&threads[launched], NULL, zxc_seek_mt_worker, &sh) != 0))
918 continue; // LCOV_EXCL_LINE
919 launched++;
920 }
921
922 // Join phase
923 for (int t = 0; t < launched; t++) pthread_join(threads[t], NULL);
924
925 pthread_mutex_destroy(&sh.lock);
926 ZXC_FREE(threads);
927
928 // Report the first error in job order, if any.
929 int64_t result = (int64_t)len;
930 for (uint32_t i = 0; i < num_jobs; i++) {
931 if (jobs[i].result < 0) {
932 result = (int64_t)jobs[i].result;
933 break;
934 }
935 }
936
937 ZXC_FREE(jobs);
938 return result;
939}
940
948 if (UNLIKELY(!s)) return;
949 if (s->dctx_initialized) zxc_cctx_free(&s->dctx);
950 ZXC_FREE(s->frames);
951 for (size_t i = 0; i < s->num_dicts; i++) ZXC_FREE(s->dicts[i].data);
952 ZXC_FREE(s->dicts);
953 ZXC_FREE(s->read_buf);
954 ZXC_FREE(s->owned_reader_ctx);
955 ZXC_FREE(s);
956}
957
964int zxc_seekable_set_checksum(zxc_seekable* s, const int enabled) {
965 if (UNLIKELY(!s)) return ZXC_ERROR_NULL_INPUT;
966 s->verify_checksums = enabled ? 1 : 0;
967 return ZXC_OK;
968}
969
978int zxc_seekable_set_dict(zxc_seekable* s, const void* dict, const size_t dict_size,
979 const void* dict_huf) {
980 if (UNLIKELY(!s || !dict || dict_size == 0)) return ZXC_ERROR_NULL_INPUT;
981 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
982 const uint32_t id = zxc_dict_id(dict, dict_size, (const uint8_t*)dict_huf);
983 int used = 0;
984 int any = 0;
985 for (size_t i = 0; i < s->num_frames && !used; i++) {
986 used = s->frames[i].dict_id == id;
987 any |= s->frames[i].dict_id != 0;
988 }
989 // No frame needs a dictionary: ignore it, as before. Otherwise one must use it.
990 if (!any) return ZXC_OK;
991 if (UNLIKELY(!used)) return ZXC_ERROR_DICT_MISMATCH;
992
993 zxc_seek_dict_t* d = NULL;
994 for (size_t i = 0; i < s->num_dicts && !d; i++)
995 if (s->dicts[i].id == id) d = &s->dicts[i];
996 if (!d) {
997 zxc_seek_dict_t* const grown =
998 (zxc_seek_dict_t*)ZXC_REALLOC(s->dicts, (s->num_dicts + 1) * sizeof(*s->dicts));
999 if (UNLIKELY(!grown)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
1000 s->dicts = grown;
1001 d = &s->dicts[s->num_dicts];
1002 ZXC_MEMSET(d, 0, sizeof(*d));
1003 }
1004 uint8_t* const copy = (uint8_t*)ZXC_MALLOC(dict_size);
1005 if (UNLIKELY(!copy)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
1006 ZXC_MEMCPY(copy, dict, dict_size);
1007 if (d == &s->dicts[s->num_dicts]) s->num_dicts++;
1008 ZXC_FREE(d->data);
1009 d->data = copy;
1010 d->size = dict_size;
1011 d->id = id;
1012 d->has_huf = dict_huf != NULL;
1013 if (dict_huf) ZXC_MEMCPY(d->huf, dict_huf, ZXC_HUF_TABLE_SIZE);
1014 if (dict_size > s->max_dict_size) s->max_dict_size = dict_size;
1015
1016 // The context's [dict | decode] buffer and the table it holds are re-carved
1017 // and reloaded on the next decompress.
1018 if (s->dctx_initialized) {
1019 zxc_cctx_free(&s->dctx);
1020 s->dctx_initialized = 0;
1021 }
1022 return ZXC_OK;
1023}
1024
1034 if (s) s->owned_reader_ctx = ctx;
1035}
uint32_t zxc_dict_id(const void *dict, size_t dict_size, const void *huf_lengths)
Computes the dictionary ID for the given content and optional table.
Definition zxc_dict.c:29
#define ZXC_DICT_SIZE_MAX
Maximum dictionary content size in bytes (64 KB - 1).
#define ZXC_HUF_TABLE_SIZE
Size of a packed literal Huffman code-lengths table (256 symbols, 4 bits each): the shared table in a...
@ ZXC_ERROR_CORRUPT_DATA
Corrupted compressed data.
Definition zxc_error.h:56
@ ZXC_ERROR_NULL_INPUT
Required input pointer is NULL.
Definition zxc_error.h:62
@ ZXC_ERROR_IO
Read/write/seek failure on file.
Definition zxc_error.h:61
@ ZXC_ERROR_DICT_MISMATCH
Provided dictionary ID does not match the file header.
Definition zxc_error.h:70
@ ZXC_ERROR_MEMORY
Memory allocation failure.
Definition zxc_error.h:41
@ ZXC_ERROR_DST_TOO_SMALL
Destination buffer too small.
Definition zxc_error.h:44
@ ZXC_ERROR_SRC_TOO_SMALL
Source buffer too small or truncated input.
Definition zxc_error.h:45
@ ZXC_ERROR_DICT_REQUIRED
File requires a dictionary but none was provided.
Definition zxc_error.h:69
@ ZXC_OK
Success (no error).
Definition zxc_error.h:38
@ ZXC_ERROR_DICT_TOO_LARGE
Dictionary exceeds maximum allowed size.
Definition zxc_error.h:71
#define ZXC_FILE_HEADER_SIZE
File header size: Magic(4) + Version(1) + Chunk(1) + Flags(1) + Reserved(7) + Checksum(2).
#define ZXC_MALLOC(size)
Heap allocator.
Definition zxc_deps.h:68
static uint32_t zxc_seek_group_len(const uint64_t nblocks, const uint64_t g)
Blocks in group g of nblocks: ZXC_SEEK_GROUP, fewer for the last.
#define ZXC_SEEK_GROUP
Blocks per group.
static uint64_t zxc_seek_table_bytes(const uint64_t nblocks)
Byte size of the groups for nblocks blocks, in 64 bits.
static uint64_t zxc_seek_group_count(const uint64_t nblocks)
Groups holding nblocks blocks; the last one may be partial.
#define ZXC_SEEK_SIZE_ENTRY
One block's on-disk size, u32.
#define ZXC_CALLOC(nmemb, size)
Zero-initialised heap allocator.
Definition zxc_deps.h:74
#define ZXC_SEEK_GROUP_BYTES
Full group: anchor + ZXC_SEEK_GROUP sizes.
#define ZXC_FREE(ptr)
Heap deallocator.
Definition zxc_deps.h:86
#define ZXC_PAD_SIZE
Safety padding appended to buffers to tolerate overruns.
#define ZXC_BLOCK_HEADER_SIZE
Block header size: Type(1)+Flags(1)+Reserved(1)+Checksum(1)+CompSize(4).
#define ZXC_REALLOC(ptr, size)
In-place / move heap reallocator.
Definition zxc_deps.h:80
#define ZXC_BLOCK_CHECKSUM_SIZE
Size of the per-block checksum field in bytes.
#define ZXC_SEEK_ANCHOR_SIZE
Group anchor: offset of its first block, u64.
#define ZXC_DECOMPRESS_TAIL_PAD
Tail padding required on the decompression destination buffer.
int64_t zxc_seekable_decompress_range_mt(zxc_seekable *s, void *dst, const size_t dst_capacity, const uint64_t offset, const size_t len, int n_threads)
Multi-threaded variant of zxc_seekable_decompress_range.
void zxc_seekable_free(zxc_seekable *s)
Releases a seekable handle and every resource it owns.
int64_t zxc_seekable_decompress_range(zxc_seekable *s, void *dst, const size_t dst_capacity, const uint64_t offset, const size_t len)
Decompresses the byte range [offset, offset + len) into dst.
uint64_t zxc_seekable_get_num_blocks(const zxc_seekable *s)
Number of blocks in the archive.
uint32_t zxc_seekable_get_block_decomp_size(const zxc_seekable *s, const uint64_t block_idx)
Decompressed byte size of a given block.
uint64_t zxc_seekable_get_decompressed_size(const zxc_seekable *s)
Total decompressed size of the archive.
int zxc_seekable_set_dict(zxc_seekable *s, const void *dict, const size_t dict_size, const void *dict_huf)
Installs the dictionary needed to decode a dict-compressed archive.
uint32_t zxc_seekable_get_block_comp_size(const zxc_seekable *s, const uint64_t block_idx)
Compressed byte size of a given block, from its seek table entry.
int zxc_seekable_set_checksum(zxc_seekable *s, const int enabled)
Turns per-block checksum verification on or off.
zxc_seekable * zxc_seekable_open(const void *src, const size_t src_size)
Opens a seekable archive held entirely in a memory buffer.
zxc_seekable * zxc_seekable_open_reader(const zxc_reader_t *r)
Opens a seekable archive over a caller-supplied random-access reader.
struct zxc_seekable_s zxc_seekable
Opaque handle for a seekable ZXC archive.
#define ZXC_MAX_THREADS
Maximum n_threads for zxc_stream_compress / zxc_stream_decompress.
On-disk header structure for a ZXC block (8 bytes, little-endian).
uint32_t comp_size
Compressed size excluding this header.
Compression / decompression context.
uint8_t * work_buf
Padded scratch buffer for buffer-API decompression.
size_t chunk_size
Effective block size in bytes.
uint8_t * dict_buffer
[dict | data] concat scratch carved from memory_block when dict_size > 0 (NULL otherwise).
size_t dict_size
Dictionary prefill size (0 = no dictionary).
int checksum_enabled
1 if checksum calculation/verification is enabled.
What a frame's header and footer declare, read without decoding.
Definition zxc_buffer.h:235
uint8_t has_checksum
1 when blocks carry checksums and the footer a digest.
Definition zxc_buffer.h:242
size_t block_size
Block size, 4 KB to 2 MB.
Definition zxc_buffer.h:239
uint64_t decompressed_size
Source bytes the frame decodes to.
Definition zxc_buffer.h:236
uint32_t dict_id
Dictionary the frame needs; 0 for none.
Definition zxc_buffer.h:240
uint8_t has_seek_table
1 when a seek table precedes the footer.
Definition zxc_buffer.h:243
Storage-agnostic reader interface for seekable archives.
uint64_t size
Total size of the compressed archive in bytes.
int64_t(* read_at)(void *ctx, void *dst, size_t len, uint64_t offset)
Reads exactly len bytes at offset into dst.
Where a container scan reads from: memory, or a positioned callback (the FILE* size query).
int zxc_read_block_header(const uint8_t *src, const size_t src_size, zxc_block_header_t *bh)
Parses and validates a block header from src.
Definition zxc_common.c:703
void zxc_cctx_free(zxc_cctx_t *ctx)
Releases all resources owned by a compression context.
Definition zxc_common.c:400
int zxc_scan_frame(const zxc_scan_src_t *src, const uint64_t end, uint64_t *start, zxc_frame_info_t *info, size_t *footer_len)
Measures the frame ending at end without decoding: its footer's compressed size gives where it starts...
Definition zxc_common.c:569
int zxc_check_eof_header(const uint8_t *hdr)
Checks an EOF block header against its one valid form: type 255, every other field zero,...
Definition zxc_common.c:748
int zxc_cctx_init(zxc_cctx_t *ctx, const size_t chunk_size, const int mode, const int level, const int checksum_enabled, const size_t dict_size)
Initialises a compression / decompression context, allocating the persistent buffer with ZXC_ALIGNED_...
Definition zxc_common.c:339
int zxc_cctx_attach_dict_huf(zxc_cctx_t *ctx, const uint8_t *lengths)
Attach the shared dictionary literal Huffman table to a context.
Definition zxc_common.c:453
int zxc_check_seek_header(const uint8_t *hdr, const size_t avail, const uint64_t total_out, const size_t block_size, uint64_t *sek_bytes)
Checks the SEK block header the file header announced, on the avail bytes present.
Definition zxc_common.c:757
int zxc_decompress_chunk_wrapper(const zxc_cctx_t *ctx, const uint8_t *src, const size_t src_sz, uint8_t *dst, const size_t dst_cap, const uint64_t block_index)
Public no-dict chunk decoder (decompression hot path).
Internal definitions, constants, SIMD helpers, and utility functions.
static uint64_t zxc_le64(const void *p)
Reads a little-endian 64-bit value from a possibly unaligned address.
static int zxc_ctz32(const uint32_t x)
Counts trailing zeros, returning 32 for a zero input.
static uint32_t zxc_le32(const void *p)
Reads a little-endian 32-bit value from a possibly unaligned address.
static uint64_t zxc_seek_block_count(const uint64_t total_decomp, const size_t block_size)
Blocks in total_decomp bytes of block_size: the seek table's entry count, which the SEK header's fiel...
static const zxc_seek_frame_t * zxc_seek_frame_of_block(const zxc_seekable *s, const uint64_t idx)
The frame holding archive-wide block idx, below the block count.
static uint32_t zxc_seek_decomp_size(const uint32_t block_size, const uint64_t total_decomp, const uint64_t idx)
Decompressed size of block idx (O(1)).
static int zxc_seek_load_spans(const zxc_seekable *s, const zxc_seek_frame_t *f, const uint64_t first, const uint32_t n, uint64_t *starts, uint32_t *sizes, uint8_t *raw)
Loads where each block of [first, first + n) starts and its on-disk size, both taken from the block's...
static void * zxc_seek_mt_worker(void *arg)
Worker thread entry point for multi-threaded seekable decompression.
void zxc_seekable_attach_owned_ctx(zxc_seekable *s, void *ctx)
Transfers ownership of a heap reader context to the handle.
static zxc_seek_source_t zxc_seek_source_of(const zxc_seekable *s)
The handle's archive as a read source, for the per-block readers.
static int zxc_seek_scan_read(const zxc_scan_src_t *scan, const uint64_t off, void *dst, const size_t len)
zxc_scan_src_t over a zxc_seek_source_t.
static int zxc_seek_read_block(const zxc_seekable *s, const uint64_t off, const uint32_t csz, const int has_cs, uint8_t *buf, const size_t buf_cap)
Reads a compressed block into buf from the memory buffer or reader.
#define ZXC_SEEK_TAIL_HDRS
EOF and SEK block headers, ahead of a frame's table: checked when it is read.
static int zxc_seek_parse_frame(zxc_seek_source_t *src, const uint64_t end, zxc_seek_frame_t *f)
Validates the seekable frame that ends at end, in two reads: header and footer as zxc_get_last_frame_...
static void zxc_seek_mt_fail_stripe(const zxc_seek_mt_shared_t *sh, const uint32_t first, const int code)
Marks every job of stripe first with code (setup-failure path).
static zxc_seekable * zxc_seekable_parse(const zxc_seek_source_t *source)
Walks the frames back from the end through zxc_seek_parse_frame, then numbers their bytes and blocks.
static uint64_t zxc_seek_block_of_offset(const zxc_seekable *s, const uint64_t offset)
Archive-wide index of the block holding decompressed offset, below the size.
#define ZXC_SEEK_MAX_FRAMES
Most frames a handle indexes: bounds the reads and memory of an open.
static size_t zxc_seek_spans_raw_max(const uint64_t first, const uint32_t n)
Scratch bound for zxc_seek_load_spans(): the groups blocks [first, first + n) touch.
static int zxc_seek_source_read(const zxc_seek_source_t *src, void *dst, const size_t len, const uint64_t off)
Reads a byte range from the archive, whatever backs it: the one bounds check every parsed offset goes...
static int zxc_seek_use_dict(const zxc_seekable *s, zxc_cctx_t *ctx, const zxc_seek_dict_t **held, const zxc_seek_frame_t *f)
Gives ctx frame f's dictionary, copied in unless *held, the one it holds, is already that one.
POSIX-shaped threading layer shared by the stream engine (zxc_driver.c) and the seekable reader (zxc_...
static int zxc_num_procs(void)
Returns the number of online logical processors.