ZXC 0.15.0
High-performance asymmetric lossless compression library
Loading...
Searching...
No Matches
zxc_driver.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
30#include <stddef.h>
31#include <stdint.h>
32#include <stdio.h>
33
34#include "../../include/zxc_buffer.h"
35#include "../../include/zxc_dict.h"
36#include "../../include/zxc_error.h"
37#include "../../include/zxc_seekable.h"
38#include "../../include/zxc_stream.h"
39#include "zxc_internal.h"
40#include "zxc_threads.h"
41
42// ============================================================================
43// PLATFORM SHIMS
44// ============================================================================
45// Threading comes from zxc_threads.h (POSIX names everywhere, Win32 underneath);
46// only the file-positioning names are remapped here, next to their users.
47#if defined(_WIN32)
48#include <io.h> /* _get_osfhandle, _fileno */
49#include <malloc.h>
50#include <sys/types.h>
51#include <windows.h>
52
53// Map POSIX file positioning functions to Windows equivalents
54#define fseeko _fseeki64
55#define ftello _ftelli64
56#else
57#include <errno.h>
58#include <sys/stat.h>
59#endif
60
61// Below this block size the per-block system calls cost more than they save.
62#define ZXC_STREAM_PREAD_MIN_BLOCK ((size_t)64 * 1024)
63
64// Output per regular-file job: the thread handoff is paid once per batch. Not for
65// a pipe, whose output would wait for a full batch.
66#define ZXC_STREAM_BATCH_BYTES ((size_t)256 * 1024)
67
68// Jobs per worker when they carry several blocks each.
69#define ZXC_STREAM_RING_BATCHED 2U
70
71// ============================================================================
72// STREAMING ENGINE (Producer / Worker / Consumer)
73// ============================================================================
74// Implements a Ring Buffer architecture to parallelize block processing.
75
92
127typedef struct {
128 uint8_t* in_buf;
129 size_t in_cap;
130 size_t in_sz;
131 uint8_t* out_buf;
132 size_t out_cap;
133 size_t result_sz;
134 uint64_t block_index;
135 uint64_t in_off;
136 int job_id;
137 ZXC_ATOMIC job_status_t status; // Atomic for lock-free status updates
138 char pad[ZXC_CACHE_LINE_SIZE]; // Prevent False Sharing
139} zxc_stream_job_t;
140
230typedef struct {
231 zxc_stream_job_t* jobs;
232 size_t ring_size;
233 int* worker_queue;
234 int wq_head;
235 int wq_tail;
236 int wq_count;
237 pthread_mutex_t lock;
238 pthread_cond_t cond_reader;
239 pthread_cond_t cond_worker;
240 pthread_cond_t cond_writer;
241 int shutdown_workers;
242 int compression_mode;
243 ZXC_ATOMIC int io_error;
244 int fail_code;
245 int write_idx;
246 int compression_level;
247 size_t chunk_size;
248 int checksum_enabled;
249 int file_has_checksum;
250 int file_has_seek;
251 uint64_t frame_in;
252 zxc_progress_callback_t progress_cb;
253 void* progress_user_data;
254 uint64_t total_input_bytes;
255 const uint8_t* dict;
256 size_t dict_size;
257 const uint8_t* dict_huf;
259 int in_fd;
260 uint64_t in_pos;
261 size_t in_alloc;
262 int batch;
263 uint8_t ahead[ZXC_BLOCK_HEADER_SIZE];
264 int ahead_len;
265 uint64_t src_total;
266 uint64_t digest;
267} zxc_stream_ctx_t;
268
270static ZXC_ALWAYS_INLINE int zxc_ring_next(const zxc_stream_ctx_t* ctx, const int idx) {
271 return (int)(((size_t)idx + 1) % ctx->ring_size);
272}
273
304typedef struct {
305 zxc_stream_ctx_t* ctx;
306 FILE* f;
307 int64_t total_bytes;
308 uint64_t digest; // archive digest (compress side)
309 uint64_t bytes_processed; // For progress callback
310 uint32_t* seek_comp;
311 uint64_t seek_count;
312 uint64_t seek_cap;
313} writer_args_t;
314
319typedef struct {
320 zxc_stream_ctx_t ctx;
321 writer_args_t w;
322 pthread_t* workers;
323 int n_workers;
324 pthread_t writer;
325 uint8_t* mem_block;
326 int read_idx; // Next ring slot the reader fills
327} zxc_stream_engine_t;
328
329#if !defined(_WIN32)
336static int64_t zxc_pread_full(const int fd, void* dst, const size_t len, const uint64_t offset) {
337 if (sizeof(off_t) < sizeof(uint64_t) && (offset > INT32_MAX || len > INT32_MAX - offset))
338 return -1; // LCOV_EXCL_LINE
339 size_t done = 0;
340 while (done < len) {
341 const ssize_t r = pread(fd, (uint8_t*)dst + done, len - done, (off_t)(offset + done));
342 if (r > 0)
343 done += (size_t)r;
344 else if (r == 0)
345 break;
346 else if (errno != EINTR)
347 return -1; // LCOV_EXCL_LINE
348 }
349 return (int64_t)done;
350}
351#endif
352
354static int zxc_stream_regular_fd(FILE* f) {
355#if defined(_WIN32)
356 // LCOV_EXCL_START - Windows path, not reachable on POSIX CI
357 const int fd = _fileno(f);
358 if (fd < 0) return -1;
359 const intptr_t handle = _get_osfhandle(fd);
360 return (handle != -1 && GetFileType((HANDLE)handle) == FILE_TYPE_DISK) ? fd : -1;
361 // LCOV_EXCL_STOP
362#else
363 struct stat st;
364 const int fd = fileno(f);
365 return (fd >= 0 && fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) ? fd : -1;
366#endif
367}
368
370static void zxc_stream_open_positioned(zxc_stream_ctx_t* ctx, FILE* f_in, const int fd) {
371#if defined(_WIN32)
372 (void)ctx;
373 (void)f_in;
374 (void)fd;
375#else
376 if (sizeof(off_t) < sizeof(uint64_t)) return; // LCOV_EXCL_LINE
377 const long long pos = ftello(f_in);
378 if (UNLIKELY(pos < 0)) return; // LCOV_EXCL_LINE
379 ctx->in_fd = fd;
380 ctx->in_pos = (uint64_t)pos;
381 ctx->ahead_len = -1;
382#endif
383}
384
390static int64_t zxc_stream_read_at(const zxc_stream_ctx_t* ctx, void* dst, const size_t len,
391 const uint64_t offset) {
392#if defined(_WIN32)
393 (void)ctx;
394 (void)dst;
395 (void)len;
396 (void)offset;
397 return ZXC_ERROR_IO;
398#else
399 const int64_t r = zxc_pread_full(ctx->in_fd, dst, len, offset);
400 return r < 0 ? ZXC_ERROR_IO : r;
401#endif
402}
403
414static int64_t zxc_stream_in_read(zxc_stream_ctx_t* ctx, FILE* f, void* dst, const size_t len) {
415 if (ctx && ctx->in_fd >= 0) {
416 const int64_t r = zxc_stream_read_at(ctx, dst, len, ctx->in_pos);
417 if (LIKELY(r > 0)) ctx->in_pos += (uint64_t)r;
418 return r;
419 }
420 const size_t got = fread(dst, 1, len, f);
421 // A short read without EOF is a read error. Not ferror(): the caller may have
422 // left a stale error indicator on the stream.
423 if (UNLIKELY(got < len && !feof(f))) return ZXC_ERROR_IO; // LCOV_EXCL_LINE
424 return (int64_t)got;
425}
426
428static void zxc_stream_reader_fail(zxc_stream_ctx_t* ctx, const int code) {
429 pthread_mutex_lock(&ctx->lock);
430 if (code && !ctx->fail_code) ctx->fail_code = code;
431 ctx->io_error = 1;
432 pthread_mutex_unlock(&ctx->lock);
433}
434
441static ZXC_ALWAYS_INLINE int zxc_stream_process(const zxc_stream_ctx_t* ctx, zxc_cctx_t* cctx,
442 const uint8_t* in, const size_t in_sz, uint8_t* out,
443 const size_t out_cap, const uint64_t block_index) {
444 return ctx->compression_mode
445 ? zxc_compress_chunk_wrapper(cctx, in, in_sz, out, out_cap, block_index)
446 : zxc_decompress_chunk_wrapper(cctx, in, in_sz, out, out_cap, block_index);
447}
448
457static int zxc_stream_decode_job(const zxc_stream_ctx_t* ctx, zxc_cctx_t* cctx,
458 const zxc_stream_job_t* job, const uint8_t* in,
459 uint8_t* dict_work) {
460 const size_t checksum_sz = ctx->file_has_checksum ? ZXC_BLOCK_CHECKSUM_SIZE : 0;
461 // One block of output plus the fast decoder's margin.
462 const size_t room = ctx->chunk_size + ZXC_DECOMPRESS_TAIL_PAD;
463 // Dictionary: decode behind the prefix so back-references resolve.
464 uint8_t* const scratch = dict_work ? dict_work + ctx->dict_size : NULL;
465 uint64_t block_index = job->block_index;
466 size_t ip = 0;
467 size_t op = 0;
468
469 while (ip < job->in_sz) {
470 const size_t rem = job->in_sz - ip;
472 if (UNLIKELY(rem < ZXC_BLOCK_HEADER_SIZE ||
475 const uint64_t block_sz = (uint64_t)ZXC_BLOCK_HEADER_SIZE + bh.comp_size + checksum_sz;
476 if (UNLIKELY(block_sz > rem || job->out_cap - op < room)) return ZXC_ERROR_CORRUPT_DATA;
477
478 const int res =
479 zxc_stream_process(ctx, cctx, in + ip, (size_t)block_sz,
480 scratch ? scratch : job->out_buf + op, room, block_index++);
481 if (UNLIKELY(res < 0)) return res;
482 if (scratch) ZXC_MEMCPY(job->out_buf + op, scratch, (size_t)res);
483 ip += (size_t)block_sz;
484 op += (size_t)res;
485 }
486 return (int)op;
487}
488
504static void* zxc_stream_worker(void* arg) {
505 zxc_stream_ctx_t* const ctx = (zxc_stream_ctx_t*)arg;
506 zxc_cctx_t cctx;
507
508 const int unified_chk = (ctx->compression_mode == 1)
509 ? ctx->checksum_enabled
510 : (ctx->file_has_checksum && ctx->checksum_enabled);
511
512 const size_t eff_chunk = (ctx->dict_size > 0 && ctx->compression_mode == 1)
513 ? zxc_block_size_ceil(ctx->dict_size + ctx->chunk_size)
514 : ctx->chunk_size;
515 // Per-worker input buffer for positioned reads: stays in this core's cache.
516 uint8_t* const in_work = ctx->in_fd >= 0 ? (uint8_t*)ZXC_MALLOC(ctx->in_alloc) : NULL;
517 if (UNLIKELY(zxc_cctx_init(&cctx, eff_chunk, ctx->compression_mode, ctx->compression_level,
518 unified_chk, ctx->dict_size) != ZXC_OK ||
519 zxc_cctx_attach_dict_huf(&cctx, ctx->dict_huf) != ZXC_OK ||
520 (ctx->in_fd >= 0 && !in_work))) {
521 // LCOV_EXCL_START
522 ZXC_FREE(in_work);
523 zxc_cctx_free(&cctx);
524 pthread_mutex_lock(&ctx->lock);
525 ctx->io_error = 1;
526 pthread_cond_broadcast(&ctx->cond_writer);
527 pthread_cond_broadcast(&ctx->cond_reader);
528 pthread_mutex_unlock(&ctx->lock);
529 return NULL;
530 // LCOV_EXCL_STOP
531 }
532
533 cctx.compression_level = ctx->compression_level;
534
535 // Per-worker dict buffer for assembling [dict | block_data]
536 const size_t dsz = ctx->dict_size;
537 uint8_t* const dict_work = cctx.dict_buffer;
538 if (dict_work) ZXC_MEMCPY(dict_work, ctx->dict, dsz);
539
540 while (1) {
541 zxc_stream_job_t* job = NULL;
542 pthread_mutex_lock(&ctx->lock);
543 while (ctx->wq_count == 0 && !ctx->shutdown_workers) {
544 pthread_cond_wait(&ctx->cond_worker, &ctx->lock);
545 }
546 if (ctx->shutdown_workers && ctx->wq_count == 0) {
547 pthread_mutex_unlock(&ctx->lock);
548 break;
549 }
550 const int jid = ctx->worker_queue[ctx->wq_tail];
551 ctx->wq_tail = zxc_ring_next(ctx, ctx->wq_tail);
552 ctx->wq_count--;
553 job = &ctx->jobs[jid];
554 pthread_mutex_unlock(&ctx->lock);
555
556 // A short read (the file shrank) is an I/O error.
557 const uint8_t* const in = in_work ? in_work : job->in_buf;
558 int res;
559 if (in_work && UNLIKELY(zxc_stream_read_at(ctx, in_work, job->in_sz, job->in_off) !=
560 (int64_t)job->in_sz)) {
561 res = ZXC_ERROR_IO;
562 } else if (ctx->compression_mode == 0) {
563 res = zxc_stream_decode_job(ctx, &cctx, job, in, dict_work);
564 } else if (dict_work) {
565 ZXC_MEMCPY(dict_work + dsz, in, job->in_sz);
566 res = zxc_stream_process(ctx, &cctx, dict_work, dsz + job->in_sz, job->out_buf,
567 job->out_cap, job->block_index);
568 } else {
569 res = zxc_stream_process(ctx, &cctx, in, job->in_sz, job->out_buf, job->out_cap,
570 job->block_index);
571 }
572
573 pthread_mutex_lock(&ctx->lock);
574 job->result_sz = UNLIKELY(res < 0) ? 0 : (size_t)res;
575 job->status = JOB_STATUS_PROCESSED;
576 if (UNLIKELY(res < 0)) {
577 // Keep the codec's own diagnosis; io_error only stops the others.
578 if (!ctx->fail_code) ctx->fail_code = res;
579 ctx->io_error = 1;
580 pthread_cond_broadcast(&ctx->cond_writer);
581 pthread_cond_broadcast(&ctx->cond_reader);
582 } else if (jid == ctx->write_idx) {
583 pthread_cond_signal(&ctx->cond_writer);
584 }
585 pthread_mutex_unlock(&ctx->lock);
586 }
587 ZXC_FREE(in_work);
588 zxc_cctx_free(&cctx);
589 return NULL;
590}
591
617static void* zxc_async_writer(void* arg) {
618 writer_args_t* const args = (writer_args_t*)arg;
619 zxc_stream_ctx_t* const ctx = args->ctx;
620 while (1) {
621 zxc_stream_job_t* const job = &ctx->jobs[ctx->write_idx];
622 pthread_mutex_lock(&ctx->lock);
623 while (job->status != JOB_STATUS_PROCESSED && !ctx->io_error)
624 pthread_cond_wait(&ctx->cond_writer, &ctx->lock);
625 // Woken by an error: the job may still be decoding, so leave it alone.
626 if (UNLIKELY(job->status != JOB_STATUS_PROCESSED)) {
627 pthread_mutex_unlock(&ctx->lock);
628 break;
629 }
630
631 const size_t result_sz = job->result_sz;
632 const size_t in_sz = job->in_sz;
633 pthread_mutex_unlock(&ctx->lock);
634
635 if (result_sz == (size_t)-1) break;
636
637 if (args->f && result_sz > 0) {
638 if (fwrite(job->out_buf, 1, result_sz, args->f) != result_sz) {
639 pthread_mutex_lock(&ctx->lock);
640 ctx->io_error = 1;
641 pthread_cond_signal(&ctx->cond_reader);
642 pthread_mutex_unlock(&ctx->lock);
643 }
644 }
645 if (UNLIKELY(ctx->io_error)) {
646 pthread_mutex_lock(&ctx->lock);
647 job->status = JOB_STATUS_FREE;
648 pthread_cond_signal(&ctx->cond_reader);
649 pthread_mutex_unlock(&ctx->lock);
650 break;
651 }
652 args->total_bytes += (int64_t)result_sz;
653 if (ctx->checksum_enabled && ctx->compression_mode == 1 &&
654 LIKELY(result_sz >= ZXC_BLOCK_CHECKSUM_SIZE))
655 args->digest = zxc_digest_combine(
656 args->digest, zxc_le32(job->out_buf + result_sz - ZXC_BLOCK_CHECKSUM_SIZE));
657
658 // Seekable: record compressed block size
659 if (args->seek_comp && ctx->compression_mode == 1) {
660 if (UNLIKELY(args->seek_count >= args->seek_cap)) {
661 const uint64_t max_cap = SIZE_MAX / sizeof(uint32_t);
662 uint32_t* nc = NULL;
663 if (LIKELY(args->seek_cap < max_cap)) {
664 args->seek_cap = args->seek_cap < max_cap / 2 ? args->seek_cap * 2 : max_cap;
665 nc = (uint32_t*)ZXC_REALLOC(args->seek_comp,
666 (size_t)args->seek_cap * sizeof(uint32_t));
667 }
668 // LCOV_EXCL_START
669 if (UNLIKELY(!nc)) {
670 pthread_mutex_lock(&ctx->lock);
671 if (!ctx->fail_code) ctx->fail_code = ZXC_ERROR_MEMORY;
672 ctx->io_error = 1;
673 job->status = JOB_STATUS_FREE;
674 pthread_cond_signal(&ctx->cond_reader);
675 pthread_mutex_unlock(&ctx->lock);
676 break;
677 }
678 // LCOV_EXCL_STOP
679 args->seek_comp = nc;
680 }
681 args->seek_comp[args->seek_count++] = (uint32_t)result_sz;
682 }
683
684 if (ctx->progress_cb) {
685 // LCOV_EXCL_START
686 args->bytes_processed += ctx->compression_mode == 1 ? in_sz : result_sz;
687 ctx->progress_cb(args->bytes_processed, ctx->total_input_bytes,
688 ctx->progress_user_data);
689 // LCOV_EXCL_STOP
690 }
691
692 pthread_mutex_lock(&ctx->lock);
693 job->status = JOB_STATUS_FREE;
694 ctx->write_idx = zxc_ring_next(ctx, ctx->write_idx);
695 pthread_cond_signal(&ctx->cond_reader);
696 pthread_mutex_unlock(&ctx->lock);
697 }
698 return NULL;
699}
700
707static void zxc_stream_engine_stop(zxc_stream_engine_t* e, const int writer_started) {
708 zxc_stream_ctx_t* const ctx = &e->ctx;
709 if (writer_started) {
710 zxc_stream_job_t* const end_job = &ctx->jobs[e->read_idx];
711 pthread_mutex_lock(&ctx->lock);
712 while (end_job->status != JOB_STATUS_FREE && !ctx->io_error)
713 pthread_cond_wait(&ctx->cond_reader, &ctx->lock);
714 end_job->result_sz = (size_t)-1;
715 end_job->status = JOB_STATUS_PROCESSED;
716 pthread_cond_broadcast(&ctx->cond_writer);
717 pthread_mutex_unlock(&ctx->lock);
718 pthread_join(e->writer, NULL);
719 }
720 pthread_mutex_lock(&ctx->lock);
721 ctx->shutdown_workers = 1;
722 pthread_cond_broadcast(&ctx->cond_worker);
723 pthread_mutex_unlock(&ctx->lock);
724 for (int i = 0; i < e->n_workers; i++) pthread_join(e->workers[i], NULL);
725
726 pthread_cond_destroy(&ctx->cond_writer);
727 pthread_cond_destroy(&ctx->cond_worker);
728 pthread_cond_destroy(&ctx->cond_reader);
729 pthread_mutex_destroy(&ctx->lock);
730 ZXC_FREE(e->workers);
731 ZXC_ALIGNED_FREE(e->mem_block);
732}
733
764static int zxc_stream_engine_start(zxc_stream_engine_t* e, FILE* f_in, const int n_threads) {
765 zxc_stream_ctx_t* const ctx = &e->ctx;
766 const int mode = ctx->compression_mode;
767 const size_t chunk = ctx->chunk_size;
768
769 int num_threads = (n_threads > 0) ? n_threads : zxc_num_procs();
770 if (num_threads > ZXC_MAX_THREADS) num_threads = ZXC_MAX_THREADS;
771 // Positioned reads need two threads: a lone worker is faster fed by the reader.
772 ctx->in_fd = -1;
773 const int in_fd = (mode == 0) ? zxc_stream_regular_fd(f_in) : -1;
774 ctx->batch =
775 (in_fd >= 0 && chunk < ZXC_STREAM_BATCH_BYTES) ? (int)(ZXC_STREAM_BATCH_BYTES / chunk) : 1;
776 if (in_fd >= 0 && num_threads > 1 && chunk >= ZXC_STREAM_PREAD_MIN_BLOCK)
777 zxc_stream_open_positioned(ctx, f_in, in_fd);
778 // One thread for the reader and writer, none with positioned reads.
779 const int num_workers = ctx->in_fd >= 0 ? num_threads : (num_threads > 1) ? num_threads - 1 : 1;
780 // Batched jobs hold several blocks: fewer keep the workers fed.
781 ctx->ring_size = (size_t)num_workers * (ctx->batch > 1 ? ZXC_STREAM_RING_BATCHED : 4U);
782
783 const uint64_t max_out = zxc_compress_bound(chunk);
784 const size_t raw_alloc_in =
785 (size_t)((mode ? chunk : max_out * (uint64_t)ctx->batch) + ZXC_PAD_SIZE);
786 const size_t alloc_in = (raw_alloc_in + ZXC_ALIGNMENT_MASK) & ~ZXC_ALIGNMENT_MASK;
787 ctx->in_alloc = alloc_in;
788
789 const size_t raw_alloc_out =
790 (size_t)((mode ? max_out : chunk * (size_t)ctx->batch + ZXC_DECOMPRESS_TAIL_PAD) +
792 const size_t alloc_out = (raw_alloc_out + ZXC_ALIGNMENT_MASK) & ~ZXC_ALIGNMENT_MASK;
793
794 // Positioned reads use per-worker input buffers.
795 const size_t ring_in = ctx->in_fd >= 0 ? 0 : alloc_in;
796 const size_t per_job_sz = sizeof(zxc_stream_job_t) + sizeof(int) + ring_in + alloc_out;
797 if (UNLIKELY(per_job_sz > SIZE_MAX / ctx->ring_size))
798 return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
799
800 uint8_t* const mem_block = ZXC_ALIGNED_MALLOC(ctx->ring_size * per_job_sz, ZXC_CACHE_LINE_SIZE);
801 if (UNLIKELY(!mem_block)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
802 e->mem_block = mem_block;
803
804 uint8_t* ptr = mem_block;
805 ctx->jobs = (zxc_stream_job_t*)ptr;
806 ptr += ctx->ring_size * sizeof(zxc_stream_job_t);
807 ctx->worker_queue = (int*)ptr;
808 ptr += ctx->ring_size * sizeof(int);
809 uint8_t* buf_in = ptr;
810 ptr += ctx->ring_size * ring_in;
811 uint8_t* buf_out = ptr;
812
813 ZXC_MEMSET(mem_block, 0, ctx->ring_size * (sizeof(zxc_stream_job_t) + sizeof(int)));
814
815 for (size_t i = 0; i < ctx->ring_size; i++) {
816 ctx->jobs[i].job_id = (int)i;
817 ctx->jobs[i].status = JOB_STATUS_FREE;
818 ctx->jobs[i].in_buf = ring_in ? buf_in + (i * ring_in) : NULL;
819 ctx->jobs[i].in_cap = alloc_in - ZXC_PAD_SIZE;
820 ctx->jobs[i].in_sz = 0;
821 ctx->jobs[i].out_buf = buf_out + (i * alloc_out);
822 ctx->jobs[i].out_cap = alloc_out - ZXC_PAD_SIZE;
823 ctx->jobs[i].result_sz = 0;
824 }
825
826 pthread_mutex_init(&ctx->lock, NULL);
827 pthread_cond_init(&ctx->cond_reader, NULL);
828 pthread_cond_init(&ctx->cond_worker, NULL);
829 pthread_cond_init(&ctx->cond_writer, NULL);
830
831 e->workers = ZXC_MALLOC((size_t)num_workers * sizeof(pthread_t));
832 if (LIKELY(e->workers)) {
833 while (e->n_workers < num_workers &&
834 pthread_create(&e->workers[e->n_workers], NULL, zxc_stream_worker, ctx) == 0)
835 e->n_workers++;
836 }
837 e->w.ctx = ctx;
838 if (UNLIKELY(e->n_workers == 0 ||
839 pthread_create(&e->writer, NULL, zxc_async_writer, &e->w) != 0)) {
840 zxc_stream_engine_stop(e, 0); // LCOV_EXCL_LINE
841 return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
842 }
843 return ZXC_OK;
844}
845
847static int zxc_stream_engine_close(zxc_stream_engine_t* e, FILE* f_in) {
849 if (e->ctx.in_fd < 0) return ZXC_OK;
850 e->ctx.in_fd = -1;
851 return fseeko(f_in, (long long)e->ctx.in_pos, SEEK_SET) == 0 ? ZXC_OK : ZXC_ERROR_IO;
852}
853
855static void zxc_stream_engine_drain(zxc_stream_engine_t* e) {
856 zxc_stream_ctx_t* const ctx = &e->ctx;
857 // The writer frees jobs in order: the last one queued is free last.
858 const zxc_stream_job_t* const last =
859 &ctx->jobs[((size_t)e->read_idx + ctx->ring_size - 1) % ctx->ring_size];
860 pthread_mutex_lock(&ctx->lock);
861 while (last->status != JOB_STATUS_FREE && !ctx->io_error)
862 pthread_cond_wait(&ctx->cond_reader, &ctx->lock);
863 pthread_mutex_unlock(&ctx->lock);
864}
865
873static size_t zxc_stream_read_blocks(zxc_stream_ctx_t* ctx, FILE* f_in, zxc_stream_job_t* job,
874 int* n_blocks, int* eof) {
875 const int positioned = ctx->in_fd >= 0;
876 const size_t checksum_sz = ctx->file_has_checksum ? ZXC_BLOCK_CHECKSUM_SIZE : 0;
877 // Folded only when finish_decompress will compare it: like the block
878 // checksums, the file flag and the caller's switch both.
879 const int fold = ctx->file_has_checksum && ctx->checksum_enabled;
880 size_t fill = 0;
881 int code = 0; // 0: a read error
882
883 for (*n_blocks = 0; *n_blocks < ctx->batch; (*n_blocks)++) {
884 uint8_t bh_buf[ZXC_BLOCK_HEADER_SIZE];
885 const uint64_t block_off = ctx->in_pos;
886 int64_t h_read;
887 if (positioned && ctx->ahead_len >= 0) {
888 h_read = ctx->ahead_len;
889 ZXC_MEMCPY(bh_buf, ctx->ahead, (size_t)h_read);
890 ctx->ahead_len = -1;
891 } else if (positioned) {
892 h_read = zxc_stream_read_at(ctx, bh_buf, sizeof(bh_buf), ctx->in_pos);
893 } else {
894 h_read = zxc_stream_in_read(NULL, f_in, bh_buf, sizeof(bh_buf));
895 }
896 if (UNLIKELY(h_read < 0)) goto _failed; // LCOV_EXCL_LINE
897 if (positioned) ctx->in_pos += (uint64_t)h_read;
898 if (UNLIKELY(h_read < ZXC_BLOCK_HEADER_SIZE)) {
899 *eof = 1;
900 return fill;
901 }
902
904 if (UNLIKELY(zxc_read_block_header(bh_buf, ZXC_BLOCK_HEADER_SIZE, &bh) != ZXC_OK)) {
905 code = ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
906 goto _failed; // LCOV_EXCL_LINE
907 }
908
909 if (bh.block_type == ZXC_BLOCK_EOF) {
910 if (UNLIKELY(zxc_check_eof_header(bh_buf) != ZXC_OK)) {
912 goto _failed;
913 }
914 ctx->frame_in += ZXC_BLOCK_HEADER_SIZE;
915 *eof = 1;
916 return fill;
917 }
918
919 if (UNLIKELY((uint64_t)bh.comp_size > (uint64_t)ctx->chunk_size)) {
920 code = ZXC_ERROR_BAD_BLOCK_SIZE; // LCOV_EXCL_LINE
921 goto _failed; // LCOV_EXCL_LINE
922 }
923
924 const size_t body_total = (size_t)bh.comp_size + checksum_sz;
925 const uint64_t total_len = (uint64_t)body_total + ZXC_BLOCK_HEADER_SIZE;
926 if (UNLIKELY(total_len > (uint64_t)(job->in_cap - fill))) goto _failed;
927
929 const uint8_t* stored = tail;
930 int got = 1;
931
932 if (positioned) {
933 // The worker reads the bodies; the digest needs only the checksum,
934 // read with the next header.
935 if (fill == 0) job->in_off = block_off;
936 ctx->in_pos += body_total;
937 if (fold) {
938 const int64_t r = zxc_stream_read_at(ctx, tail, sizeof(tail),
939 ctx->in_pos - ZXC_BLOCK_CHECKSUM_SIZE);
940 got = r >= ZXC_BLOCK_CHECKSUM_SIZE;
941 if (LIKELY(got)) {
942 ctx->ahead_len = (int)(r - ZXC_BLOCK_CHECKSUM_SIZE);
943 ZXC_MEMCPY(ctx->ahead, tail + ZXC_BLOCK_CHECKSUM_SIZE, (size_t)ctx->ahead_len);
944 }
945 }
946 } else {
947 uint8_t* const block = job->in_buf + fill;
948 ZXC_MEMCPY(block, bh_buf, ZXC_BLOCK_HEADER_SIZE);
949 stored = block + ZXC_BLOCK_HEADER_SIZE + bh.comp_size;
950
951 // Single fread for body + checksum (reduces syscalls)
952 got = fread(block + ZXC_BLOCK_HEADER_SIZE, 1, body_total, f_in) == body_total;
953 }
954
955 if (UNLIKELY(!got)) goto _failed;
956 if (fold) ctx->digest = zxc_digest_combine(ctx->digest, zxc_le32(stored));
957 fill += (size_t)total_len;
958 ctx->frame_in += total_len;
959 }
960 return fill;
961
962_failed:
963 zxc_stream_reader_fail(ctx, code);
964 *eof = 1;
965 return 0;
966}
967
969static size_t zxc_stream_read_chunk(zxc_stream_ctx_t* ctx, FILE* f_in, zxc_stream_job_t* job,
970 int* eof) {
971 const size_t got = fread(job->in_buf, 1, ctx->chunk_size, f_in);
972 ctx->src_total += got;
973 if (got < ctx->chunk_size) *eof = 1;
974 return got;
975}
976
982static int zxc_stream_read_loop(zxc_stream_ctx_t* ctx, FILE* f_in, int read_idx) {
983 int read_eof = 0;
984 uint64_t block_index = 0;
985
986 while (!read_eof && !ctx->io_error) {
987 zxc_stream_job_t* const job = &ctx->jobs[read_idx];
988 pthread_mutex_lock(&ctx->lock);
989 while (job->status != JOB_STATUS_FREE && !ctx->io_error)
990 pthread_cond_wait(&ctx->cond_reader, &ctx->lock);
991 pthread_mutex_unlock(&ctx->lock);
992
993 if (UNLIKELY(ctx->io_error)) break;
994
995 int n_blocks = 1;
996 const size_t read_sz = ctx->compression_mode
997 ? zxc_stream_read_chunk(ctx, f_in, job, &read_eof)
998 : zxc_stream_read_blocks(ctx, f_in, job, &n_blocks, &read_eof);
999 if (UNLIKELY(read_eof && read_sz == 0)) break;
1000
1001 job->in_sz = read_sz;
1002 job->block_index = block_index;
1003 block_index += (uint64_t)n_blocks;
1004 pthread_mutex_lock(&ctx->lock);
1005 job->status = JOB_STATUS_FILLED;
1006 ctx->worker_queue[ctx->wq_head] = read_idx;
1007 ctx->wq_head = zxc_ring_next(ctx, ctx->wq_head);
1008 ctx->wq_count++;
1009 read_idx = zxc_ring_next(ctx, read_idx);
1010 pthread_cond_signal(&ctx->cond_worker);
1011 pthread_mutex_unlock(&ctx->lock);
1012 }
1013 return read_idx;
1014}
1015
1021static void zxc_stream_finish_compress(zxc_stream_ctx_t* ctx, writer_args_t* w, FILE* f_out) {
1022 // EOF block
1023 uint8_t eof_buf[ZXC_BLOCK_HEADER_SIZE];
1024 const zxc_block_header_t eof_bh = {
1025 .block_type = ZXC_BLOCK_EOF, .block_flags = 0, .reserved = 0, .comp_size = 0};
1027 if (UNLIKELY(f_out &&
1028 fwrite(eof_buf, 1, ZXC_BLOCK_HEADER_SIZE, f_out) != ZXC_BLOCK_HEADER_SIZE))
1029 ctx->io_error = 1; // LCOV_EXCL_LINE
1030 else
1031 w->total_bytes += ZXC_BLOCK_HEADER_SIZE;
1032
1033 // Seekable: write SEK block between EOF and footer, empty when no block was read
1034 if (!ctx->io_error && w->seek_comp) {
1035 // Header, then one group at a time (seek_comp itself stays resident).
1036 uint8_t st_buf[ZXC_SEEK_GROUP_BYTES];
1037 const int h = zxc_seek_table_header(st_buf, sizeof(st_buf), w->seek_count);
1038 if (UNLIKELY(h < 0 || (f_out && fwrite(st_buf, 1, (size_t)h, f_out) != (size_t)h)))
1039 ctx->io_error = 1; // LCOV_EXCL_LINE
1040 else
1041 w->total_bytes += h;
1042
1043 uint64_t anchor = ZXC_FILE_HEADER_SIZE;
1044 for (uint64_t i = 0; i < w->seek_count && !ctx->io_error; i += ZXC_SEEK_GROUP) {
1045 const size_t bytes =
1046 zxc_seek_write_group(st_buf, &anchor, w->seek_comp + i,
1047 zxc_seek_group_len(w->seek_count, i / ZXC_SEEK_GROUP));
1048 if (UNLIKELY(f_out && fwrite(st_buf, 1, bytes, f_out) != bytes))
1049 ctx->io_error = 1; // LCOV_EXCL_LINE
1050 else
1051 w->total_bytes += (int64_t)bytes;
1052 }
1053 }
1054
1055 // Footer (FORMAT.md 8)
1056 uint8_t footer_buf[ZXC_FOOTER_MAX_SIZE_WITH_DIGEST];
1057 const size_t footer_len =
1058 (size_t)zxc_write_file_footer(footer_buf, sizeof(footer_buf), (uint64_t)w->total_bytes,
1059 ctx->src_total, w->digest, ctx->checksum_enabled);
1060 if (UNLIKELY(f_out && fwrite(footer_buf, 1, footer_len, f_out) != footer_len))
1061 ctx->io_error = 1;
1062 else
1063 w->total_bytes += (int64_t)footer_len;
1064}
1065
1069static void zxc_stream_finish_decompress(zxc_stream_ctx_t* ctx, const uint64_t frame_out,
1070 FILE* f_in) {
1071 // After the EOF block: the SEK block when the header announced one, then the
1072 // footer, whose length follows from the bytes read and the bytes produced.
1073
1074 if (ctx->file_has_seek) {
1075 uint8_t sek[ZXC_BLOCK_HEADER_SIZE];
1076 uint64_t remaining = 0;
1077 uint64_t sek_bytes = 0;
1078 // A short read is a truncation, unless the bytes read already disagree.
1079 const int64_t got = zxc_stream_in_read(ctx, f_in, sek, sizeof(sek));
1080 const int src_rc = got < 0 ? ZXC_ERROR_IO
1081 : zxc_check_seek_header(sek, (size_t)got, frame_out,
1082 ctx->chunk_size, &remaining);
1083 if (UNLIKELY(src_rc != ZXC_OK)) {
1084 if (src_rc == ZXC_ERROR_CORRUPT_DATA && !ctx->fail_code) ctx->fail_code = src_rc;
1085 ctx->io_error = 1;
1086 }
1087 sek_bytes = remaining;
1088 // Skip the SEK payload; positioned, a cut table shows at the footer read.
1089 if (ctx->in_fd >= 0) {
1090 ctx->in_pos += remaining;
1091 remaining = 0;
1092 }
1093 uint8_t discard[512];
1094 while (remaining > 0 && !ctx->io_error) {
1095 const size_t chunk = remaining < sizeof(discard) ? (size_t)remaining : sizeof(discard);
1096 if (UNLIKELY(fread(discard, 1, chunk, f_in) != chunk)) ctx->io_error = 1;
1097 remaining -= chunk;
1098 }
1099 ctx->frame_in += ZXC_BLOCK_HEADER_SIZE + sek_bytes;
1100 }
1101 const zxc_footer_layout_t fl =
1102 zxc_footer_layout(ctx->frame_in, frame_out, ctx->file_has_checksum);
1103 uint64_t stored_digest = 0;
1104 if (!ctx->io_error) {
1105 uint8_t footer[ZXC_FOOTER_MAX_SIZE_WITH_DIGEST];
1106 const int64_t got = zxc_stream_in_read(ctx, f_in, footer, fl.len);
1107 const int frc =
1108 got < 0 ? ZXC_ERROR_IO
1109 : zxc_check_file_footer(footer, (size_t)got, &fl, frame_out, &stored_digest);
1110 if (UNLIKELY(frc != ZXC_OK)) {
1111 if (frc == ZXC_ERROR_CORRUPT_DATA && !ctx->fail_code) ctx->fail_code = frc;
1112 ctx->io_error = 1;
1113 }
1114 }
1115 if (!ctx->io_error && ctx->file_has_checksum && ctx->checksum_enabled &&
1116 UNLIKELY(stored_digest != ctx->digest)) {
1117 if (!ctx->fail_code) ctx->fail_code = ZXC_ERROR_BAD_CHECKSUM;
1118 ctx->io_error = 1;
1119 }
1120 // Whatever follows is the next frame's, or an error: see zxc_stream_decompress().
1121}
1122
1135static int zxc_stream_read_exact(zxc_stream_ctx_t* ctx, FILE* f, void* dst, const size_t len,
1136 const int short_code) {
1137 const int64_t got = zxc_stream_in_read(ctx, f, dst, len);
1138 if (LIKELY(got == (int64_t)len)) return ZXC_OK;
1139 if (UNLIKELY(got < 0)) return ZXC_ERROR_IO; // LCOV_EXCL_LINE
1140 return got == 0 ? 1 : short_code;
1141}
1142
1155static int zxc_stream_read_frame_header(zxc_stream_ctx_t* ctx, FILE* f_in, const uint8_t* magic,
1156 const uint32_t dict_id, size_t* chunk, int* has_checksum,
1157 int* has_seek) {
1158 uint8_t h[ZXC_FILE_HEADER_SIZE];
1159 uint32_t header_dict_id = 0;
1160 ZXC_MEMCPY(h, magic, sizeof(uint32_t));
1161 const int rrc =
1162 zxc_stream_read_exact(ctx, f_in, h + sizeof(uint32_t),
1163 ZXC_FILE_HEADER_SIZE - sizeof(uint32_t), ZXC_ERROR_SRC_TOO_SMALL);
1164 if (UNLIKELY(rrc != ZXC_OK)) return rrc == 1 ? ZXC_ERROR_SRC_TOO_SMALL : rrc;
1165
1166 const int hrc = zxc_read_file_header(h, ZXC_FILE_HEADER_SIZE, chunk, has_checksum,
1167 &header_dict_id, has_seek);
1168 if (UNLIKELY(hrc != ZXC_OK)) return hrc;
1169 if (header_dict_id != 0) {
1170 if (UNLIKELY(dict_id == 0)) return ZXC_ERROR_DICT_REQUIRED;
1171 if (UNLIKELY(dict_id != header_dict_id)) return ZXC_ERROR_DICT_MISMATCH;
1172 }
1173 return ZXC_OK;
1174}
1175
1181static int64_t zxc_stream_decode_frame(zxc_stream_engine_t* e, FILE* f_in, const int has_seek) {
1182 zxc_stream_ctx_t* const ctx = &e->ctx;
1183 const int64_t base = e->w.total_bytes;
1184 ctx->file_has_seek = has_seek;
1185 ctx->frame_in = ZXC_FILE_HEADER_SIZE;
1186 ctx->digest = 0;
1187 ctx->ahead_len = -1;
1188
1189 e->read_idx = zxc_stream_read_loop(ctx, f_in, e->read_idx);
1191 if (!ctx->io_error)
1192 zxc_stream_finish_decompress(ctx, (uint64_t)(e->w.total_bytes - base), f_in);
1193
1194 // fail_code holds the cause, else a read/write failed; a worker may still set it.
1195 if (UNLIKELY(ctx->io_error)) {
1196 pthread_mutex_lock(&ctx->lock);
1197 const int code = ctx->fail_code;
1198 pthread_mutex_unlock(&ctx->lock);
1199 return code ? code : ZXC_ERROR_IO;
1200 }
1201 return e->w.total_bytes - base;
1202}
1203
1205static int zxc_file_span(FILE* f, long long* saved, uint64_t* size) {
1206 *saved = ftello(f);
1207 if (UNLIKELY(*saved < 0 || fseeko(f, 0, SEEK_END) != 0)) return ZXC_ERROR_IO;
1208 const long long n = ftello(f);
1209 if (UNLIKELY(n < 0)) {
1210 // LCOV_EXCL_START
1211 fseeko(f, *saved, SEEK_SET);
1212 return ZXC_ERROR_IO;
1213 // LCOV_EXCL_STOP
1214 }
1215 *size = (uint64_t)n;
1216 return ZXC_OK;
1217}
1218
1226int64_t zxc_stream_compress(FILE* f_in, FILE* f_out, const zxc_compress_opts_t* opts) {
1227 if (UNLIKELY(!f_in)) return ZXC_ERROR_NULL_INPUT;
1228
1229 const int n_threads = opts ? opts->n_threads : 0;
1230 const int checksum_enabled = opts ? opts->checksum_enabled : 0;
1231 const int seekable = opts ? opts->seekable : 0;
1232 const int level = ZXC_OPTS_LEVEL(opts, ZXC_LEVEL_DEFAULT);
1233 const size_t block_size = ZXC_OPTS_BLOCK_SIZE(opts, ZXC_BLOCK_SIZE_DEFAULT);
1234 const uint8_t* dict = opts ? (const uint8_t*)opts->dict : NULL;
1235 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
1236 zxc_progress_callback_t cb = opts ? opts->progress_cb : NULL;
1237 void* ud = opts ? opts->user_data : NULL;
1238
1239 if (UNLIKELY(!zxc_validate_block_size(block_size))) return ZXC_ERROR_BAD_BLOCK_SIZE;
1240
1241 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
1242
1243 const uint8_t* dict_huf = ZXC_OPTS_DICT_HUF(opts);
1244 zxc_stream_engine_t e;
1245 ZXC_MEMSET(&e, 0, sizeof(e));
1246 zxc_stream_ctx_t* const ctx = &e.ctx;
1247 ctx->compression_mode = 1;
1248 ctx->compression_level = level;
1249 ctx->chunk_size = block_size;
1250 ctx->checksum_enabled = checksum_enabled;
1251 ctx->file_has_checksum = checksum_enabled;
1252 ctx->progress_cb = cb;
1253 ctx->progress_user_data = ud;
1254 ctx->dict = dict;
1255 ctx->dict_size = dict_size;
1256 ctx->dict_huf = dict_huf;
1257
1258 // Input size for progress tracking; for decompression the CLI passes it.
1259 long long saved_pos = 0;
1260 uint64_t in_size = 0;
1261 if (cb && zxc_file_span(f_in, &saved_pos, &in_size) == ZXC_OK) {
1262 ctx->total_input_bytes = in_size; // LCOV_EXCL_LINE
1263 fseeko(f_in, saved_pos, SEEK_SET); // LCOV_EXCL_LINE
1264 }
1265
1266 // Seekable: initial block-size tracking array
1267 if (seekable) {
1268 e.w.seek_cap = 64;
1269 e.w.seek_comp = (uint32_t*)ZXC_MALLOC(e.w.seek_cap * sizeof(uint32_t));
1270 if (UNLIKELY(!e.w.seek_comp)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
1271 }
1272 e.w.f = f_out;
1273 const int src = zxc_stream_engine_start(&e, f_in, n_threads);
1274 if (UNLIKELY(src != ZXC_OK)) {
1275 ZXC_FREE(e.w.seek_comp); // LCOV_EXCL_LINE
1276 return src; // LCOV_EXCL_LINE
1277 }
1278
1279 // The writer waits for a job: the header goes first.
1280 if (f_out) {
1281 uint8_t h[ZXC_FILE_HEADER_SIZE];
1282 zxc_write_file_header(h, ZXC_FILE_HEADER_SIZE, block_size, checksum_enabled,
1283 zxc_dict_id(dict, dict_size, dict_huf), seekable);
1284 if (UNLIKELY(fwrite(h, 1, ZXC_FILE_HEADER_SIZE, f_out) != ZXC_FILE_HEADER_SIZE))
1285 ctx->io_error = 1;
1286 }
1287 e.w.total_bytes = ZXC_FILE_HEADER_SIZE;
1288
1289 e.read_idx = zxc_stream_read_loop(ctx, f_in, 0);
1291
1292 if (!ctx->io_error && e.w.total_bytes >= 0) zxc_stream_finish_compress(ctx, &e.w, f_out);
1293 // stdio defers write errors to flush time: push the last buffer to the OS
1294 // so a failure (ENOSPC, EFBIG, EPIPE) is reported here instead of being
1295 // left to the caller's fclose, after a success count was returned.
1296 if (UNLIKELY(!ctx->io_error && f_out && fflush(f_out) != 0)) ctx->io_error = 1;
1297 ZXC_FREE(e.w.seek_comp);
1298
1299 if (UNLIKELY(ctx->io_error)) return ctx->fail_code ? ctx->fail_code : ZXC_ERROR_IO;
1300 return e.w.total_bytes;
1301}
1302
1311int64_t zxc_stream_decompress(FILE* f_in, FILE* f_out, const zxc_decompress_opts_t* opts) {
1312 if (UNLIKELY(!f_in)) return ZXC_ERROR_NULL_INPUT;
1313
1314 const int n_threads = opts ? opts->n_threads : 0;
1315 const int checksum_enabled = opts ? opts->checksum_enabled : 0;
1316 const uint8_t* dict = opts ? (const uint8_t*)opts->dict : NULL;
1317 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
1318 const uint8_t* dict_huf = ZXC_OPTS_DICT_HUF(opts);
1319 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
1320 const uint32_t dict_id = zxc_dict_id(dict, dict_size, dict_huf);
1321
1322 zxc_stream_engine_t e;
1323 int running = 0;
1324 uint8_t magic[sizeof(uint32_t)];
1325 int rc = zxc_stream_read_exact(NULL, f_in, magic, sizeof(magic), ZXC_ERROR_SRC_TOO_SMALL);
1326
1327 int64_t total = 0;
1328 for (int frames = 0;; frames++) {
1329 if (rc == 1) {
1330 // A clean end of input, after at least one frame.
1331 rc = frames ? ZXC_OK : ZXC_ERROR_SRC_TOO_SMALL;
1332 break;
1333 }
1334 if (UNLIKELY(rc != ZXC_OK)) break;
1335 if (UNLIKELY(zxc_le32(magic) != ZXC_MAGIC_WORD)) {
1337 break;
1338 }
1339 size_t chunk = 0;
1340 int has_checksum = 0;
1341 int has_seek = 0;
1342 rc = zxc_stream_read_frame_header(running ? &e.ctx : NULL, f_in, magic, dict_id, &chunk,
1343 &has_checksum, &has_seek);
1344 if (UNLIKELY(rc != ZXC_OK)) break;
1345
1346 // The workers are set up for one block size and checksum flag.
1347 if (running && (e.ctx.chunk_size != chunk || e.ctx.file_has_checksum != has_checksum)) {
1348 running = 0;
1349 rc = zxc_stream_engine_close(&e, f_in);
1350 if (UNLIKELY(rc != ZXC_OK)) break; // LCOV_EXCL_LINE
1351 }
1352 if (!running) {
1353 ZXC_MEMSET(&e, 0, sizeof(e));
1354 e.ctx.chunk_size = chunk;
1355 e.ctx.checksum_enabled = checksum_enabled;
1356 e.ctx.file_has_checksum = has_checksum;
1357 e.ctx.progress_cb = opts ? opts->progress_cb : NULL;
1358 e.ctx.progress_user_data = opts ? opts->user_data : NULL;
1359 e.ctx.dict = dict;
1360 e.ctx.dict_size = dict_size;
1361 e.ctx.dict_huf = dict_huf;
1362 e.w.f = f_out;
1363 e.w.bytes_processed = (uint64_t)total; // Progress counts across frames
1364 rc = zxc_stream_engine_start(&e, f_in, n_threads);
1365 if (UNLIKELY(rc != ZXC_OK)) break; // LCOV_EXCL_LINE
1366 running = 1;
1367 }
1368
1369 const int64_t r = zxc_stream_decode_frame(&e, f_in, has_seek);
1370 if (UNLIKELY(r < 0)) {
1371 rc = (int)r;
1372 break;
1373 }
1374 total += r;
1375 rc = zxc_stream_read_exact(&e.ctx, f_in, magic, sizeof(magic), ZXC_ERROR_CORRUPT_DATA);
1376 }
1377 if (running) {
1378 const int crc = zxc_stream_engine_close(&e, f_in);
1379 if (rc == ZXC_OK) rc = crc;
1380 }
1381 // stdio defers write errors to flush time (see zxc_stream_compress).
1382 if (rc == ZXC_OK && f_out && UNLIKELY(fflush(f_out) != 0)) rc = ZXC_ERROR_IO;
1383 return rc != ZXC_OK ? rc : total;
1384}
1385
1387static int zxc_file_scan_read(const zxc_scan_src_t* src, const uint64_t off, void* dst,
1388 const size_t len) {
1389 FILE* const f = (FILE*)src->ctx;
1390 if (UNLIKELY(fseeko(f, (long long)off, SEEK_SET) != 0 || fread(dst, 1, len, f) != len))
1391 return ZXC_ERROR_IO; // LCOV_EXCL_LINE
1392 return ZXC_OK;
1393}
1394
1399static int zxc_stream_read_frame_info(FILE* f_in, zxc_frame_info_t* info) {
1400 long long saved = 0;
1401 uint64_t size = 0;
1402 int rc = zxc_file_span(f_in, &saved, &size);
1403 if (UNLIKELY(rc != ZXC_OK)) return rc;
1404 const zxc_scan_src_t src = {zxc_file_scan_read, NULL, f_in, size};
1405 rc = zxc_scan_whole_frame(&src, info, NULL);
1406 fseeko(f_in, saved, SEEK_SET);
1407 return rc;
1408}
1409
1417 if (UNLIKELY(!f_in)) return ZXC_ERROR_NULL_INPUT;
1418 long long saved = 0;
1419 uint64_t size = 0;
1420 int rc = zxc_file_span(f_in, &saved, &size);
1421 if (UNLIKELY(rc != ZXC_OK)) return rc;
1423 const zxc_scan_src_t src = {zxc_file_scan_read, NULL, f_in, size};
1424 rc = zxc_scan_container(&src, 0, &info);
1425 fseeko(f_in, saved, SEEK_SET);
1426 if (UNLIKELY(rc != ZXC_OK)) return rc;
1427 if (UNLIKELY(info.dsize > (uint64_t)INT64_MAX)) return ZXC_ERROR_CORRUPT_DATA;
1428 return (int64_t)info.dsize;
1429}
1430
1436// cppcheck-suppress unusedFunction
1437int zxc_stream_get_frame_info(FILE* f_in, zxc_frame_info_t* info, const size_t info_size) {
1438 if (UNLIKELY(!f_in || !info)) return ZXC_ERROR_NULL_INPUT;
1439 zxc_frame_info_t got;
1440 const int rc = zxc_stream_read_frame_info(f_in, &got);
1441 if (rc == ZXC_OK) zxc_frame_info_copy(info, info_size, &got);
1442 return rc;
1443}
1444
1450// cppcheck-suppress unusedFunction
1451int zxc_stream_get_last_frame_info(FILE* f_in, const uint64_t end, zxc_frame_info_t* info,
1452 const size_t info_size) {
1453 if (UNLIKELY(!f_in || !info)) return ZXC_ERROR_NULL_INPUT;
1454 long long saved = 0;
1455 uint64_t size = 0;
1456 int rc = zxc_file_span(f_in, &saved, &size);
1457 if (UNLIKELY(rc != ZXC_OK)) return rc;
1458 zxc_frame_info_t got;
1459 if (UNLIKELY(end > size)) {
1461 } else {
1462 const zxc_scan_src_t src = {zxc_file_scan_read, NULL, f_in, end};
1463 uint64_t start = 0;
1464 rc = zxc_scan_frame(&src, end, &start, &got, NULL);
1465 }
1466 fseeko(f_in, saved, SEEK_SET);
1467 if (rc == ZXC_OK) zxc_frame_info_copy(info, info_size, &got);
1468 return rc;
1469}
1470
1471// ============================================================================
1472// SEEKABLE FILE* WRAPPER
1473// ============================================================================
1474// Adapts a FILE* into a thread-safe zxc_reader_t (pread, or ReadFile +
1475// OVERLAPPED on Windows). It lives here rather than in zxc_seekable.c so that
1476// file stays freestanding.
1477
1478#if defined(_WIN32)
1480typedef struct {
1481 HANDLE handle; /* OS handle from _get_osfhandle(_fileno(f)) */
1482 uint64_t size; /* total file size in bytes */
1483} zxc_stdio_ctx_t;
1484
1497// LCOV_EXCL_START - Windows I/O path, not reachable on POSIX CI
1498static int64_t zxc_stdio_read_at(void* vctx, void* dst, size_t len, uint64_t offset) {
1499 zxc_stdio_ctx_t* const ctx = (zxc_stdio_ctx_t*)vctx;
1500 OVERLAPPED ov;
1501 ZXC_MEMSET(&ov, 0, sizeof(ov));
1502 ov.Offset = (DWORD)(offset & 0xFFFFFFFFU);
1503 ov.OffsetHigh = (DWORD)(offset >> 32);
1504 DWORD bytes_read = 0;
1505 if (!ReadFile(ctx->handle, dst, (DWORD)len, &bytes_read, &ov)) return ZXC_ERROR_IO;
1506 return (bytes_read == (DWORD)len) ? (int64_t)len : ZXC_ERROR_IO;
1507}
1508// LCOV_EXCL_STOP
1509
1510#else /* POSIX */
1512typedef struct {
1513 int fd; /* descriptor from fileno(f) */
1514 uint64_t size; /* total file size in bytes */
1515} zxc_stdio_ctx_t;
1516
1529// cppcheck-suppress constParameterCallback ; the reader callback type takes void*
1530static int64_t zxc_stdio_read_at(void* vctx, void* dst, size_t len, uint64_t offset) {
1531 const zxc_stdio_ctx_t* const ctx = (const zxc_stdio_ctx_t*)vctx;
1532 return zxc_pread_full(ctx->fd, dst, len, offset) == (int64_t)len ? (int64_t)len : ZXC_ERROR_IO;
1533}
1534#endif /* _WIN32 */
1535
1547 if (UNLIKELY(!f)) return NULL;
1548
1549 // Snapshot the caller's file position so we can restore it.
1550 const long long saved_pos = ftello(f);
1551 if (UNLIKELY(saved_pos < 0)) return NULL; // LCOV_EXCL_LINE
1552
1553 // LCOV_EXCL_START - ftello/fseeko failure paths not reachable in CI
1554 if (UNLIKELY(fseeko(f, 0, SEEK_END) != 0)) return NULL;
1555 const long long file_size = ftello(f);
1556 (void)fseeko(f, saved_pos, SEEK_SET);
1557 if (UNLIKELY(file_size <= 0)) return NULL;
1558 // LCOV_EXCL_STOP
1559
1560 zxc_stdio_ctx_t* const ctx = (zxc_stdio_ctx_t*)ZXC_MALLOC(sizeof(*ctx));
1561 if (UNLIKELY(!ctx)) return NULL; // LCOV_EXCL_LINE
1562
1563#if defined(_WIN32)
1564 ctx->handle = (HANDLE)(intptr_t)_get_osfhandle(_fileno(f)); // LCOV_EXCL_LINE
1565#else
1566 ctx->fd = fileno(f);
1567#endif
1568 ctx->size = (uint64_t)file_size;
1569
1570 const zxc_reader_t reader = {
1571 .read_at = zxc_stdio_read_at, .ctx = ctx, .size = (uint64_t)file_size};
1572
1573 zxc_seekable* const s = zxc_seekable_open_reader(&reader);
1574 if (UNLIKELY(!s)) {
1575 ZXC_FREE(ctx);
1576 return NULL;
1577 }
1578
1579 // Hand the ctx lifetime over to the seekable handle.
1581 return s;
1582}
#define ZXC_BLOCK_SIZE_DEFAULT
Default block size (512 KB).
uint64_t zxc_compress_bound(const size_t input_size)
Maximum compressed size for an input of input_size bytes.
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).
@ ZXC_ERROR_BAD_MAGIC
Invalid magic word in file header.
Definition zxc_error.h:48
@ 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_BAD_HEADER
Corrupted or invalid header (checksum mismatch).
Definition zxc_error.h:50
@ 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_ERROR_BAD_CHECKSUM
A block's decoded bytes fail its checksum, or the archive digest mismatches.
Definition zxc_error.h:51
@ ZXC_OK
Success (no error).
Definition zxc_error.h:38
@ ZXC_ERROR_BAD_BLOCK_SIZE
Invalid block size.
Definition zxc_error.h:66
@ ZXC_ERROR_DICT_TOO_LARGE
Dictionary exceeds maximum allowed size.
Definition zxc_error.h:71
#define ZXC_MAGIC_WORD
Magic word opening every frame, stored little-endian (F5 2E B0 9C).
#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.
#define ZXC_ALWAYS_INLINE
Forces a function to be inlined (fallback for non-GCC/Clang/MSVC compilers).
#define ZXC_ALIGNED_FREE(ptr)
Counterpart deallocator for ZXC_ALIGNED_MALLOC.
Definition zxc_deps.h:137
#define ZXC_ALIGNMENT_MASK
Bitmask for cache-line alignment checks.
#define ZXC_ALIGNED_MALLOC(size, alignment)
Cache-line-aligned allocator.
Definition zxc_deps.h:134
#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_CACHE_LINE_SIZE
Assumed CPU cache line size for alignment.
#define ZXC_DECOMPRESS_TAIL_PAD
Tail padding required on the decompression destination buffer.
@ ZXC_LEVEL_DEFAULT
Recommended default: better ratio and decode speed than LZ4.
zxc_seekable * zxc_seekable_open_reader(const zxc_reader_t *r)
Opens a seekable archive through a user-supplied reader.
struct zxc_seekable_s zxc_seekable
Opaque handle for a seekable ZXC archive.
zxc_seekable * zxc_seekable_open_file(FILE *f)
Opens a seekable archive backed by an open FILE*.
int64_t zxc_stream_get_decompressed_size(FILE *f_in)
Reads the total decompressed size from the footers of a file.
int64_t zxc_stream_decompress(FILE *f_in, FILE *f_out, const zxc_decompress_opts_t *opts)
Decompresses a FILE* stream to another FILE* stream.
int64_t zxc_stream_compress(FILE *f_in, FILE *f_out, const zxc_compress_opts_t *opts)
Compresses a FILE* stream to another FILE* stream.
int zxc_stream_get_frame_info(FILE *f_in, zxc_frame_info_t *info, const size_t info_size)
Reads a file's frame header and footer, without decoding.
int zxc_stream_get_last_frame_info(FILE *f_in, const uint64_t end, zxc_frame_info_t *info, const size_t info_size)
Reads the frame that ends at offset end of a file, without decoding.
#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.
uint8_t block_type
Block type (see zxc_block_type_t).
Compression / decompression context.
zxc_dict_huf_state_t * dict_huf
Tree-at-attach state (PivCo tree + codes + code lengths), carved from the workspace only when dict_si...
int compression_level
Compression level.
uint8_t * dict_buffer
[dict | data] concat scratch carved from memory_block when dict_size > 0 (NULL otherwise).
int checksum_enabled
1 if checksum calculation/verification is enabled.
Options for compression.
Definition zxc_opts.h:58
int seekable
1 to append a seek table for random-access decompression.
Definition zxc_opts.h:64
zxc_progress_callback_t progress_cb
Optional progress callback (NULL to disable).
Definition zxc_opts.h:72
const void * dict
Pre-trained dictionary content (NULL = none).
Definition zxc_opts.h:65
void * user_data
User context pointer passed to progress_cb.
Definition zxc_opts.h:73
int checksum_enabled
1 to append the block checksums and archive digest, 0 to disable.
Definition zxc_opts.h:63
int n_threads
Worker thread count (0 = auto-detect CPU cores).
Definition zxc_opts.h:59
What zxc_scan_container learns about a container.
uint64_t dsize
Sum of the stored decompressed sizes.
Options for decompression.
Definition zxc_opts.h:86
int checksum_enabled
1 to verify the block checksums and archive digest, 0 to skip.
Definition zxc_opts.h:88
int n_threads
Worker thread count (0 = auto-detect CPU cores).
Definition zxc_opts.h:87
zxc_progress_callback_t progress_cb
Optional progress callback (NULL to disable).
Definition zxc_opts.h:94
void * user_data
User context pointer passed to progress_cb.
Definition zxc_opts.h:95
const void * dict
Pre-trained dictionary content (NULL = none).
Definition zxc_opts.h:89
What a frame's header and footer declare, read without decoding.
Definition zxc_buffer.h:235
Storage-agnostic reader interface for seekable archives.
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_write_file_header(uint8_t *dst, const size_t dst_capacity, const size_t chunk_size, const int has_checksum, const uint32_t dict_id, const int has_seek)
Serialises a ZXC file header into dst.
Definition zxc_common.c:486
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_write_block_header(uint8_t *dst, const size_t dst_capacity, const zxc_block_header_t *bh)
Serialises a block header (8 bytes) into dst.
Definition zxc_common.c:684
int zxc_scan_container(const zxc_scan_src_t *src, const size_t req_chunk, zxc_container_info_t *info)
Measures a whole container without decoding: frames from the last back to the first through zxc_scan_...
Definition zxc_common.c:613
int zxc_seek_table_header(uint8_t *dst, const size_t dst_capacity, const uint64_t num_blocks)
Writes a seek table's block header for num_blocks entries.
Definition zxc_common.c:738
int zxc_read_file_header(const uint8_t *src, const size_t src_size, size_t *out_block_size, int *out_has_checksum, uint32_t *out_dict_id, int *out_has_seek)
Parses and validates a ZXC file header from src.
Definition zxc_common.c:516
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_scan_whole_frame(const zxc_scan_src_t *src, zxc_frame_info_t *info, size_t *footer_len)
The frame that must span all of src: zxc_get_frame_info() and its FILE* twin.
Definition zxc_common.c:601
size_t zxc_seek_write_group(uint8_t *dst, uint64_t *anchor, const uint32_t *sizes, const uint32_t cnt)
Writes one group (*anchor, then cnt sizes) into dst, which holds ZXC_SEEK_GROUP_BYTES,...
Definition zxc_common.c:770
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_write_file_footer(uint8_t *dst, const size_t dst_capacity, const uint64_t prefix, const uint64_t src_size, const uint64_t digest, const int checksum_enabled)
Writes the file footer: the archive digest when checksum_enabled, then the two sizes and their length...
Definition zxc_common.c:832
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_file_footer(const uint8_t *footer, const size_t avail, const zxc_footer_layout_t *l, const uint64_t src_size, uint64_t *digest)
Checks a footer against the one the frame implies.
Definition zxc_common.c:847
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_compress_chunk_wrapper(zxc_cctx_t *ctx, const uint8_t *chunk, const size_t src_sz, uint8_t *dst, const size_t dst_cap, const uint64_t block_index)
Compresses one chunk into a single ZXC block (the compression hot path).
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).
static int zxc_stream_regular_fd(FILE *f)
Descriptor of a regular-file stream, -1 for anything else (may be live).
Definition zxc_driver.c:354
static void zxc_stream_engine_stop(zxc_stream_engine_t *e, const int writer_started)
Stops the threads, then frees the ring and its synchronisation objects.
Definition zxc_driver.c:707
static void zxc_stream_finish_decompress(zxc_stream_ctx_t *ctx, const uint64_t frame_out, FILE *f_in)
Consumes and validates the trailer of a decompressed stream.
static size_t zxc_stream_read_blocks(zxc_stream_ctx_t *ctx, FILE *f_in, zxc_stream_job_t *job, int *n_blocks, int *eof)
Fills a decompression job with up to ctx->batch consecutive blocks.
Definition zxc_driver.c:873
static int64_t zxc_stdio_read_at(void *vctx, void *dst, size_t len, uint64_t offset)
Thread-safe positioned read backing the seekable FILE* reader.
static void zxc_stream_reader_fail(zxc_stream_ctx_t *ctx, const int code)
Records a reader failure; workers write fail_code under the same lock.
Definition zxc_driver.c:428
static int64_t zxc_stream_read_at(const zxc_stream_ctx_t *ctx, void *dst, const size_t len, const uint64_t offset)
Positioned read of the input, safe from any thread.
Definition zxc_driver.c:390
static void zxc_stream_open_positioned(zxc_stream_ctx_t *ctx, FILE *f_in, const int fd)
Switches a regular file to positioned reads from its current position.
Definition zxc_driver.c:370
static int64_t zxc_stream_in_read(zxc_stream_ctx_t *ctx, FILE *f, void *dst, const size_t len)
Reads up to len input bytes on the reader thread: by position while the engine reads positioned,...
Definition zxc_driver.c:414
static int zxc_stream_read_frame_info(FILE *f_in, zxc_frame_info_t *info)
Reads the header and the end of the file in f_in, from offset 0, and validates the frame they describ...
static int zxc_file_span(FILE *f, long long *saved, uint64_t *size)
The size of f; its position goes to saved, for the caller to restore.
static int zxc_stream_process(const zxc_stream_ctx_t *ctx, zxc_cctx_t *cctx, const uint8_t *in, const size_t in_sz, uint8_t *out, const size_t out_cap, const uint64_t block_index)
Runs one chunk through the codec the stream's mode selects.
Definition zxc_driver.c:441
static int64_t zxc_stream_decode_frame(zxc_stream_engine_t *e, FILE *f_in, const int has_seek)
Decodes one frame's blocks, SEK block and footer through a started engine.
static size_t zxc_stream_read_chunk(zxc_stream_ctx_t *ctx, FILE *f_in, zxc_stream_job_t *job, int *eof)
Fills a compression job with the next chunk of the source.
Definition zxc_driver.c:969
static int zxc_ring_next(const zxc_stream_ctx_t *ctx, const int idx)
The ring slot after idx.
Definition zxc_driver.c:270
static int zxc_stream_engine_close(zxc_stream_engine_t *e, FILE *f_in)
Stops a decompression engine; moves the FILE* to where positioned reads ended.
Definition zxc_driver.c:847
static int64_t zxc_pread_full(const int fd, void *dst, const size_t len, const uint64_t offset)
Thread-safe pread of len bytes, retrying EINTR and partial transfers.
Definition zxc_driver.c:336
static int zxc_stream_read_exact(zxc_stream_ctx_t *ctx, FILE *f, void *dst, const size_t len, const int short_code)
Reads exactly len bytes, telling a clean end from a short read.
static int zxc_stream_engine_start(zxc_stream_engine_t *e, FILE *f_in, const int n_threads)
Allocates the job ring and starts the workers and the writer.
Definition zxc_driver.c:764
static void * zxc_stream_worker(void *arg)
Worker thread: pull a job, process it, hand it to the writer.
Definition zxc_driver.c:504
static int zxc_file_scan_read(const zxc_scan_src_t *src, const uint64_t off, void *dst, const size_t len)
zxc_scan_src_t over a FILE*.
static void * zxc_async_writer(void *arg)
Asynchronous writer thread function.
Definition zxc_driver.c:617
static int zxc_stream_read_frame_header(zxc_stream_ctx_t *ctx, FILE *f_in, const uint8_t *magic, const uint32_t dict_id, size_t *chunk, int *has_checksum, int *has_seek)
Reads and checks a frame header whose magic word is already read.
job_status_t
Represents the lifecycle states of a processing job within the ring buffer.
Definition zxc_driver.c:91
@ JOB_STATUS_FREE
The job slot is empty and available to be filled with new data by the writer.
Definition zxc_driver.c:91
@ JOB_STATUS_FILLED
The job slot has been populated with input data and is ready for processing by a worker.
Definition zxc_driver.c:91
@ JOB_STATUS_PROCESSED
The worker has finished processing the data; the result is ready to be consumed/written out.
Definition zxc_driver.c:91
static void zxc_stream_engine_drain(zxc_stream_engine_t *e)
Waits until the writer has written every job queued so far.
Definition zxc_driver.c:855
static int zxc_stream_read_loop(zxc_stream_ctx_t *ctx, FILE *f_in, int read_idx)
Reads the input and feeds the ring until the stream ends.
Definition zxc_driver.c:982
static int zxc_stream_decode_job(const zxc_stream_ctx_t *ctx, zxc_cctx_t *cctx, const zxc_stream_job_t *job, const uint8_t *in, uint8_t *dict_work)
Decodes a job's blocks into its output.
Definition zxc_driver.c:457
static void zxc_stream_finish_compress(zxc_stream_ctx_t *ctx, writer_args_t *w, FILE *f_out)
Closes a compressed stream: EOF block, optional seek table, footer.
Internal definitions, constants, SIMD helpers, and utility functions.
static uint32_t zxc_le32(const void *p)
Reads a little-endian 32-bit value from a possibly unaligned address.
static void zxc_frame_info_copy(zxc_frame_info_t *info, const size_t info_size, const zxc_frame_info_t *got)
Copies the fields of got that fit the caller's info_size bytes.
static int zxc_validate_block_size(const size_t bs)
Validates a block size.
void zxc_seekable_attach_owned_ctx(zxc_seekable *s, void *ctx)
Hands ownership of a heap-allocated reader context to a seekable handle.
static uint64_t zxc_digest_combine(const uint64_t acc, const uint32_t block_checksum)
Folds a data block's 32-bit checksum into the running archive digest.
static size_t zxc_block_size_ceil(const size_t v)
Branchless bit_ceil: smallest power of two >= v, clamped to ZXC_BLOCK_SIZE_MIN.
#define ZXC_OPTS_DICT_HUF(o)
Shared literal Huffman table, gated on dict the same way.
#define ZXC_OPTS_LEVEL(o, dflt)
Compression level, 0 meaning the default, clamped to the highest level the encoder implements.
#define ZXC_FOOTER_MAX_SIZE_WITH_DIGEST
Most bytes a footer spans: the digest and the two longest sizes.
#define ZXC_OPTS_DICT_SIZE(o)
Dictionary length from a (possibly NULL) options struct.
#define ZXC_OPTS_BLOCK_SIZE(o, dflt)
Block size, 0 meaning the default.
static zxc_footer_layout_t zxc_footer_layout(const uint64_t prefix, const uint64_t src_size, const int checksum_enabled)
The footer of a frame whose first prefix bytes precede it.
void(* zxc_progress_callback_t)(uint64_t bytes_processed, uint64_t bytes_total, const void *user_data)
Progress callback function type.
Definition zxc_opts.h:43
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.