// SPDX-License-Identifier: LGPL-3.0-or-later /** * \file cpr/bzip2.c * * Interfaces with `libbzip2` and implements BZ2 compressor/decompressor. * * \copyright The DoubleFourteen Code Forge (C) All Rights Reserved * \author Lorenzo Cogotti */ #include "cpr/bzip2.h" #include #include #include #include #include typedef struct Bzip2StmObj Bzip2StmObj; struct Bzip2StmObj { bz_stream bz2; void *streamp; const StmOps *ops; unsigned bufsiz; Boolean8 compressing; char buf[FLEX_ARRAY]; // `bufsiz' bytes }; #define BZIP2_EBADSTREAM 0xffff #define BZIP2_BUFSIZ (32 * 1024) #define MAKESINT64(lo32, hi32) \ ((Sint64) ((Uint64) (lo32) | (((Uint64) hi32) << 32))) static Sint64 Bzip2_FlushData(Bzip2StmHn hn) { size_t nbytes = hn->bufsiz - hn->bz2.avail_out; Sint64 n = hn->ops->Write(hn->streamp, hn->buf, nbytes); if (n <= 0) return -1; size_t left = nbytes - n; memmove(hn->buf, hn->buf + n, left); hn->bz2.next_out = hn->buf + left; hn->bz2.avail_out = hn->bufsiz - left; return n; } static Sint64 Bzip2_StmRead(void *streamp, void *buf, size_t nbytes) { return Bzip2_Read((Bzip2StmHn) streamp, buf, nbytes); } static Sint64 Bzip2_StmWrite(void *streamp, const void *buf, size_t nbytes) { return Bzip2_Write((Bzip2StmHn) streamp, buf, nbytes); } static Sint64 Bzip2_StmTell(void *streamp) { Bzip2StmHn hn = (Bzip2StmHn) streamp; return hn->compressing ? MAKESINT64(hn->bz2.total_out_lo32, hn->bz2.total_out_hi32) : MAKESINT64(hn->bz2.total_in_lo32, hn->bz2.total_in_hi32); } static Judgement Bzip2_StmFinish(void *streamp) { return Bzip2_Finish((Bzip2StmHn) streamp); } static void Bzip2_StmClose(void *streamp) { Bzip2_Close((Bzip2StmHn) streamp); } static const StmOps bzip2_stmOps = { Bzip2_StmRead, Bzip2_StmWrite, NULL, Bzip2_StmTell, Bzip2_StmFinish, Bzip2_StmClose }; static const StmOps bzip2_ncStmOps = { Bzip2_StmRead, Bzip2_StmWrite, NULL, Bzip2_StmTell, Bzip2_StmFinish, NULL }; const StmOps *const Bzip2_StmOps = &bzip2_stmOps; const StmOps *const Bzip2_NcStmOps = &bzip2_ncStmOps; static THREAD_LOCAL Bzip2Ret bzip2_errStat = 0; static void Bzip2_SetErrStat(Bzip2Ret ret) { bzip2_errStat = ret; } Bzip2Ret Bzip2_GetErrStat(void) { return bzip2_errStat; } const char *Bzip2_ErrorString(Bzip2Ret ret) { switch (ret) { case BZIP2_EBADSTREAM: return "Bad stream operation"; case BZ_OK: return "Success"; case BZ_SEQUENCE_ERROR: return "Sequence error"; case BZ_PARAM_ERROR: return "Invalid parameter"; case BZ_MEM_ERROR: return "Memory allocation failure"; case BZ_DATA_ERROR: return "Data integrity error"; case BZ_DATA_ERROR_MAGIC: return "Stream magic number mismatch"; case BZ_IO_ERROR: return "I/O error"; case BZ_UNEXPECTED_EOF: return "Unexpected compressed stream end"; case BZ_OUTBUFF_FULL: return "Output buffer full"; case BZ_CONFIG_ERROR: return "Bzip2 library configuration error"; default: return "Unknown BZ2 error"; } } Bzip2StmHn Bzip2_OpenCompress(void *streamp, const StmOps *ops, const Bzip2CprOpts *opts) { const Bzip2CprOpts defOpts = { 0, 0, 0, 0 }; if (!opts) opts = &defOpts; if (!ops->Write) { Bzip2_SetErrStat(BZIP2_EBADSTREAM); return NULL; } int compression = CLAMP(opts->compression, 0, 9); if (compression == 0) compression = 9; // default value int verbosity = CLAMP(opts->verbose, 0, 4); int factor = opts->factor; size_t bufsiz = MIN(opts->bufsiz, INT_MAX); if (bufsiz == 0) bufsiz = BZIP2_BUFSIZ; Bzip2StmObj *hn = (Bzip2StmObj *) malloc(offsetof(Bzip2StmObj, buf) + bufsiz); if (!hn) { Bzip2_SetErrStat(BZ_MEM_ERROR); return NULL; } memset(&hn->bz2, 0, sizeof(hn->bz2)); hn->streamp = streamp; hn->ops = ops; hn->bufsiz = bufsiz; hn->compressing = TRUE; int err = BZ2_bzCompressInit(&hn->bz2, compression, verbosity, factor); if (err != BZ_OK) { Bzip2_SetErrStat(err); free(hn); return NULL; } hn->bz2.next_out = hn->buf; hn->bz2.avail_out = hn->bufsiz; Bzip2_SetErrStat(BZ_OK); return hn; } Bzip2StmHn Bzip2_OpenDecompress(void *streamp, const StmOps *ops, const Bzip2DecOpts *opts) { const Bzip2DecOpts defOpts = { 0, 0, FALSE }; if (!opts) opts = &defOpts; if (!ops->Read) { Bzip2_SetErrStat(BZIP2_EBADSTREAM); return NULL; } int small = opts->low_mem; int verbosity = CLAMP(opts->verbose, 0, 4); size_t bufsiz = MIN(opts->bufsiz, INT_MAX); if (bufsiz == 0) bufsiz = BZIP2_BUFSIZ; Bzip2StmObj *hn = (Bzip2StmObj *) malloc(offsetof(Bzip2StmObj, buf[bufsiz])); if (!hn) { Bzip2_SetErrStat(BZ_MEM_ERROR); return NULL; } memset(&hn->bz2, 0, sizeof(hn->bz2)); hn->streamp = streamp; hn->ops = ops; hn->bufsiz = bufsiz; hn->compressing = FALSE; int err = BZ2_bzDecompressInit(&hn->bz2, verbosity, small); if (err != BZ_OK) { Bzip2_SetErrStat(err); free(hn); return NULL; } Bzip2_SetErrStat(BZ_OK); return hn; } Sint64 Bzip2_Read(Bzip2StmHn hn, void *buf, size_t nbytes) { if (hn->compressing) { Bzip2_SetErrStat(BZIP2_EBADSTREAM); return -1; } Bzip2Ret ret = BZ_OK; hn->bz2.next_out = (char *) buf; hn->bz2.avail_out = nbytes; while (hn->bz2.avail_out > 0) { if (hn->bz2.avail_in == 0) { Sint64 n = hn->ops->Read(hn->streamp, hn->buf, hn->bufsiz); if (n <= 0) { if (n < 0) ret = BZ_IO_ERROR; break; // EOF } hn->bz2.next_in = hn->buf; hn->bz2.avail_in = n; } int err = BZ2_bzDecompress(&hn->bz2); if (err == BZ_STREAM_END) break; if (err != BZ_OK) { ret = err; break; } } Bzip2_SetErrStat(ret); return nbytes - hn->bz2.avail_out; } Sint64 Bzip2_Write(Bzip2StmHn hn, const void *buf, size_t nbytes) { if (!hn->compressing) { Bzip2_SetErrStat(BZIP2_EBADSTREAM); return -1; } Bzip2Ret ret = BZ_OK; hn->bz2.next_in = (char *) buf; // safe hn->bz2.avail_in = nbytes; while (hn->bz2.avail_in > 0) { if (hn->bz2.avail_out == 0) { Sint64 n = Bzip2_FlushData(hn); if (n <= 0) { if (n < 0) ret = BZ_IO_ERROR; break; } } int err = BZ2_bzCompress(&hn->bz2, BZ_RUN); if (err != BZ_RUN_OK) { ret = err; break; } } Bzip2_SetErrStat(ret); return nbytes - hn->bz2.avail_in; } Judgement Bzip2_Finish(Bzip2StmHn hn) { if (!hn->compressing) { Bzip2_SetErrStat(BZIP2_EBADSTREAM); return NG; } int err; do { // Call BZ2_bzCompress() repeatedly with BZ_FINISH to consume all data err = BZ2_bzCompress(&hn->bz2, BZ_FINISH); if (err != BZ_STREAM_END && err != BZ_FINISH_OK) { Bzip2_SetErrStat(err); return NG; } if (Bzip2_FlushData(hn) == -1) { Bzip2_SetErrStat(BZ_IO_ERROR); return NG; } } while (err != BZ_STREAM_END); Bzip2_SetErrStat(BZ_OK); return OK; } void Bzip2_Close(Bzip2StmHn hn) { if (hn->ops->Close) hn->ops->Close(hn->streamp); if (hn->compressing) BZ2_bzCompressEnd(&hn->bz2); else BZ2_bzDecompressEnd(&hn->bz2); free(hn); }