File: compressor_libdeflate.c

package info (click to toggle)
erofs-utils 1.9-3
  • links: PTS
  • area: main
  • in suites: sid
  • size: 1,484 kB
  • sloc: ansic: 28,409; makefile: 203; sh: 33
file content (168 lines) | stat: -rw-r--r-- 4,530 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 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(const struct erofs_compress *c,
			       const void *src, unsigned int srcsize,
			       void *dst, unsigned int dstcapacity)
{
	struct erofs_libdeflate_context *ctx = c->private_data;
	size_t csize;

	csize = libdeflate_deflate_compress(ctx->strm, src, srcsize,
					    dst, dstcapacity);
	if (!csize)
		return -ENOSPC;
	/* See the comment in libdeflate_compress_destsize() */
	if (!((u8 *)dst)[0])
		((u8 *)dst)[0] = 1 << (2 + 1);
	return csize;
}

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)
{
	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;
	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 = libdeflate_compress,
	.compress_destsize = libdeflate_compress_destsize,
};