355 lines
7.3 KiB
C
355 lines
7.3 KiB
C
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||
|
|
||
|
/**
|
||
|
* \file cpr/xz.c
|
||
|
*
|
||
|
* Interfaces with `liblzma` and implements LZMA compressor/decompressor.
|
||
|
*
|
||
|
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
|
||
|
* \author Lorenzo Cogotti
|
||
|
*/
|
||
|
|
||
|
#include "cpr/xz.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <limits.h>
|
||
|
#include <lzma.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
// some equivalency we assume to be true
|
||
|
STATIC_ASSERT((lzma_check) XZCHK_NONE == LZMA_CHECK_NONE,
|
||
|
"Incorrect XZCHK_NONE definition");
|
||
|
STATIC_ASSERT((lzma_check) XZCHK_CRC32 == LZMA_CHECK_CRC32,
|
||
|
"Incorrect XZCHK_CRC32 definition");
|
||
|
STATIC_ASSERT((lzma_check) XZCHK_CRC64 == LZMA_CHECK_CRC64,
|
||
|
"Incorrect XZCHK_CRC64 definition");
|
||
|
STATIC_ASSERT((lzma_check) XZCHK_SHA256 == LZMA_CHECK_SHA256,
|
||
|
"Incorrect XZCHK_SHA256 definition");
|
||
|
|
||
|
typedef struct XzStmObj XzStmObj;
|
||
|
struct XzStmObj {
|
||
|
lzma_stream xz;
|
||
|
void *streamp;
|
||
|
const StmOps *ops;
|
||
|
unsigned bufsiz;
|
||
|
lzma_action action;
|
||
|
Boolean8 encoding;
|
||
|
Uint8 buf[FLEX_ARRAY]; // `bufsiz` bytes
|
||
|
};
|
||
|
|
||
|
#define XZ_BUFSIZ (32 * 1024)
|
||
|
|
||
|
#define XZ_EIO -2
|
||
|
#define XZ_EBADSTREAM -1
|
||
|
|
||
|
static Sint64 Xz_FlushData(XzStmHn hn)
|
||
|
{
|
||
|
size_t nbytes = hn->bufsiz - hn->xz.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->xz.next_out = hn->buf + left;
|
||
|
hn->xz.avail_out = hn->bufsiz - left;
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
static Sint64 Xz_StmRead(void *streamp, void *buf, size_t nbytes)
|
||
|
{
|
||
|
return Xz_Read((XzStmHn) streamp, buf, nbytes);
|
||
|
}
|
||
|
|
||
|
static Sint64 Xz_StmWrite(void *streamp, const void *buf, size_t nbytes)
|
||
|
{
|
||
|
return Xz_Write((XzStmHn) streamp, buf, nbytes);
|
||
|
}
|
||
|
|
||
|
static Sint64 Xz_StmTell(void *streamp)
|
||
|
{
|
||
|
XzStmHn hn = (XzStmHn) streamp;
|
||
|
return hn->encoding ? hn->xz.total_out : hn->xz.total_in;
|
||
|
}
|
||
|
|
||
|
static Judgement Xz_StmFinish(void *streamp)
|
||
|
{
|
||
|
return Xz_Finish((XzStmHn) streamp);
|
||
|
}
|
||
|
|
||
|
static void Xz_StmClose(void *streamp)
|
||
|
{
|
||
|
Xz_Close((XzStmHn) streamp);
|
||
|
}
|
||
|
|
||
|
static const StmOps xz_stmOps = {
|
||
|
Xz_StmRead,
|
||
|
Xz_StmWrite,
|
||
|
NULL,
|
||
|
Xz_StmTell,
|
||
|
Xz_StmFinish,
|
||
|
Xz_StmClose
|
||
|
};
|
||
|
static const StmOps xz_ncStmOps = {
|
||
|
Xz_StmRead,
|
||
|
Xz_StmWrite,
|
||
|
NULL,
|
||
|
Xz_StmTell,
|
||
|
Xz_StmFinish,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
const StmOps *const Xz_StmOps = &xz_stmOps;
|
||
|
const StmOps *const Xz_NcStmOps = &xz_ncStmOps;
|
||
|
|
||
|
static THREAD_LOCAL XzRet xz_errStat = LZMA_OK;
|
||
|
|
||
|
static void Xz_SetErrStat(XzRet ret)
|
||
|
{
|
||
|
xz_errStat = ret;
|
||
|
}
|
||
|
|
||
|
XzRet Xz_GetErrStat(void)
|
||
|
{
|
||
|
return xz_errStat;
|
||
|
}
|
||
|
|
||
|
const char *Xz_ErrorString(XzRet ret)
|
||
|
{
|
||
|
assert(ret != LZMA_NO_CHECK);
|
||
|
assert(ret != LZMA_GET_CHECK);
|
||
|
|
||
|
switch (ret) {
|
||
|
case XZ_EIO: return "I/O error";
|
||
|
case XZ_EBADSTREAM: return "Bad stream operation";
|
||
|
|
||
|
case LZMA_OK: return "Success";
|
||
|
case LZMA_UNSUPPORTED_CHECK: return "Cannot calculate the integrity check";
|
||
|
case LZMA_MEM_ERROR: return "Memory allocation failure";
|
||
|
case LZMA_MEMLIMIT_ERROR: return "Memory usage limit was reached";
|
||
|
case LZMA_FORMAT_ERROR: return "Unrecognized file format";
|
||
|
case LZMA_OPTIONS_ERROR: return "Invalid or unsupported options";
|
||
|
case LZMA_DATA_ERROR: return "Data is corrupt";
|
||
|
case LZMA_BUF_ERROR: return "No progress is possible";
|
||
|
case LZMA_PROG_ERROR: return "Programming error";
|
||
|
default: return "Unknown error";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
XzStmHn Xz_OpenCompress(void *streamp,
|
||
|
const StmOps *ops,
|
||
|
const XzEncOpts *opts)
|
||
|
{
|
||
|
const XzEncOpts default_opts = {
|
||
|
6,
|
||
|
FALSE,
|
||
|
XZCHK_CRC32,
|
||
|
XZ_BUFSIZ
|
||
|
};
|
||
|
|
||
|
if (!opts)
|
||
|
opts = &default_opts;
|
||
|
|
||
|
Uint32 compression = MIN(opts->compress, 9);
|
||
|
|
||
|
Uint32 presets = 0;
|
||
|
if (opts->extreme)
|
||
|
presets |= LZMA_PRESET_EXTREME;
|
||
|
|
||
|
size_t bufsiz = MIN(opts->bufsiz, INT_MAX);
|
||
|
if (bufsiz == 0)
|
||
|
bufsiz = XZ_BUFSIZ;
|
||
|
|
||
|
XzStmObj *hn = (XzStmObj *) malloc(offsetof(XzStmObj, buf[bufsiz]));
|
||
|
if (!hn) {
|
||
|
Xz_SetErrStat(LZMA_MEM_ERROR);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
memset(&hn->xz, 0, sizeof(hn->xz));
|
||
|
hn->streamp = streamp;
|
||
|
hn->ops = ops;
|
||
|
hn->bufsiz = bufsiz;
|
||
|
hn->action = LZMA_RUN; // ...actually ignored for encoding buffers
|
||
|
hn->encoding = TRUE;
|
||
|
|
||
|
lzma_ret err = lzma_easy_encoder(
|
||
|
&hn->xz,
|
||
|
compression | presets,
|
||
|
(lzma_check) opts->chk
|
||
|
);
|
||
|
|
||
|
if (err != LZMA_OK) {
|
||
|
Xz_SetErrStat(err);
|
||
|
free(hn);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
hn->xz.next_out = hn->buf;
|
||
|
hn->xz.avail_out = hn->bufsiz;
|
||
|
Xz_SetErrStat(LZMA_OK);
|
||
|
return hn;
|
||
|
}
|
||
|
|
||
|
XzStmHn Xz_OpenDecompress(void *streamp,
|
||
|
const StmOps *ops,
|
||
|
const XzDecOpts *opts)
|
||
|
{
|
||
|
const XzDecOpts default_opts = {
|
||
|
U64_C(0xffffffffffffffff),
|
||
|
FALSE,
|
||
|
FALSE,
|
||
|
XZ_BUFSIZ
|
||
|
};
|
||
|
|
||
|
if (!opts)
|
||
|
opts = &default_opts;
|
||
|
|
||
|
Uint32 mask = LZMA_CONCATENATED;
|
||
|
if (opts->no_concat)
|
||
|
mask &= ~LZMA_CONCATENATED;
|
||
|
|
||
|
#ifdef LZMA_IGNORE_CHECK
|
||
|
if (opts->no_chk)
|
||
|
mask |= LZMA_IGNORE_CHECK;
|
||
|
#endif
|
||
|
|
||
|
size_t bufsiz = MIN(opts->bufsiz, INT_MAX);
|
||
|
if (bufsiz == 0)
|
||
|
bufsiz = XZ_BUFSIZ;
|
||
|
|
||
|
XzStmObj *hn = (XzStmObj *) malloc(offsetof(XzStmObj, buf[bufsiz]));
|
||
|
if (!hn) {
|
||
|
Xz_SetErrStat(LZMA_MEM_ERROR);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
memset(&hn->xz, 0, sizeof(hn->xz));
|
||
|
hn->streamp = streamp;
|
||
|
hn->ops = ops;
|
||
|
hn->action = LZMA_RUN; // used to force LZMA_FINISH on EOF
|
||
|
hn->bufsiz = bufsiz;
|
||
|
hn->encoding = FALSE;
|
||
|
|
||
|
lzma_ret err = lzma_auto_decoder(&hn->xz, opts->memlimit, mask);
|
||
|
if (err != LZMA_OK) {
|
||
|
Xz_SetErrStat(err);
|
||
|
free(hn);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
Xz_SetErrStat(LZMA_OK);
|
||
|
return hn;
|
||
|
}
|
||
|
|
||
|
Sint64 Xz_Read(XzStmHn hn, void *buf, size_t nbytes)
|
||
|
{
|
||
|
if (hn->encoding) {
|
||
|
Xz_SetErrStat(XZ_EBADSTREAM);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
XzRet ret = LZMA_OK;
|
||
|
|
||
|
hn->xz.next_out = (Uint8 *) buf;
|
||
|
hn->xz.avail_out = nbytes;
|
||
|
while (hn->xz.avail_out > 0) {
|
||
|
if (hn->xz.avail_in == 0) {
|
||
|
Sint64 n = hn->ops->Read(hn->streamp, hn->buf, hn->bufsiz);
|
||
|
if (n <= 0) {
|
||
|
if (n < 0) {
|
||
|
ret = XZ_EIO;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
hn->action = LZMA_FINISH;
|
||
|
}
|
||
|
|
||
|
hn->xz.next_in = hn->buf;
|
||
|
hn->xz.avail_in = n;
|
||
|
}
|
||
|
|
||
|
lzma_ret err = lzma_code(&hn->xz, hn->action);
|
||
|
if (err == LZMA_STREAM_END)
|
||
|
break; // NOTE: shouldn't happen for a stream to end before EOF...
|
||
|
if (err != LZMA_OK) {
|
||
|
ret = err;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Xz_SetErrStat(ret);
|
||
|
return nbytes - hn->xz.avail_out;
|
||
|
}
|
||
|
|
||
|
Sint64 Xz_Write(XzStmHn hn, const void *buf, size_t nbytes)
|
||
|
{
|
||
|
if (!hn->encoding) {
|
||
|
Xz_SetErrStat(XZ_EBADSTREAM);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
XzRet ret = LZMA_OK;
|
||
|
|
||
|
hn->xz.next_in = (Uint8 *) buf;
|
||
|
hn->xz.avail_in = nbytes;
|
||
|
while (hn->xz.avail_in > 0) {
|
||
|
if (hn->xz.avail_out == 0) {
|
||
|
Sint64 n = Xz_FlushData(hn);
|
||
|
if (n < -1) ret = XZ_EIO;
|
||
|
|
||
|
break; // short-write
|
||
|
}
|
||
|
|
||
|
// Disregard `hn->action` on write, we will flush upon Xz_Finish()
|
||
|
lzma_ret err = lzma_code(&hn->xz, LZMA_RUN);
|
||
|
if (err != LZMA_OK) {
|
||
|
ret = err;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Xz_SetErrStat(ret);
|
||
|
return nbytes - hn->xz.avail_in;
|
||
|
}
|
||
|
|
||
|
Judgement Xz_Finish(XzStmHn hn)
|
||
|
{
|
||
|
if (!hn->encoding) {
|
||
|
Xz_SetErrStat(XZ_EBADSTREAM);
|
||
|
return NG;
|
||
|
}
|
||
|
|
||
|
lzma_ret err;
|
||
|
do {
|
||
|
// Flush LZMA to disk
|
||
|
err = lzma_code(&hn->xz, LZMA_FINISH);
|
||
|
if (err != LZMA_STREAM_END && err != LZMA_OK) {
|
||
|
Xz_SetErrStat(err);
|
||
|
return NG;
|
||
|
}
|
||
|
if (Xz_FlushData(hn) == -1) {
|
||
|
Xz_SetErrStat(XZ_EIO);
|
||
|
return NG;
|
||
|
}
|
||
|
} while (err != LZMA_STREAM_END);
|
||
|
|
||
|
if (hn->xz.avail_out != hn->bufsiz) {
|
||
|
Xz_SetErrStat(LZMA_BUF_ERROR);
|
||
|
return NG;
|
||
|
}
|
||
|
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
void Xz_Close(XzStmHn hn)
|
||
|
{
|
||
|
if (hn->ops->Close)
|
||
|
hn->ops->Close(hn->streamp);
|
||
|
|
||
|
lzma_end(&hn->xz);
|
||
|
free(hn);
|
||
|
}
|