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"
54#define fseeko _fseeki64
55#define ftello _ftelli64
62#define ZXC_STREAM_PREAD_MIN_BLOCK ((size_t)64 * 1024)
66#define ZXC_STREAM_BATCH_BYTES ((size_t)256 * 1024)
69#define ZXC_STREAM_RING_BATCHED 2U
134 uint64_t block_index;
231 zxc_stream_job_t* jobs;
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;
246 int compression_level;
248 int checksum_enabled;
249 int file_has_checksum;
253 void* progress_user_data;
254 uint64_t total_input_bytes;
257 const uint8_t* dict_huf;
271 return (
int)(((size_t)idx + 1) % ctx->ring_size);
305 zxc_stream_ctx_t* ctx;
309 uint64_t bytes_processed;
320 zxc_stream_ctx_t ctx;
327} zxc_stream_engine_t;
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))
341 const ssize_t r = pread(fd, (uint8_t*)dst + done, len - done, (off_t)(offset + done));
346 else if (errno != EINTR)
349 return (int64_t)done;
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;
364 const int fd = fileno(f);
365 return (fd >= 0 && fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) ? fd : -1;
376 if (
sizeof(off_t) <
sizeof(uint64_t))
return;
377 const long long pos = ftello(f_in);
378 if (UNLIKELY(pos < 0))
return;
380 ctx->in_pos = (uint64_t)pos;
391 const uint64_t offset) {
415 if (ctx && ctx->in_fd >= 0) {
417 if (LIKELY(r > 0)) ctx->in_pos += (uint64_t)r;
420 const size_t got = fread(dst, 1, len, f);
423 if (UNLIKELY(got < len && !feof(f)))
return ZXC_ERROR_IO;
429 pthread_mutex_lock(&ctx->lock);
430 if (code && !ctx->fail_code) ctx->fail_code = code;
432 pthread_mutex_unlock(&ctx->lock);
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
458 const zxc_stream_job_t* job,
const uint8_t* in,
459 uint8_t* dict_work) {
464 uint8_t*
const scratch = dict_work ? dict_work + ctx->dict_size : NULL;
465 uint64_t block_index = job->block_index;
469 while (ip < job->in_sz) {
470 const size_t rem = job->in_sz - ip;
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;
505 zxc_stream_ctx_t*
const ctx = (zxc_stream_ctx_t*)arg;
508 const int unified_chk = (ctx->compression_mode == 1)
510 : (ctx->file_has_checksum && ctx->checksum_enabled);
512 const size_t eff_chunk = (ctx->dict_size > 0 && ctx->compression_mode == 1)
516 uint8_t*
const in_work = ctx->in_fd >= 0 ? (uint8_t*)
ZXC_MALLOC(ctx->in_alloc) : NULL;
518 unified_chk, ctx->dict_size) !=
ZXC_OK ||
520 (ctx->in_fd >= 0 && !in_work))) {
524 pthread_mutex_lock(&ctx->lock);
526 pthread_cond_broadcast(&ctx->cond_writer);
527 pthread_cond_broadcast(&ctx->cond_reader);
528 pthread_mutex_unlock(&ctx->lock);
536 const size_t dsz = ctx->dict_size;
538 if (dict_work) ZXC_MEMCPY(dict_work, ctx->dict, dsz);
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);
546 if (ctx->shutdown_workers && ctx->wq_count == 0) {
547 pthread_mutex_unlock(&ctx->lock);
550 const int jid = ctx->worker_queue[ctx->wq_tail];
553 job = &ctx->jobs[jid];
554 pthread_mutex_unlock(&ctx->lock);
557 const uint8_t*
const in = in_work ? in_work : job->in_buf;
560 (int64_t)job->in_sz)) {
562 }
else if (ctx->compression_mode == 0) {
564 }
else if (dict_work) {
565 ZXC_MEMCPY(dict_work + dsz, in, job->in_sz);
567 job->out_cap, job->block_index);
573 pthread_mutex_lock(&ctx->lock);
574 job->result_sz = UNLIKELY(res < 0) ? 0 : (size_t)res;
576 if (UNLIKELY(res < 0)) {
578 if (!ctx->fail_code) ctx->fail_code = res;
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);
585 pthread_mutex_unlock(&ctx->lock);
618 writer_args_t*
const args = (writer_args_t*)arg;
619 zxc_stream_ctx_t*
const ctx = args->ctx;
621 zxc_stream_job_t*
const job = &ctx->jobs[ctx->write_idx];
622 pthread_mutex_lock(&ctx->lock);
624 pthread_cond_wait(&ctx->cond_writer, &ctx->lock);
627 pthread_mutex_unlock(&ctx->lock);
631 const size_t result_sz = job->result_sz;
632 const size_t in_sz = job->in_sz;
633 pthread_mutex_unlock(&ctx->lock);
635 if (result_sz == (
size_t)-1)
break;
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);
641 pthread_cond_signal(&ctx->cond_reader);
642 pthread_mutex_unlock(&ctx->lock);
645 if (UNLIKELY(ctx->io_error)) {
646 pthread_mutex_lock(&ctx->lock);
648 pthread_cond_signal(&ctx->cond_reader);
649 pthread_mutex_unlock(&ctx->lock);
652 args->total_bytes += (int64_t)result_sz;
653 if (ctx->checksum_enabled && ctx->compression_mode == 1 &&
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);
663 if (LIKELY(args->seek_cap < max_cap)) {
664 args->seek_cap = args->seek_cap < max_cap / 2 ? args->seek_cap * 2 : max_cap;
666 (
size_t)args->seek_cap *
sizeof(uint32_t));
670 pthread_mutex_lock(&ctx->lock);
674 pthread_cond_signal(&ctx->cond_reader);
675 pthread_mutex_unlock(&ctx->lock);
679 args->seek_comp = nc;
681 args->seek_comp[args->seek_count++] = (uint32_t)result_sz;
684 if (ctx->progress_cb) {
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);
692 pthread_mutex_lock(&ctx->lock);
695 pthread_cond_signal(&ctx->cond_reader);
696 pthread_mutex_unlock(&ctx->lock);
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);
713 pthread_cond_wait(&ctx->cond_reader, &ctx->lock);
714 end_job->result_sz = (size_t)-1;
716 pthread_cond_broadcast(&ctx->cond_writer);
717 pthread_mutex_unlock(&ctx->lock);
718 pthread_join(e->writer, NULL);
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);
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);
765 zxc_stream_ctx_t*
const ctx = &e->ctx;
766 const int mode = ctx->compression_mode;
767 const size_t chunk = ctx->chunk_size;
769 int num_threads = (n_threads > 0) ? n_threads :
zxc_num_procs();
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)
779 const int num_workers = ctx->in_fd >= 0 ? num_threads : (num_threads > 1) ? num_threads - 1 : 1;
781 ctx->ring_size = (size_t)num_workers * (ctx->batch > 1 ? ZXC_STREAM_RING_BATCHED : 4U);
784 const size_t raw_alloc_in =
785 (size_t)((mode ? chunk : max_out * (uint64_t)ctx->batch) +
ZXC_PAD_SIZE);
787 ctx->in_alloc = alloc_in;
789 const size_t raw_alloc_out =
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))
802 e->mem_block = mem_block;
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;
813 ZXC_MEMSET(mem_block, 0, ctx->ring_size * (
sizeof(zxc_stream_job_t) +
sizeof(
int)));
815 for (
size_t i = 0; i < ctx->ring_size; i++) {
816 ctx->jobs[i].job_id = (int)i;
818 ctx->jobs[i].in_buf = ring_in ? buf_in + (i * ring_in) : NULL;
820 ctx->jobs[i].in_sz = 0;
821 ctx->jobs[i].out_buf = buf_out + (i * alloc_out);
823 ctx->jobs[i].result_sz = 0;
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);
831 e->workers =
ZXC_MALLOC((
size_t)num_workers *
sizeof(pthread_t));
832 if (LIKELY(e->workers)) {
833 while (e->n_workers < num_workers &&
838 if (UNLIKELY(e->n_workers == 0 ||
849 if (e->ctx.in_fd < 0)
return ZXC_OK;
856 zxc_stream_ctx_t*
const ctx = &e->ctx;
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);
862 pthread_cond_wait(&ctx->cond_reader, &ctx->lock);
863 pthread_mutex_unlock(&ctx->lock);
874 int* n_blocks,
int* eof) {
875 const int positioned = ctx->in_fd >= 0;
879 const int fold = ctx->file_has_checksum && ctx->checksum_enabled;
883 for (*n_blocks = 0; *n_blocks < ctx->batch; (*n_blocks)++) {
885 const uint64_t block_off = ctx->in_pos;
887 if (positioned && ctx->ahead_len >= 0) {
888 h_read = ctx->ahead_len;
889 ZXC_MEMCPY(bh_buf, ctx->ahead, (
size_t)h_read);
891 }
else if (positioned) {
896 if (UNLIKELY(h_read < 0))
goto _failed;
897 if (positioned) ctx->in_pos += (uint64_t)h_read;
919 if (UNLIKELY((uint64_t)bh.
comp_size > (uint64_t)ctx->chunk_size)) {
924 const size_t body_total = (size_t)bh.
comp_size + checksum_sz;
926 if (UNLIKELY(total_len > (uint64_t)(job->in_cap - fill)))
goto _failed;
929 const uint8_t* stored = tail;
935 if (fill == 0) job->in_off = block_off;
936 ctx->in_pos += body_total;
947 uint8_t*
const block = job->in_buf + fill;
955 if (UNLIKELY(!got))
goto _failed;
957 fill += (size_t)total_len;
958 ctx->frame_in += total_len;
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;
984 uint64_t block_index = 0;
986 while (!read_eof && !ctx->io_error) {
987 zxc_stream_job_t*
const job = &ctx->jobs[read_idx];
988 pthread_mutex_lock(&ctx->lock);
990 pthread_cond_wait(&ctx->cond_reader, &ctx->lock);
991 pthread_mutex_unlock(&ctx->lock);
993 if (UNLIKELY(ctx->io_error))
break;
996 const size_t read_sz = ctx->compression_mode
999 if (UNLIKELY(read_eof && read_sz == 0))
break;
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);
1006 ctx->worker_queue[ctx->wq_head] = read_idx;
1010 pthread_cond_signal(&ctx->cond_worker);
1011 pthread_mutex_unlock(&ctx->lock);
1025 .
block_type = ZXC_BLOCK_EOF, .block_flags = 0, .reserved = 0, .comp_size = 0};
1027 if (UNLIKELY(f_out &&
1034 if (!ctx->io_error && w->seek_comp) {
1038 if (UNLIKELY(h < 0 || (f_out && fwrite(st_buf, 1, (
size_t)h, f_out) != (
size_t)h)))
1041 w->total_bytes += h;
1044 for (uint64_t i = 0; i < w->seek_count && !ctx->io_error; i +=
ZXC_SEEK_GROUP) {
1045 const size_t bytes =
1048 if (UNLIKELY(f_out && fwrite(st_buf, 1, bytes, f_out) != bytes))
1051 w->total_bytes += (int64_t)bytes;
1057 const size_t footer_len =
1059 ctx->src_total, w->digest, ctx->checksum_enabled);
1060 if (UNLIKELY(f_out && fwrite(footer_buf, 1, footer_len, f_out) != footer_len))
1063 w->total_bytes += (int64_t)footer_len;
1074 if (ctx->file_has_seek) {
1076 uint64_t remaining = 0;
1077 uint64_t sek_bytes = 0;
1082 ctx->chunk_size, &remaining);
1083 if (UNLIKELY(src_rc !=
ZXC_OK)) {
1087 sek_bytes = remaining;
1089 if (ctx->in_fd >= 0) {
1090 ctx->in_pos += remaining;
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;
1103 uint64_t stored_digest = 0;
1104 if (!ctx->io_error) {
1110 if (UNLIKELY(frc !=
ZXC_OK)) {
1115 if (!ctx->io_error && ctx->file_has_checksum && ctx->checksum_enabled &&
1116 UNLIKELY(stored_digest != ctx->digest)) {
1136 const int short_code) {
1138 if (LIKELY(got == (int64_t)len))
return ZXC_OK;
1140 return got == 0 ? 1 : short_code;
1156 const uint32_t dict_id,
size_t* chunk,
int* has_checksum,
1159 uint32_t header_dict_id = 0;
1160 ZXC_MEMCPY(h, magic,
sizeof(uint32_t));
1167 &header_dict_id, has_seek);
1168 if (UNLIKELY(hrc !=
ZXC_OK))
return hrc;
1169 if (header_dict_id != 0) {
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;
1187 ctx->ahead_len = -1;
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);
1201 return e->w.total_bytes - base;
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)) {
1211 fseeko(f, *saved, SEEK_SET);
1215 *size = (uint64_t)n;
1229 const int n_threads = opts ? opts->
n_threads : 0;
1231 const int seekable = opts ? opts->
seekable : 0;
1234 const uint8_t* dict = opts ? (
const uint8_t*)opts->
dict : NULL;
1237 void* ud = opts ? opts->
user_data : NULL;
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;
1255 ctx->dict_size = dict_size;
1256 ctx->dict_huf = dict_huf;
1259 long long saved_pos = 0;
1260 uint64_t in_size = 0;
1262 ctx->total_input_bytes = in_size;
1263 fseeko(f_in, saved_pos, SEEK_SET);
1269 e.w.seek_comp = (uint32_t*)
ZXC_MALLOC(e.w.seek_cap *
sizeof(uint32_t));
1274 if (UNLIKELY(src !=
ZXC_OK)) {
1283 zxc_dict_id(dict, dict_size, dict_huf), seekable);
1296 if (UNLIKELY(!ctx->io_error && f_out && fflush(f_out) != 0)) ctx->io_error = 1;
1299 if (UNLIKELY(ctx->io_error))
return ctx->fail_code ? ctx->fail_code :
ZXC_ERROR_IO;
1300 return e.w.total_bytes;
1314 const int n_threads = opts ? opts->
n_threads : 0;
1316 const uint8_t* dict = opts ? (
const uint8_t*)opts->
dict : NULL;
1320 const uint32_t dict_id =
zxc_dict_id(dict, dict_size, dict_huf);
1322 zxc_stream_engine_t e;
1324 uint8_t magic[
sizeof(uint32_t)];
1328 for (
int frames = 0;; frames++) {
1334 if (UNLIKELY(rc !=
ZXC_OK))
break;
1340 int has_checksum = 0;
1343 &has_checksum, &has_seek);
1344 if (UNLIKELY(rc !=
ZXC_OK))
break;
1347 if (running && (e.ctx.chunk_size != chunk || e.ctx.file_has_checksum != has_checksum)) {
1350 if (UNLIKELY(rc !=
ZXC_OK))
break;
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;
1360 e.ctx.dict_size = dict_size;
1361 e.ctx.dict_huf = dict_huf;
1363 e.w.bytes_processed = (uint64_t)total;
1365 if (UNLIKELY(rc !=
ZXC_OK))
break;
1370 if (UNLIKELY(r < 0)) {
1379 if (rc ==
ZXC_OK) rc = crc;
1383 return rc !=
ZXC_OK ? rc : total;
1389 FILE*
const f = (FILE*)src->ctx;
1390 if (UNLIKELY(fseeko(f, (
long long)off, SEEK_SET) != 0 || fread(dst, 1, len, f) != len))
1400 long long saved = 0;
1403 if (UNLIKELY(rc !=
ZXC_OK))
return rc;
1406 fseeko(f_in, saved, SEEK_SET);
1418 long long saved = 0;
1421 if (UNLIKELY(rc !=
ZXC_OK))
return rc;
1425 fseeko(f_in, saved, SEEK_SET);
1426 if (UNLIKELY(rc !=
ZXC_OK))
return rc;
1428 return (int64_t)info.
dsize;
1452 const size_t info_size) {
1454 long long saved = 0;
1457 if (UNLIKELY(rc !=
ZXC_OK))
return rc;
1459 if (UNLIKELY(end > size)) {
1466 fseeko(f_in, saved, SEEK_SET);
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;
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;
1531 const zxc_stdio_ctx_t*
const ctx = (
const zxc_stdio_ctx_t*)vctx;
1547 if (UNLIKELY(!f))
return NULL;
1550 const long long saved_pos = ftello(f);
1551 if (UNLIKELY(saved_pos < 0))
return NULL;
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;
1560 zxc_stdio_ctx_t*
const ctx = (zxc_stdio_ctx_t*)
ZXC_MALLOC(
sizeof(*ctx));
1561 if (UNLIKELY(!ctx))
return NULL;
1564 ctx->handle = (HANDLE)(intptr_t)_get_osfhandle(_fileno(f));
1566 ctx->fd = fileno(f);
1568 ctx->size = (uint64_t)file_size;
#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.
#define ZXC_DICT_SIZE_MAX
Maximum dictionary content size in bytes (64 KB - 1).
@ ZXC_ERROR_BAD_MAGIC
Invalid magic word in file header.
@ ZXC_ERROR_CORRUPT_DATA
Corrupted compressed data.
@ ZXC_ERROR_NULL_INPUT
Required input pointer is NULL.
@ ZXC_ERROR_IO
Read/write/seek failure on file.
@ ZXC_ERROR_DICT_MISMATCH
Provided dictionary ID does not match the file header.
@ ZXC_ERROR_MEMORY
Memory allocation failure.
@ ZXC_ERROR_BAD_HEADER
Corrupted or invalid header (checksum mismatch).
@ ZXC_ERROR_SRC_TOO_SMALL
Source buffer too small or truncated input.
@ ZXC_ERROR_DICT_REQUIRED
File requires a dictionary but none was provided.
@ ZXC_ERROR_BAD_CHECKSUM
A block's decoded bytes fail its checksum, or the archive digest mismatches.
@ ZXC_OK
Success (no error).
@ ZXC_ERROR_BAD_BLOCK_SIZE
Invalid block size.
@ ZXC_ERROR_DICT_TOO_LARGE
Dictionary exceeds maximum allowed size.
#define ZXC_MALLOC(size)
Heap allocator.
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.
#define ZXC_ALIGNMENT_MASK
Bitmask for cache-line alignment checks.
#define ZXC_ALIGNED_MALLOC(size, alignment)
Cache-line-aligned allocator.
#define ZXC_SEEK_GROUP_BYTES
Full group: anchor + ZXC_SEEK_GROUP sizes.
#define ZXC_FREE(ptr)
Heap deallocator.
#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.
#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.
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.
int seekable
1 to append a seek table for random-access decompression.
zxc_progress_callback_t progress_cb
Optional progress callback (NULL to disable).
const void * dict
Pre-trained dictionary content (NULL = none).
void * user_data
User context pointer passed to progress_cb.
int checksum_enabled
1 to append the block checksums and archive digest, 0 to disable.
int n_threads
Worker thread count (0 = auto-detect CPU cores).
What zxc_scan_container learns about a container.
uint64_t dsize
Sum of the stored decompressed sizes.
Options for decompression.
int checksum_enabled
1 to verify the block checksums and archive digest, 0 to skip.
int n_threads
Worker thread count (0 = auto-detect CPU cores).
zxc_progress_callback_t progress_cb
Optional progress callback (NULL to disable).
void * user_data
User context pointer passed to progress_cb.
const void * dict
Pre-trained dictionary content (NULL = none).
What a frame's header and footer declare, read without decoding.
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.
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.
void zxc_cctx_free(zxc_cctx_t *ctx)
Releases all resources owned by a compression context.
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.
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_...
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.
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.
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...
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.
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,...
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,...
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_...
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...
int zxc_cctx_attach_dict_huf(zxc_cctx_t *ctx, const uint8_t *lengths)
Attach the shared dictionary literal Huffman table to a context.
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.
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.
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).
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.
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.
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.
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.
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.
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,...
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.
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.
static int zxc_ring_next(const zxc_stream_ctx_t *ctx, const int idx)
The ring slot after idx.
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.
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.
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.
static void * zxc_stream_worker(void *arg)
Worker thread: pull a job, process it, hand it to the writer.
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.
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.
@ JOB_STATUS_FREE
The job slot is empty and available to be filled with new data by the writer.
@ JOB_STATUS_FILLED
The job slot has been populated with input data and is ready for processing by a worker.
@ JOB_STATUS_PROCESSED
The worker has finished processing the data; the result is ready to be consumed/written out.
static void zxc_stream_engine_drain(zxc_stream_engine_t *e)
Waits until the writer has written every job queued so far.
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.
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.
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.
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.