blob: 65531de85e5359108fdfbde6d1c330ab8c066f40 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
#include "erofs/internal.h"
#include "erofs/print.h"
#include "erofs/config.h"
#include <libdeflate.h>
#include <stdlib.h>
#include "compressor.h"
#include "erofs/atomic.h"
struct erofs_libdeflate_context {
struct libdeflate_compressor *strm;
u8 *fitblk_buffer;
unsigned int fitblk_bufsiz;
size_t last_uncompressed_size;
};
static int libdeflate_compress_destsize(const struct erofs_compress *c,
const void *src, unsigned int *srcsize,
void *dst, unsigned int dstsize)
{
struct erofs_libdeflate_context *ctx = c->private_data;
size_t l = 0; /* largest input that fits so far */
size_t l_csize = 0;
size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */
size_t m;
if (dstsize + 9 > ctx->fitblk_bufsiz) {
u8 *buf = realloc(ctx->fitblk_buffer, dstsize + 9);
if (!buf)
return -ENOMEM;
ctx->fitblk_bufsiz = dstsize + 9;
ctx->fitblk_buffer = buf;
}
if (ctx->last_uncompressed_size)
m = ctx->last_uncompressed_size * 15 / 16;
else
m = dstsize * 4;
for (;;) {
size_t csize;
m = max(m, l + 1);
m = min(m, r - 1);
csize = libdeflate_deflate_compress(ctx->strm, src, m,
ctx->fitblk_buffer,
dstsize + 9);
/*printf("Tried %zu => %zu\n", m, csize);*/
if (csize > 0 && csize <= dstsize) {
/* Fits */
memcpy(dst, ctx->fitblk_buffer, csize);
l = m;
l_csize = csize;
if (r <= l + 1 || csize +
(22 - 2*(int)c->compression_level) >= dstsize)
break;
/*
* Estimate needed input prefix size based on current
* compression ratio.
*/
m = (dstsize * m) / csize;
} else {
/* Doesn't fit */
r = m;
if (r <= l + 1)
break;
m = (l + r) / 2;
}
}
/*
* Since generic EROFS on-disk compressed data will be filled with
* leading 0s (but no more than one block, 4KB for example, even the
* whole pcluster is 128KB) if not filled, it will be used to identify
* the actual compressed length as well without taking more reserved
* compressed bytes or some extra metadata to record this.
*
* DEFLATE streams can also be used in this way, if it starts from a
* non-last stored block, flag an unused bit instead to avoid the zero
* byte. It's still a valid one according to the DEFLATE specification.
*/
if (l_csize && !((u8 *)dst)[0])
((u8 *)dst)[0] = 1 << (2 + 1);
/*printf("Choosing %zu => %zu\n", l, l_csize);*/
*srcsize = l;
ctx->last_uncompressed_size = l;
return l_csize;
}
static int compressor_libdeflate_exit(struct erofs_compress *c)
{
struct erofs_libdeflate_context *ctx = c->private_data;
if (!ctx)
return -EINVAL;
libdeflate_free_compressor(ctx->strm);
free(ctx->fitblk_buffer);
free(ctx);
return 0;
}
static int compressor_libdeflate_init(struct erofs_compress *c)
{
static erofs_atomic_bool_t __warnonce;
struct erofs_libdeflate_context *ctx;
DBG_BUGON(c->private_data);
ctx = calloc(1, sizeof(struct erofs_libdeflate_context));
if (!ctx)
return -ENOMEM;
ctx->strm = libdeflate_alloc_compressor(c->compression_level);
if (!ctx->strm) {
free(ctx);
return -ENOMEM;
}
c->private_data = ctx;
if (!erofs_atomic_test_and_set(&__warnonce))
erofs_warn("EXPERIMENTAL libdeflate compressor in use. Use at your own risk!");
return 0;
}
static void compressor_libdeflate_reset(struct erofs_compress *c)
{
struct erofs_libdeflate_context *ctx = c->private_data;
ctx->last_uncompressed_size = 0;
}
static int erofs_compressor_libdeflate_setlevel(struct erofs_compress *c,
int compression_level)
{
if (compression_level < 0)
compression_level = erofs_compressor_libdeflate.default_level;
if (compression_level > erofs_compressor_libdeflate.best_level) {
erofs_err("invalid compression level %d", compression_level);
return -EINVAL;
}
c->compression_level = compression_level;
return 0;
}
const struct erofs_compressor erofs_compressor_libdeflate = {
.default_level = 1,
.best_level = 12,
.init = compressor_libdeflate_init,
.exit = compressor_libdeflate_exit,
.reset = compressor_libdeflate_reset,
.setlevel = erofs_compressor_libdeflate_setlevel,
.compress_destsize = libdeflate_compress_destsize,
};