]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
e4e61fdb | 2 | |
0c15577a | 3 | #include <stdio.h> |
4b5bc539 | 4 | #include <sys/mman.h> |
ca78ad1d | 5 | #include <sys/stat.h> |
07630cea | 6 | #include <unistd.h> |
d89c8fdf | 7 | |
91d6f1ee | 8 | #if HAVE_LZ4 |
2782dae5 | 9 | #include <lz4.h> |
91d6f1ee | 10 | #include <lz4hc.h> |
2782dae5 | 11 | #include <lz4frame.h> |
91d6f1ee AC |
12 | #endif |
13 | ||
349cc4a5 | 14 | #if HAVE_XZ |
3ffd4af2 | 15 | #include <lzma.h> |
d89c8fdf ZJS |
16 | #endif |
17 | ||
ef5924aa NL |
18 | #if HAVE_ZSTD |
19 | #include <zstd.h> | |
20 | #include <zstd_errors.h> | |
21 | #endif | |
22 | ||
b5efdb8a | 23 | #include "alloc-util.h" |
1e4e5572 | 24 | #include "bitfield.h" |
e4e61fdb | 25 | #include "compress.h" |
0c15577a | 26 | #include "dlfcn-util.h" |
1a823cde | 27 | #include "fileio.h" |
c004493c | 28 | #include "io-util.h" |
93a1f792 | 29 | #include "log.h" |
8b43440b | 30 | #include "string-table.h" |
07630cea | 31 | #include "string-util.h" |
4094c4bf | 32 | #include "unaligned.h" |
d89c8fdf | 33 | |
349cc4a5 | 34 | #if HAVE_LZ4 |
3fc72d54 MC |
35 | static void *lz4_dl = NULL; |
36 | ||
5c672e90 ZJS |
37 | static DLSYM_PROTOTYPE(LZ4F_compressBegin) = NULL; |
38 | static DLSYM_PROTOTYPE(LZ4F_compressBound) = NULL; | |
39 | static DLSYM_PROTOTYPE(LZ4F_compressEnd) = NULL; | |
40 | static DLSYM_PROTOTYPE(LZ4F_compressUpdate) = NULL; | |
41 | static DLSYM_PROTOTYPE(LZ4F_createCompressionContext) = NULL; | |
42 | static DLSYM_PROTOTYPE(LZ4F_createDecompressionContext) = NULL; | |
43 | static DLSYM_PROTOTYPE(LZ4F_decompress) = NULL; | |
44 | static DLSYM_PROTOTYPE(LZ4F_freeCompressionContext) = NULL; | |
45 | static DLSYM_PROTOTYPE(LZ4F_freeDecompressionContext) = NULL; | |
46 | static DLSYM_PROTOTYPE(LZ4F_isError) = NULL; | |
2782dae5 DDM |
47 | static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL; |
48 | /* These are used in test-compress.c so we don't make them static. */ | |
5c672e90 ZJS |
49 | DLSYM_PROTOTYPE(LZ4_compress_default) = NULL; |
50 | DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL; | |
51 | DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL; | |
52 | DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL; | |
3fc72d54 MC |
53 | |
54 | DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(LZ4F_compressionContext_t, sym_LZ4F_freeCompressionContext, NULL); | |
55 | DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(LZ4F_decompressionContext_t, sym_LZ4F_freeDecompressionContext, NULL); | |
4b5bc539 ZJS |
56 | #endif |
57 | ||
ef5924aa | 58 | #if HAVE_ZSTD |
3fc72d54 MC |
59 | static void *zstd_dl = NULL; |
60 | ||
5c672e90 ZJS |
61 | static DLSYM_PROTOTYPE(ZSTD_CCtx_setParameter) = NULL; |
62 | static DLSYM_PROTOTYPE(ZSTD_compress) = NULL; | |
63 | static DLSYM_PROTOTYPE(ZSTD_compressStream2) = NULL; | |
64 | static DLSYM_PROTOTYPE(ZSTD_createCCtx) = NULL; | |
65 | static DLSYM_PROTOTYPE(ZSTD_createDCtx) = NULL; | |
66 | static DLSYM_PROTOTYPE(ZSTD_CStreamInSize) = NULL; | |
67 | static DLSYM_PROTOTYPE(ZSTD_CStreamOutSize) = NULL; | |
68 | static DLSYM_PROTOTYPE(ZSTD_decompressStream) = NULL; | |
69 | static DLSYM_PROTOTYPE(ZSTD_DStreamInSize) = NULL; | |
70 | static DLSYM_PROTOTYPE(ZSTD_DStreamOutSize) = NULL; | |
71 | static DLSYM_PROTOTYPE(ZSTD_freeCCtx) = NULL; | |
72 | static DLSYM_PROTOTYPE(ZSTD_freeDCtx) = NULL; | |
73 | static DLSYM_PROTOTYPE(ZSTD_getErrorCode) = NULL; | |
74 | static DLSYM_PROTOTYPE(ZSTD_getErrorName) = NULL; | |
75 | static DLSYM_PROTOTYPE(ZSTD_getFrameContentSize) = NULL; | |
76 | static DLSYM_PROTOTYPE(ZSTD_isError) = NULL; | |
3fc72d54 MC |
77 | |
78 | DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ZSTD_CCtx*, sym_ZSTD_freeCCtx, NULL); | |
79 | DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ZSTD_DCtx*, sym_ZSTD_freeDCtx, NULL); | |
ef5924aa NL |
80 | |
81 | static int zstd_ret_to_errno(size_t ret) { | |
3fc72d54 | 82 | switch (sym_ZSTD_getErrorCode(ret)) { |
ef5924aa NL |
83 | case ZSTD_error_dstSize_tooSmall: |
84 | return -ENOBUFS; | |
85 | case ZSTD_error_memory_allocation: | |
86 | return -ENOMEM; | |
87 | default: | |
88 | return -EBADMSG; | |
89 | } | |
90 | } | |
91 | #endif | |
92 | ||
3fc72d54 MC |
93 | #if HAVE_XZ |
94 | static void *lzma_dl = NULL; | |
95 | ||
5c672e90 ZJS |
96 | static DLSYM_PROTOTYPE(lzma_code) = NULL; |
97 | static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL; | |
98 | static DLSYM_PROTOTYPE(lzma_end) = NULL; | |
99 | static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL; | |
100 | static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL; | |
91d6f1ee | 101 | static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL; |
3fc72d54 MC |
102 | |
103 | /* We can't just do _cleanup_(sym_lzma_end) because a compiler bug makes | |
104 | * this fail with: | |
105 | * ../src/basic/compress.c: In function ‘decompress_blob_xz’: | |
106 | * ../src/basic/compress.c:304:9: error: cleanup argument not a function | |
107 | * 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT; | |
108 | * | ^~~~~~~~~ | |
109 | */ | |
110 | static inline void lzma_end_wrapper(lzma_stream *ls) { | |
111 | sym_lzma_end(ls); | |
112 | } | |
113 | #endif | |
114 | ||
d89c8fdf ZJS |
115 | #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) |
116 | ||
acc50c92 | 117 | static const char* const compression_table[_COMPRESSION_MAX] = { |
4d698d12 | 118 | [COMPRESSION_NONE] = "NONE", |
acc50c92 LP |
119 | [COMPRESSION_XZ] = "XZ", |
120 | [COMPRESSION_LZ4] = "LZ4", | |
121 | [COMPRESSION_ZSTD] = "ZSTD", | |
d89c8fdf | 122 | }; |
e4e61fdb | 123 | |
cfaf7800 AC |
124 | static const char* const compression_lowercase_table[_COMPRESSION_MAX] = { |
125 | [COMPRESSION_NONE] = "none", | |
126 | [COMPRESSION_XZ] = "xz", | |
127 | [COMPRESSION_LZ4] = "lz4", | |
128 | [COMPRESSION_ZSTD] = "zstd", | |
129 | }; | |
130 | ||
acc50c92 | 131 | DEFINE_STRING_TABLE_LOOKUP(compression, Compression); |
cfaf7800 | 132 | DEFINE_STRING_TABLE_LOOKUP(compression_lowercase, Compression); |
d89c8fdf | 133 | |
83f3d73d YW |
134 | bool compression_supported(Compression c) { |
135 | static const unsigned supported = | |
136 | (1U << COMPRESSION_NONE) | | |
137 | (1U << COMPRESSION_XZ) * HAVE_XZ | | |
138 | (1U << COMPRESSION_LZ4) * HAVE_LZ4 | | |
139 | (1U << COMPRESSION_ZSTD) * HAVE_ZSTD; | |
140 | ||
1e4e5572 MY |
141 | assert(c >= 0); |
142 | assert(c < _COMPRESSION_MAX); | |
143 | ||
144 | return BIT_SET(supported, c); | |
83f3d73d YW |
145 | } |
146 | ||
3fc72d54 MC |
147 | #if HAVE_XZ |
148 | int dlopen_lzma(void) { | |
cd7c2077 LP |
149 | ELF_NOTE_DLOPEN("lzma", |
150 | "Support lzma compression in journal and coredump files", | |
151 | COMPRESSION_PRIORITY_XZ, | |
152 | "liblzma.so.5"); | |
153 | ||
3fc72d54 MC |
154 | return dlopen_many_sym_or_warn( |
155 | &lzma_dl, | |
156 | "liblzma.so.5", LOG_DEBUG, | |
157 | DLSYM_ARG(lzma_code), | |
158 | DLSYM_ARG(lzma_easy_encoder), | |
159 | DLSYM_ARG(lzma_end), | |
160 | DLSYM_ARG(lzma_stream_buffer_encode), | |
91d6f1ee | 161 | DLSYM_ARG(lzma_lzma_preset), |
3fc72d54 MC |
162 | DLSYM_ARG(lzma_stream_decoder)); |
163 | } | |
164 | #endif | |
165 | ||
5d6f46b6 | 166 | int compress_blob_xz(const void *src, uint64_t src_size, |
91d6f1ee | 167 | void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { |
3fc72d54 MC |
168 | |
169 | assert(src); | |
170 | assert(src_size > 0); | |
171 | assert(dst); | |
172 | assert(dst_alloc_size > 0); | |
173 | assert(dst_size); | |
174 | ||
349cc4a5 | 175 | #if HAVE_XZ |
91d6f1ee | 176 | lzma_options_lzma opt = { |
1930eed2 | 177 | 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, |
0240c603 LP |
178 | LZMA_PB_DEFAULT, LZMA_MODE_FAST, 128, LZMA_MF_HC3, 4 |
179 | }; | |
91d6f1ee AC |
180 | lzma_filter filters[] = { |
181 | { LZMA_FILTER_LZMA2, &opt }, | |
0240c603 | 182 | { LZMA_VLI_UNKNOWN, NULL } |
1930eed2 | 183 | }; |
e4e61fdb | 184 | lzma_ret ret; |
76cc0bf6 | 185 | size_t out_pos = 0; |
3fc72d54 | 186 | int r; |
e4e61fdb | 187 | |
3fc72d54 MC |
188 | r = dlopen_lzma(); |
189 | if (r < 0) | |
190 | return r; | |
e4e61fdb | 191 | |
91d6f1ee AC |
192 | if (level >= 0) { |
193 | r = sym_lzma_lzma_preset(&opt, (uint32_t) level); | |
194 | if (r < 0) | |
195 | return r; | |
196 | } | |
197 | ||
d89c8fdf | 198 | /* Returns < 0 if we couldn't compress the data or the |
e4e61fdb LP |
199 | * compressed result is longer than the original */ |
200 | ||
1930eed2 JS |
201 | if (src_size < 80) |
202 | return -ENOBUFS; | |
203 | ||
91d6f1ee | 204 | ret = sym_lzma_stream_buffer_encode(filters, LZMA_CHECK_NONE, NULL, |
5d6f46b6 | 205 | src, src_size, dst, &out_pos, dst_alloc_size); |
e4e61fdb | 206 | if (ret != LZMA_OK) |
d89c8fdf | 207 | return -ENOBUFS; |
e4e61fdb | 208 | |
76cc0bf6 | 209 | *dst_size = out_pos; |
bfeaa62d | 210 | return 0; |
d89c8fdf ZJS |
211 | #else |
212 | return -EPROTONOSUPPORT; | |
213 | #endif | |
e4e61fdb LP |
214 | } |
215 | ||
3fc72d54 MC |
216 | #if HAVE_LZ4 |
217 | int dlopen_lz4(void) { | |
cd7c2077 LP |
218 | ELF_NOTE_DLOPEN("lz4", |
219 | "Support lz4 compression in journal and coredump files", | |
220 | COMPRESSION_PRIORITY_LZ4, | |
221 | "liblz4.so.1"); | |
222 | ||
3fc72d54 MC |
223 | return dlopen_many_sym_or_warn( |
224 | &lz4_dl, | |
225 | "liblz4.so.1", LOG_DEBUG, | |
226 | DLSYM_ARG(LZ4F_compressBegin), | |
227 | DLSYM_ARG(LZ4F_compressBound), | |
228 | DLSYM_ARG(LZ4F_compressEnd), | |
229 | DLSYM_ARG(LZ4F_compressUpdate), | |
230 | DLSYM_ARG(LZ4F_createCompressionContext), | |
231 | DLSYM_ARG(LZ4F_createDecompressionContext), | |
232 | DLSYM_ARG(LZ4F_decompress), | |
233 | DLSYM_ARG(LZ4F_freeCompressionContext), | |
234 | DLSYM_ARG(LZ4F_freeDecompressionContext), | |
235 | DLSYM_ARG(LZ4F_isError), | |
236 | DLSYM_ARG(LZ4_compress_default), | |
91d6f1ee | 237 | DLSYM_ARG(LZ4_compress_HC), |
3fc72d54 MC |
238 | DLSYM_ARG(LZ4_decompress_safe), |
239 | DLSYM_ARG(LZ4_decompress_safe_partial), | |
240 | DLSYM_ARG(LZ4_versionNumber)); | |
241 | } | |
242 | #endif | |
243 | ||
5d6f46b6 | 244 | int compress_blob_lz4(const void *src, uint64_t src_size, |
91d6f1ee | 245 | void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { |
d89c8fdf ZJS |
246 | |
247 | assert(src); | |
248 | assert(src_size > 0); | |
249 | assert(dst); | |
5d6f46b6 | 250 | assert(dst_alloc_size > 0); |
d89c8fdf ZJS |
251 | assert(dst_size); |
252 | ||
3fc72d54 MC |
253 | #if HAVE_LZ4 |
254 | int r; | |
255 | ||
256 | r = dlopen_lz4(); | |
257 | if (r < 0) | |
258 | return r; | |
d89c8fdf ZJS |
259 | /* Returns < 0 if we couldn't compress the data or the |
260 | * compressed result is longer than the original */ | |
261 | ||
262 | if (src_size < 9) | |
263 | return -ENOBUFS; | |
e4e61fdb | 264 | |
91d6f1ee AC |
265 | if (level <= 0) |
266 | r = sym_LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); | |
267 | else | |
268 | r = sym_LZ4_compress_HC(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8, level); | |
d89c8fdf ZJS |
269 | if (r <= 0) |
270 | return -ENOBUFS; | |
271 | ||
4094c4bf | 272 | unaligned_write_le64(dst, src_size); |
d89c8fdf ZJS |
273 | *dst_size = r + 8; |
274 | ||
bfeaa62d | 275 | return 0; |
d89c8fdf ZJS |
276 | #else |
277 | return -EPROTONOSUPPORT; | |
278 | #endif | |
279 | } | |
280 | ||
3fc72d54 MC |
281 | #if HAVE_ZSTD |
282 | int dlopen_zstd(void) { | |
cd7c2077 LP |
283 | ELF_NOTE_DLOPEN("zstd", |
284 | "Support zstd compression in journal and coredump files", | |
285 | COMPRESSION_PRIORITY_ZSTD, | |
286 | "libzstd.so.1"); | |
287 | ||
3fc72d54 MC |
288 | return dlopen_many_sym_or_warn( |
289 | &zstd_dl, | |
290 | "libzstd.so.1", LOG_DEBUG, | |
291 | DLSYM_ARG(ZSTD_getErrorCode), | |
292 | DLSYM_ARG(ZSTD_compress), | |
293 | DLSYM_ARG(ZSTD_getFrameContentSize), | |
294 | DLSYM_ARG(ZSTD_decompressStream), | |
295 | DLSYM_ARG(ZSTD_getErrorName), | |
296 | DLSYM_ARG(ZSTD_DStreamOutSize), | |
297 | DLSYM_ARG(ZSTD_CStreamInSize), | |
298 | DLSYM_ARG(ZSTD_CStreamOutSize), | |
299 | DLSYM_ARG(ZSTD_CCtx_setParameter), | |
300 | DLSYM_ARG(ZSTD_compressStream2), | |
301 | DLSYM_ARG(ZSTD_DStreamInSize), | |
302 | DLSYM_ARG(ZSTD_freeCCtx), | |
303 | DLSYM_ARG(ZSTD_freeDCtx), | |
304 | DLSYM_ARG(ZSTD_isError), | |
305 | DLSYM_ARG(ZSTD_createDCtx), | |
306 | DLSYM_ARG(ZSTD_createCCtx)); | |
307 | } | |
308 | #endif | |
309 | ||
8653185a LP |
310 | int compress_blob_zstd( |
311 | const void *src, uint64_t src_size, | |
91d6f1ee | 312 | void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { |
8653185a LP |
313 | |
314 | assert(src); | |
315 | assert(src_size > 0); | |
316 | assert(dst); | |
317 | assert(dst_alloc_size > 0); | |
318 | assert(dst_size); | |
319 | ||
3fc72d54 MC |
320 | #if HAVE_ZSTD |
321 | size_t k; | |
322 | int r; | |
323 | ||
324 | r = dlopen_zstd(); | |
325 | if (r < 0) | |
326 | return r; | |
327 | ||
91d6f1ee | 328 | k = sym_ZSTD_compress(dst, dst_alloc_size, src, src_size, level < 0 ? 0 : level); |
3fc72d54 | 329 | if (sym_ZSTD_isError(k)) |
8653185a LP |
330 | return zstd_ret_to_errno(k); |
331 | ||
332 | *dst_size = k; | |
bfeaa62d | 333 | return 0; |
8653185a LP |
334 | #else |
335 | return -EPROTONOSUPPORT; | |
336 | #endif | |
337 | } | |
338 | ||
319a4f4b LP |
339 | int decompress_blob_xz( |
340 | const void *src, | |
341 | uint64_t src_size, | |
342 | void **dst, | |
343 | size_t* dst_size, | |
344 | size_t dst_max) { | |
d89c8fdf | 345 | |
e4e61fdb LP |
346 | assert(src); |
347 | assert(src_size > 0); | |
348 | assert(dst); | |
e4e61fdb | 349 | assert(dst_size); |
e4e61fdb | 350 | |
3fc72d54 MC |
351 | #if HAVE_XZ |
352 | _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; | |
353 | lzma_ret ret; | |
354 | size_t space; | |
355 | int r; | |
356 | ||
357 | r = dlopen_lzma(); | |
358 | if (r < 0) | |
359 | return r; | |
360 | ||
361 | ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); | |
e4e61fdb | 362 | if (ret != LZMA_OK) |
d89c8fdf | 363 | return -ENOMEM; |
e4e61fdb | 364 | |
f5fbe71d | 365 | space = MIN(src_size * 2, dst_max ?: SIZE_MAX); |
319a4f4b | 366 | if (!greedy_realloc(dst, space, 1)) |
01c3322e | 367 | return -ENOMEM; |
e4e61fdb LP |
368 | |
369 | s.next_in = src; | |
370 | s.avail_in = src_size; | |
371 | ||
372 | s.next_out = *dst; | |
93b73b06 | 373 | s.avail_out = space; |
e4e61fdb LP |
374 | |
375 | for (;;) { | |
fa1c4b51 | 376 | size_t used; |
e4e61fdb | 377 | |
3fc72d54 | 378 | ret = sym_lzma_code(&s, LZMA_FINISH); |
e4e61fdb LP |
379 | if (ret == LZMA_STREAM_END) |
380 | break; | |
e74c1e1c | 381 | if (ret != LZMA_OK) |
d89c8fdf | 382 | return -ENOMEM; |
e4e61fdb | 383 | |
93b73b06 LP |
384 | if (dst_max > 0 && (space - s.avail_out) >= dst_max) |
385 | break; | |
e74c1e1c | 386 | if (dst_max > 0 && space == dst_max) |
d89c8fdf | 387 | return -ENOBUFS; |
93b73b06 | 388 | |
5e592c66 | 389 | used = space - s.avail_out; |
f5fbe71d | 390 | space = MIN(2 * space, dst_max ?: SIZE_MAX); |
319a4f4b | 391 | if (!greedy_realloc(dst, space, 1)) |
01c3322e | 392 | return -ENOMEM; |
e4e61fdb | 393 | |
5e592c66 | 394 | s.avail_out = space - used; |
8e170d29 | 395 | s.next_out = *(uint8_t**)dst + used; |
e4e61fdb LP |
396 | } |
397 | ||
93b73b06 | 398 | *dst_size = space - s.avail_out; |
d89c8fdf ZJS |
399 | return 0; |
400 | #else | |
401 | return -EPROTONOSUPPORT; | |
402 | #endif | |
e4e61fdb LP |
403 | } |
404 | ||
319a4f4b LP |
405 | int decompress_blob_lz4( |
406 | const void *src, | |
407 | uint64_t src_size, | |
408 | void **dst, | |
409 | size_t* dst_size, | |
410 | size_t dst_max) { | |
d89c8fdf | 411 | |
d89c8fdf ZJS |
412 | assert(src); |
413 | assert(src_size > 0); | |
414 | assert(dst); | |
d89c8fdf | 415 | assert(dst_size); |
d89c8fdf | 416 | |
3fc72d54 MC |
417 | #if HAVE_LZ4 |
418 | char* out; | |
419 | int r, size; /* LZ4 uses int for size */ | |
420 | ||
421 | r = dlopen_lz4(); | |
422 | if (r < 0) | |
423 | return r; | |
424 | ||
d89c8fdf ZJS |
425 | if (src_size <= 8) |
426 | return -EBADMSG; | |
427 | ||
4094c4bf LP |
428 | size = unaligned_read_le64(src); |
429 | if (size < 0 || (unsigned) size != unaligned_read_le64(src)) | |
fa1c4b51 | 430 | return -EFBIG; |
319a4f4b LP |
431 | out = greedy_realloc(dst, size, 1); |
432 | if (!out) | |
433 | return -ENOMEM; | |
d89c8fdf | 434 | |
3fc72d54 | 435 | r = sym_LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size); |
fa1c4b51 | 436 | if (r < 0 || r != size) |
d89c8fdf ZJS |
437 | return -EBADMSG; |
438 | ||
439 | *dst_size = size; | |
440 | return 0; | |
441 | #else | |
442 | return -EPROTONOSUPPORT; | |
443 | #endif | |
444 | } | |
445 | ||
8653185a | 446 | int decompress_blob_zstd( |
319a4f4b LP |
447 | const void *src, |
448 | uint64_t src_size, | |
449 | void **dst, | |
450 | size_t *dst_size, | |
451 | size_t dst_max) { | |
8653185a | 452 | |
8653185a LP |
453 | assert(src); |
454 | assert(src_size > 0); | |
455 | assert(dst); | |
8653185a | 456 | assert(dst_size); |
8653185a | 457 | |
3fc72d54 MC |
458 | #if HAVE_ZSTD |
459 | uint64_t size; | |
460 | int r; | |
461 | ||
462 | r = dlopen_zstd(); | |
463 | if (r < 0) | |
464 | return r; | |
465 | ||
466 | size = sym_ZSTD_getFrameContentSize(src, src_size); | |
a2415327 ZJS |
467 | if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN)) |
468 | return -EBADMSG; | |
8653185a | 469 | |
a2415327 ZJS |
470 | if (dst_max > 0 && size > dst_max) |
471 | size = dst_max; | |
472 | if (size > SIZE_MAX) | |
473 | return -E2BIG; | |
8653185a | 474 | |
3fc72d54 | 475 | if (!(greedy_realloc(dst, MAX(sym_ZSTD_DStreamOutSize(), size), 1))) |
a2415327 | 476 | return -ENOMEM; |
8653185a | 477 | |
3fc72d54 | 478 | _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx(); |
a2415327 ZJS |
479 | if (!dctx) |
480 | return -ENOMEM; | |
8653185a | 481 | |
a2415327 ZJS |
482 | ZSTD_inBuffer input = { |
483 | .src = src, | |
484 | .size = src_size, | |
485 | }; | |
486 | ZSTD_outBuffer output = { | |
487 | .dst = *dst, | |
319a4f4b | 488 | .size = MALLOC_SIZEOF_SAFE(*dst), |
a2415327 | 489 | }; |
8653185a | 490 | |
3fc72d54 | 491 | size_t k = sym_ZSTD_decompressStream(dctx, &output, &input); |
559795fa LP |
492 | if (sym_ZSTD_isError(k)) |
493 | return log_debug_errno(zstd_ret_to_errno(k), "ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); | |
494 | if (output.pos < size) | |
495 | return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream."); | |
a2415327 ZJS |
496 | |
497 | *dst_size = size; | |
498 | return 0; | |
8653185a LP |
499 | #else |
500 | return -EPROTONOSUPPORT; | |
501 | #endif | |
502 | } | |
503 | ||
504 | int decompress_blob( | |
acc50c92 | 505 | Compression compression, |
319a4f4b LP |
506 | const void *src, |
507 | uint64_t src_size, | |
508 | void **dst, | |
509 | size_t* dst_size, | |
510 | size_t dst_max) { | |
8653185a | 511 | |
e74c1e1c LP |
512 | switch (compression) { |
513 | case COMPRESSION_XZ: | |
8653185a LP |
514 | return decompress_blob_xz( |
515 | src, src_size, | |
319a4f4b | 516 | dst, dst_size, dst_max); |
e74c1e1c | 517 | case COMPRESSION_LZ4: |
8653185a LP |
518 | return decompress_blob_lz4( |
519 | src, src_size, | |
319a4f4b | 520 | dst, dst_size, dst_max); |
e74c1e1c | 521 | case COMPRESSION_ZSTD: |
8653185a LP |
522 | return decompress_blob_zstd( |
523 | src, src_size, | |
319a4f4b | 524 | dst, dst_size, dst_max); |
e74c1e1c | 525 | default: |
b4a11ca3 | 526 | return -EPROTONOSUPPORT; |
e74c1e1c | 527 | } |
d89c8fdf ZJS |
528 | } |
529 | ||
319a4f4b LP |
530 | int decompress_startswith_xz( |
531 | const void *src, | |
532 | uint64_t src_size, | |
533 | void **buffer, | |
534 | const void *prefix, | |
535 | size_t prefix_len, | |
536 | uint8_t extra) { | |
d89c8fdf | 537 | |
319a4f4b LP |
538 | /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to |
539 | * follow the prefix */ | |
e4e61fdb LP |
540 | |
541 | assert(src); | |
542 | assert(src_size > 0); | |
543 | assert(buffer); | |
e4e61fdb | 544 | assert(prefix); |
e4e61fdb | 545 | |
3fc72d54 MC |
546 | #if HAVE_XZ |
547 | _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; | |
548 | size_t allocated; | |
549 | lzma_ret ret; | |
550 | int r; | |
551 | ||
552 | r = dlopen_lzma(); | |
553 | if (r < 0) | |
554 | return r; | |
555 | ||
556 | ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); | |
e4e61fdb | 557 | if (ret != LZMA_OK) |
d89c8fdf | 558 | return -EBADMSG; |
e4e61fdb | 559 | |
319a4f4b | 560 | if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) |
d89c8fdf | 561 | return -ENOMEM; |
e4e61fdb | 562 | |
319a4f4b LP |
563 | allocated = MALLOC_SIZEOF_SAFE(*buffer); |
564 | ||
e4e61fdb LP |
565 | s.next_in = src; |
566 | s.avail_in = src_size; | |
567 | ||
568 | s.next_out = *buffer; | |
319a4f4b | 569 | s.avail_out = allocated; |
e4e61fdb LP |
570 | |
571 | for (;;) { | |
3fc72d54 | 572 | ret = sym_lzma_code(&s, LZMA_FINISH); |
e4e61fdb | 573 | |
4c701096 | 574 | if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) |
d89c8fdf | 575 | return -EBADMSG; |
e4e61fdb | 576 | |
319a4f4b | 577 | if (allocated - s.avail_out >= prefix_len + 1) |
5e592c66 ZJS |
578 | return memcmp(*buffer, prefix, prefix_len) == 0 && |
579 | ((const uint8_t*) *buffer)[prefix_len] == extra; | |
e4e61fdb LP |
580 | |
581 | if (ret == LZMA_STREAM_END) | |
d89c8fdf | 582 | return 0; |
e4e61fdb | 583 | |
319a4f4b | 584 | s.avail_out += allocated; |
e4e61fdb | 585 | |
319a4f4b | 586 | if (!(greedy_realloc(buffer, allocated * 2, 1))) |
d89c8fdf | 587 | return -ENOMEM; |
e4e61fdb | 588 | |
319a4f4b LP |
589 | allocated = MALLOC_SIZEOF_SAFE(*buffer); |
590 | s.next_out = *(uint8_t**)buffer + allocated - s.avail_out; | |
5e592c66 | 591 | } |
d89c8fdf ZJS |
592 | |
593 | #else | |
594 | return -EPROTONOSUPPORT; | |
595 | #endif | |
596 | } | |
597 | ||
319a4f4b LP |
598 | int decompress_startswith_lz4( |
599 | const void *src, | |
600 | uint64_t src_size, | |
601 | void **buffer, | |
602 | const void *prefix, | |
603 | size_t prefix_len, | |
604 | uint8_t extra) { | |
605 | ||
319a4f4b LP |
606 | /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to |
607 | * follow the prefix */ | |
d89c8fdf | 608 | |
d89c8fdf ZJS |
609 | assert(src); |
610 | assert(src_size > 0); | |
611 | assert(buffer); | |
d89c8fdf | 612 | assert(prefix); |
d89c8fdf | 613 | |
3fc72d54 MC |
614 | #if HAVE_LZ4 |
615 | size_t allocated; | |
616 | int r; | |
617 | ||
618 | r = dlopen_lz4(); | |
619 | if (r < 0) | |
620 | return r; | |
621 | ||
d89c8fdf ZJS |
622 | if (src_size <= 8) |
623 | return -EBADMSG; | |
624 | ||
319a4f4b | 625 | if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) |
d89c8fdf | 626 | return -ENOMEM; |
319a4f4b LP |
627 | allocated = MALLOC_SIZEOF_SAFE(*buffer); |
628 | ||
3fc72d54 | 629 | r = sym_LZ4_decompress_safe_partial( |
319a4f4b LP |
630 | (char*)src + 8, |
631 | *buffer, | |
632 | src_size - 8, | |
633 | prefix_len + 1, | |
634 | allocated); | |
635 | ||
636 | /* One lz4 < 1.8.3, we might get "failure" (r < 0), or "success" where just a part of the buffer is | |
637 | * decompressed. But if we get a smaller amount of bytes than requested, we don't know whether there | |
638 | * isn't enough data to fill the requested size or whether we just got a partial answer. | |
e41ef6fd ZJS |
639 | */ |
640 | if (r < 0 || (size_t) r < prefix_len + 1) { | |
641 | size_t size; | |
642 | ||
3fc72d54 | 643 | if (sym_LZ4_versionNumber() >= 10803) |
e41ef6fd ZJS |
644 | /* We trust that the newer lz4 decompresses the number of bytes we |
645 | * requested if available in the compressed string. */ | |
646 | return 0; | |
647 | ||
648 | if (r > 0) | |
649 | /* Compare what we have first, in case of mismatch we can | |
650 | * shortcut the full comparison. */ | |
651 | if (memcmp(*buffer, prefix, r) != 0) | |
652 | return 0; | |
653 | ||
654 | /* Before version 1.8.3, lz4 always tries to decode full a "sequence", | |
655 | * so in pathological cases might need to decompress the full field. */ | |
319a4f4b | 656 | r = decompress_blob_lz4(src, src_size, buffer, &size, 0); |
1f4b467d ZJS |
657 | if (r < 0) |
658 | return r; | |
d89c8fdf | 659 | |
e41ef6fd ZJS |
660 | if (size < prefix_len + 1) |
661 | return 0; | |
662 | } | |
d89c8fdf | 663 | |
e41ef6fd ZJS |
664 | return memcmp(*buffer, prefix, prefix_len) == 0 && |
665 | ((const uint8_t*) *buffer)[prefix_len] == extra; | |
d89c8fdf ZJS |
666 | #else |
667 | return -EPROTONOSUPPORT; | |
668 | #endif | |
e4e61fdb | 669 | } |
355b59e2 | 670 | |
8653185a | 671 | int decompress_startswith_zstd( |
319a4f4b LP |
672 | const void *src, |
673 | uint64_t src_size, | |
674 | void **buffer, | |
675 | const void *prefix, | |
676 | size_t prefix_len, | |
8653185a | 677 | uint8_t extra) { |
3fc72d54 | 678 | |
8653185a LP |
679 | assert(src); |
680 | assert(src_size > 0); | |
681 | assert(buffer); | |
8653185a | 682 | assert(prefix); |
8653185a | 683 | |
3fc72d54 MC |
684 | #if HAVE_ZSTD |
685 | int r; | |
686 | ||
687 | r = dlopen_zstd(); | |
688 | if (r < 0) | |
689 | return r; | |
690 | ||
691 | uint64_t size = sym_ZSTD_getFrameContentSize(src, src_size); | |
e4a321fc ZJS |
692 | if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN)) |
693 | return -EBADMSG; | |
694 | ||
695 | if (size < prefix_len + 1) | |
696 | return 0; /* Decompressed text too short to match the prefix and extra */ | |
697 | ||
3fc72d54 | 698 | _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx(); |
8653185a LP |
699 | if (!dctx) |
700 | return -ENOMEM; | |
701 | ||
3fc72d54 | 702 | if (!(greedy_realloc(buffer, MAX(sym_ZSTD_DStreamOutSize(), prefix_len + 1), 1))) |
8653185a LP |
703 | return -ENOMEM; |
704 | ||
705 | ZSTD_inBuffer input = { | |
706 | .src = src, | |
707 | .size = src_size, | |
708 | }; | |
709 | ZSTD_outBuffer output = { | |
710 | .dst = *buffer, | |
319a4f4b | 711 | .size = MALLOC_SIZEOF_SAFE(*buffer), |
8653185a | 712 | }; |
e4a321fc | 713 | size_t k; |
8653185a | 714 | |
3fc72d54 MC |
715 | k = sym_ZSTD_decompressStream(dctx, &output, &input); |
716 | if (sym_ZSTD_isError(k)) { | |
717 | log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); | |
e4a321fc | 718 | return zstd_ret_to_errno(k); |
8653185a | 719 | } |
e4a321fc ZJS |
720 | assert(output.pos >= prefix_len + 1); |
721 | ||
722 | return memcmp(*buffer, prefix, prefix_len) == 0 && | |
723 | ((const uint8_t*) *buffer)[prefix_len] == extra; | |
8653185a LP |
724 | #else |
725 | return -EPROTONOSUPPORT; | |
726 | #endif | |
727 | } | |
728 | ||
729 | int decompress_startswith( | |
acc50c92 | 730 | Compression compression, |
319a4f4b LP |
731 | const void *src, |
732 | uint64_t src_size, | |
733 | void **buffer, | |
734 | const void *prefix, | |
735 | size_t prefix_len, | |
8653185a LP |
736 | uint8_t extra) { |
737 | ||
e74c1e1c LP |
738 | switch (compression) { |
739 | ||
740 | case COMPRESSION_XZ: | |
8653185a LP |
741 | return decompress_startswith_xz( |
742 | src, src_size, | |
319a4f4b | 743 | buffer, |
8653185a LP |
744 | prefix, prefix_len, |
745 | extra); | |
746 | ||
e74c1e1c | 747 | case COMPRESSION_LZ4: |
8653185a LP |
748 | return decompress_startswith_lz4( |
749 | src, src_size, | |
319a4f4b | 750 | buffer, |
8653185a LP |
751 | prefix, prefix_len, |
752 | extra); | |
e74c1e1c | 753 | case COMPRESSION_ZSTD: |
8653185a LP |
754 | return decompress_startswith_zstd( |
755 | src, src_size, | |
319a4f4b | 756 | buffer, |
8653185a LP |
757 | prefix, prefix_len, |
758 | extra); | |
e74c1e1c | 759 | default: |
d89c8fdf | 760 | return -EBADMSG; |
e74c1e1c | 761 | } |
d89c8fdf ZJS |
762 | } |
763 | ||
5b6f8e13 | 764 | int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { |
3fc72d54 MC |
765 | assert(fdf >= 0); |
766 | assert(fdt >= 0); | |
767 | ||
349cc4a5 | 768 | #if HAVE_XZ |
3fc72d54 | 769 | _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; |
355b59e2 | 770 | lzma_ret ret; |
355b59e2 ZJS |
771 | uint8_t buf[BUFSIZ], out[BUFSIZ]; |
772 | lzma_action action = LZMA_RUN; | |
3fc72d54 | 773 | int r; |
355b59e2 | 774 | |
3fc72d54 MC |
775 | r = dlopen_lzma(); |
776 | if (r < 0) | |
777 | return r; | |
355b59e2 | 778 | |
3fc72d54 | 779 | ret = sym_lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); |
d7a0f1f4 FS |
780 | if (ret != LZMA_OK) |
781 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
782 | "Failed to initialize XZ encoder: code %u", | |
783 | ret); | |
355b59e2 ZJS |
784 | |
785 | for (;;) { | |
786 | if (s.avail_in == 0 && action == LZMA_RUN) { | |
787 | size_t m = sizeof(buf); | |
788 | ssize_t n; | |
789 | ||
f5fbe71d | 790 | if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes) |
59f448cf | 791 | m = (size_t) max_bytes; |
355b59e2 ZJS |
792 | |
793 | n = read(fdf, buf, m); | |
794 | if (n < 0) | |
795 | return -errno; | |
796 | if (n == 0) | |
797 | action = LZMA_FINISH; | |
798 | else { | |
799 | s.next_in = buf; | |
800 | s.avail_in = n; | |
801 | ||
f5fbe71d | 802 | if (max_bytes != UINT64_MAX) { |
59f448cf | 803 | assert(max_bytes >= (uint64_t) n); |
355b59e2 ZJS |
804 | max_bytes -= n; |
805 | } | |
806 | } | |
807 | } | |
808 | ||
809 | if (s.avail_out == 0) { | |
810 | s.next_out = out; | |
811 | s.avail_out = sizeof(out); | |
812 | } | |
813 | ||
3fc72d54 | 814 | ret = sym_lzma_code(&s, action); |
d7a0f1f4 FS |
815 | if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) |
816 | return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), | |
817 | "Compression failed: code %u", | |
818 | ret); | |
355b59e2 ZJS |
819 | |
820 | if (s.avail_out == 0 || ret == LZMA_STREAM_END) { | |
821 | ssize_t n, k; | |
822 | ||
823 | n = sizeof(out) - s.avail_out; | |
824 | ||
e22c60a9 | 825 | k = loop_write(fdt, out, n); |
355b59e2 ZJS |
826 | if (k < 0) |
827 | return k; | |
355b59e2 ZJS |
828 | |
829 | if (ret == LZMA_STREAM_END) { | |
5b6f8e13 LB |
830 | if (ret_uncompressed_size) |
831 | *ret_uncompressed_size = s.total_in; | |
832 | ||
d483ac1d LP |
833 | if (s.total_in == 0) |
834 | log_debug("XZ compression finished (no input data)"); | |
835 | else | |
836 | log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", | |
837 | s.total_in, s.total_out, | |
838 | (double) s.total_out / s.total_in * 100); | |
355b59e2 | 839 | |
bfeaa62d | 840 | return 0; |
355b59e2 ZJS |
841 | } |
842 | } | |
843 | } | |
3b1a55e1 ZJS |
844 | #else |
845 | return -EPROTONOSUPPORT; | |
846 | #endif | |
355b59e2 ZJS |
847 | } |
848 | ||
4b5bc539 | 849 | #define LZ4_BUFSIZE (512*1024u) |
d89c8fdf | 850 | |
5b6f8e13 | 851 | int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { |
d89c8fdf | 852 | |
349cc4a5 | 853 | #if HAVE_LZ4 |
4b5bc539 | 854 | LZ4F_errorCode_t c; |
3fc72d54 | 855 | _cleanup_(sym_LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; |
834bab01 LB |
856 | _cleanup_free_ void *in_buff = NULL; |
857 | _cleanup_free_ char *out_buff = NULL; | |
5b6f8e13 LB |
858 | size_t out_allocsize, n, offset = 0, frame_size; |
859 | uint64_t total_in = 0, total_out; | |
4b5bc539 | 860 | int r; |
4b5bc539 ZJS |
861 | static const LZ4F_preferences_t preferences = { |
862 | .frameInfo.blockSizeID = 5, | |
863 | }; | |
d89c8fdf | 864 | |
3fc72d54 MC |
865 | r = dlopen_lz4(); |
866 | if (r < 0) | |
867 | return r; | |
868 | ||
869 | c = sym_LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); | |
870 | if (sym_LZ4F_isError(c)) | |
4b5bc539 | 871 | return -ENOMEM; |
d89c8fdf | 872 | |
3fc72d54 | 873 | frame_size = sym_LZ4F_compressBound(LZ4_BUFSIZE, &preferences); |
834bab01 LB |
874 | out_allocsize = frame_size + 64*1024; /* add some space for header and trailer */ |
875 | out_buff = malloc(out_allocsize); | |
876 | if (!out_buff) | |
877 | return -ENOMEM; | |
878 | ||
879 | in_buff = malloc(LZ4_BUFSIZE); | |
880 | if (!in_buff) | |
4b5bc539 | 881 | return -ENOMEM; |
d89c8fdf | 882 | |
3fc72d54 MC |
883 | n = offset = total_out = sym_LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences); |
884 | if (sym_LZ4F_isError(n)) | |
4b5bc539 | 885 | return -EINVAL; |
d89c8fdf | 886 | |
834bab01 | 887 | log_debug("Buffer size is %zu bytes, header size %zu bytes.", out_allocsize, n); |
d89c8fdf | 888 | |
834bab01 | 889 | for (;;) { |
4b5bc539 | 890 | ssize_t k; |
d89c8fdf | 891 | |
834bab01 LB |
892 | k = loop_read(fdf, in_buff, LZ4_BUFSIZE, true); |
893 | if (k < 0) | |
894 | return k; | |
895 | if (k == 0) | |
896 | break; | |
3fc72d54 | 897 | n = sym_LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset, |
834bab01 | 898 | in_buff, k, NULL); |
3fc72d54 | 899 | if (sym_LZ4F_isError(n)) |
834bab01 | 900 | return -ENOTRECOVERABLE; |
d89c8fdf | 901 | |
4b5bc539 ZJS |
902 | total_in += k; |
903 | offset += n; | |
904 | total_out += n; | |
d89c8fdf | 905 | |
834bab01 LB |
906 | if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) |
907 | return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), | |
908 | "Compressed stream longer than %" PRIu64 " bytes", max_bytes); | |
d89c8fdf | 909 | |
834bab01 | 910 | if (out_allocsize - offset < frame_size + 4) { |
e22c60a9 | 911 | k = loop_write(fdt, out_buff, offset); |
834bab01 LB |
912 | if (k < 0) |
913 | return k; | |
4b5bc539 ZJS |
914 | offset = 0; |
915 | } | |
916 | } | |
d89c8fdf | 917 | |
3fc72d54 MC |
918 | n = sym_LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL); |
919 | if (sym_LZ4F_isError(n)) | |
834bab01 | 920 | return -ENOTRECOVERABLE; |
d89c8fdf | 921 | |
4b5bc539 ZJS |
922 | offset += n; |
923 | total_out += n; | |
e22c60a9 | 924 | r = loop_write(fdt, out_buff, offset); |
4b5bc539 | 925 | if (r < 0) |
834bab01 | 926 | return r; |
d89c8fdf | 927 | |
5b6f8e13 LB |
928 | if (ret_uncompressed_size) |
929 | *ret_uncompressed_size = total_in; | |
930 | ||
2584f745 A |
931 | if (total_in == 0) |
932 | log_debug("LZ4 compression finished (no input data)"); | |
933 | else | |
934 | log_debug("LZ4 compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", | |
d483ac1d LP |
935 | total_in, total_out, |
936 | (double) total_out / total_in * 100); | |
834bab01 | 937 | |
bfeaa62d | 938 | return 0; |
d89c8fdf ZJS |
939 | #else |
940 | return -EPROTONOSUPPORT; | |
941 | #endif | |
942 | } | |
943 | ||
59f448cf | 944 | int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { |
3fc72d54 MC |
945 | assert(fdf >= 0); |
946 | assert(fdt >= 0); | |
d89c8fdf | 947 | |
349cc4a5 | 948 | #if HAVE_XZ |
3fc72d54 | 949 | _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; |
355b59e2 ZJS |
950 | lzma_ret ret; |
951 | ||
952 | uint8_t buf[BUFSIZ], out[BUFSIZ]; | |
953 | lzma_action action = LZMA_RUN; | |
3fc72d54 | 954 | int r; |
355b59e2 | 955 | |
3fc72d54 MC |
956 | r = dlopen_lzma(); |
957 | if (r < 0) | |
958 | return r; | |
355b59e2 | 959 | |
3fc72d54 | 960 | ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); |
d7a0f1f4 FS |
961 | if (ret != LZMA_OK) |
962 | return log_debug_errno(SYNTHETIC_ERRNO(ENOMEM), | |
963 | "Failed to initialize XZ decoder: code %u", | |
964 | ret); | |
355b59e2 ZJS |
965 | |
966 | for (;;) { | |
967 | if (s.avail_in == 0 && action == LZMA_RUN) { | |
968 | ssize_t n; | |
969 | ||
970 | n = read(fdf, buf, sizeof(buf)); | |
971 | if (n < 0) | |
972 | return -errno; | |
973 | if (n == 0) | |
974 | action = LZMA_FINISH; | |
975 | else { | |
976 | s.next_in = buf; | |
977 | s.avail_in = n; | |
978 | } | |
979 | } | |
980 | ||
981 | if (s.avail_out == 0) { | |
982 | s.next_out = out; | |
983 | s.avail_out = sizeof(out); | |
984 | } | |
985 | ||
3fc72d54 | 986 | ret = sym_lzma_code(&s, action); |
d7a0f1f4 FS |
987 | if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) |
988 | return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), | |
989 | "Decompression failed: code %u", | |
990 | ret); | |
355b59e2 ZJS |
991 | |
992 | if (s.avail_out == 0 || ret == LZMA_STREAM_END) { | |
993 | ssize_t n, k; | |
994 | ||
995 | n = sizeof(out) - s.avail_out; | |
996 | ||
f5fbe71d | 997 | if (max_bytes != UINT64_MAX) { |
59f448cf | 998 | if (max_bytes < (uint64_t) n) |
d89c8fdf | 999 | return -EFBIG; |
355b59e2 ZJS |
1000 | |
1001 | max_bytes -= n; | |
1002 | } | |
1003 | ||
e22c60a9 | 1004 | k = loop_write(fdt, out, n); |
355b59e2 ZJS |
1005 | if (k < 0) |
1006 | return k; | |
355b59e2 ZJS |
1007 | |
1008 | if (ret == LZMA_STREAM_END) { | |
d483ac1d LP |
1009 | if (s.total_in == 0) |
1010 | log_debug("XZ decompression finished (no input data)"); | |
1011 | else | |
1012 | log_debug("XZ decompression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", | |
1013 | s.total_in, s.total_out, | |
1014 | (double) s.total_out / s.total_in * 100); | |
355b59e2 ZJS |
1015 | |
1016 | return 0; | |
1017 | } | |
1018 | } | |
1019 | } | |
d89c8fdf | 1020 | #else |
d7a0f1f4 FS |
1021 | return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), |
1022 | "Cannot decompress file. Compiled without XZ support."); | |
d89c8fdf ZJS |
1023 | #endif |
1024 | } | |
1025 | ||
8e64dd1e | 1026 | int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { |
349cc4a5 | 1027 | #if HAVE_LZ4 |
4b5bc539 | 1028 | size_t c; |
3fc72d54 | 1029 | _cleanup_(sym_LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; |
4b5bc539 ZJS |
1030 | _cleanup_free_ char *buf = NULL; |
1031 | char *src; | |
1032 | struct stat st; | |
3fc72d54 | 1033 | int r; |
4b5bc539 ZJS |
1034 | size_t total_in = 0, total_out = 0; |
1035 | ||
3fc72d54 MC |
1036 | r = dlopen_lz4(); |
1037 | if (r < 0) | |
1038 | return r; | |
1039 | ||
1040 | c = sym_LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); | |
1041 | if (sym_LZ4F_isError(c)) | |
4b5bc539 ZJS |
1042 | return -ENOMEM; |
1043 | ||
1044 | if (fstat(in, &st) < 0) | |
5146f9f0 | 1045 | return log_debug_errno(errno, "fstat() failed: %m"); |
4b5bc539 | 1046 | |
1a823cde TS |
1047 | if (file_offset_beyond_memory_size(st.st_size)) |
1048 | return -EFBIG; | |
1049 | ||
4b5bc539 ZJS |
1050 | buf = malloc(LZ4_BUFSIZE); |
1051 | if (!buf) | |
1052 | return -ENOMEM; | |
1053 | ||
1054 | src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, in, 0); | |
e0685172 | 1055 | if (src == MAP_FAILED) |
4b5bc539 ZJS |
1056 | return -errno; |
1057 | ||
1058 | while (total_in < (size_t) st.st_size) { | |
1059 | size_t produced = LZ4_BUFSIZE; | |
1060 | size_t used = st.st_size - total_in; | |
1061 | ||
3fc72d54 MC |
1062 | c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); |
1063 | if (sym_LZ4F_isError(c)) { | |
4b5bc539 ZJS |
1064 | r = -EBADMSG; |
1065 | goto cleanup; | |
1066 | } | |
1067 | ||
1068 | total_in += used; | |
1069 | total_out += produced; | |
1070 | ||
f5fbe71d | 1071 | if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) { |
82e24b00 | 1072 | log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes); |
5146f9f0 | 1073 | r = -EFBIG; |
4b5bc539 ZJS |
1074 | goto cleanup; |
1075 | } | |
1076 | ||
e22c60a9 | 1077 | r = loop_write(out, buf, produced); |
4b5bc539 ZJS |
1078 | if (r < 0) |
1079 | goto cleanup; | |
1080 | } | |
1081 | ||
d483ac1d LP |
1082 | if (total_in == 0) |
1083 | log_debug("LZ4 decompression finished (no input data)"); | |
1084 | else | |
1085 | log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", | |
1086 | total_in, total_out, | |
1087 | (double) total_out / total_in * 100); | |
3fc72d54 | 1088 | r = 0; |
4b5bc539 ZJS |
1089 | cleanup: |
1090 | munmap(src, st.st_size); | |
1091 | return r; | |
d89c8fdf | 1092 | #else |
d7a0f1f4 FS |
1093 | return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), |
1094 | "Cannot decompress file. Compiled without LZ4 support."); | |
d89c8fdf ZJS |
1095 | #endif |
1096 | } | |
1097 | ||
5b6f8e13 | 1098 | int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { |
3fc72d54 MC |
1099 | assert(fdf >= 0); |
1100 | assert(fdt >= 0); | |
1101 | ||
ef5924aa | 1102 | #if HAVE_ZSTD |
3fc72d54 | 1103 | _cleanup_(sym_ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; |
ef5924aa NL |
1104 | _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; |
1105 | size_t in_allocsize, out_allocsize; | |
1106 | size_t z; | |
1107 | uint64_t left = max_bytes, in_bytes = 0; | |
3fc72d54 | 1108 | int r; |
ef5924aa | 1109 | |
3fc72d54 MC |
1110 | r = dlopen_zstd(); |
1111 | if (r < 0) | |
1112 | return r; | |
ef5924aa NL |
1113 | |
1114 | /* Create the context and buffers */ | |
3fc72d54 MC |
1115 | in_allocsize = sym_ZSTD_CStreamInSize(); |
1116 | out_allocsize = sym_ZSTD_CStreamOutSize(); | |
ef5924aa NL |
1117 | in_buff = malloc(in_allocsize); |
1118 | out_buff = malloc(out_allocsize); | |
3fc72d54 | 1119 | cctx = sym_ZSTD_createCCtx(); |
ef5924aa NL |
1120 | if (!cctx || !out_buff || !in_buff) |
1121 | return -ENOMEM; | |
1122 | ||
3fc72d54 MC |
1123 | z = sym_ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); |
1124 | if (sym_ZSTD_isError(z)) | |
1125 | log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z)); | |
ef5924aa NL |
1126 | |
1127 | /* This loop read from the input file, compresses that entire chunk, | |
1128 | * and writes all output produced to the output file. | |
1129 | */ | |
1130 | for (;;) { | |
1131 | bool is_last_chunk; | |
1132 | ZSTD_inBuffer input = { | |
1133 | .src = in_buff, | |
1134 | .size = 0, | |
1135 | .pos = 0 | |
1136 | }; | |
1137 | ssize_t red; | |
1138 | ||
1139 | red = loop_read(fdf, in_buff, in_allocsize, true); | |
1140 | if (red < 0) | |
1141 | return red; | |
1142 | is_last_chunk = red == 0; | |
1143 | ||
1144 | in_bytes += (size_t) red; | |
1145 | input.size = (size_t) red; | |
1146 | ||
1147 | for (bool finished = false; !finished;) { | |
1148 | ZSTD_outBuffer output = { | |
1149 | .dst = out_buff, | |
1150 | .size = out_allocsize, | |
1151 | .pos = 0 | |
1152 | }; | |
1153 | size_t remaining; | |
1154 | ssize_t wrote; | |
1155 | ||
1156 | /* Compress into the output buffer and write all of the | |
1157 | * output to the file so we can reuse the buffer next | |
1158 | * iteration. | |
1159 | */ | |
3fc72d54 | 1160 | remaining = sym_ZSTD_compressStream2( |
ef5924aa NL |
1161 | cctx, &output, &input, |
1162 | is_last_chunk ? ZSTD_e_end : ZSTD_e_continue); | |
1163 | ||
3fc72d54 MC |
1164 | if (sym_ZSTD_isError(remaining)) { |
1165 | log_debug("ZSTD encoder failed: %s", sym_ZSTD_getErrorName(remaining)); | |
ef5924aa NL |
1166 | return zstd_ret_to_errno(remaining); |
1167 | } | |
1168 | ||
1169 | if (left < output.pos) | |
1170 | return -EFBIG; | |
1171 | ||
e22c60a9 | 1172 | wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); |
ef5924aa NL |
1173 | if (wrote < 0) |
1174 | return wrote; | |
1175 | ||
1176 | left -= output.pos; | |
1177 | ||
1178 | /* If we're on the last chunk we're finished when zstd | |
1179 | * returns 0, which means its consumed all the input AND | |
1180 | * finished the frame. Otherwise, we're finished when | |
1181 | * we've consumed all the input. | |
1182 | */ | |
1183 | finished = is_last_chunk ? (remaining == 0) : (input.pos == input.size); | |
1184 | } | |
1185 | ||
1186 | /* zstd only returns 0 when the input is completely consumed */ | |
1187 | assert(input.pos == input.size); | |
1188 | if (is_last_chunk) | |
1189 | break; | |
1190 | } | |
1191 | ||
5b6f8e13 LB |
1192 | if (ret_uncompressed_size) |
1193 | *ret_uncompressed_size = in_bytes; | |
1194 | ||
d483ac1d LP |
1195 | if (in_bytes == 0) |
1196 | log_debug("ZSTD compression finished (no input data)"); | |
1197 | else | |
89d36ce8 YW |
1198 | log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", |
1199 | in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100); | |
ef5924aa | 1200 | |
bfeaa62d | 1201 | return 0; |
ef5924aa NL |
1202 | #else |
1203 | return -EPROTONOSUPPORT; | |
1204 | #endif | |
1205 | } | |
1206 | ||
1207 | int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { | |
3fc72d54 MC |
1208 | assert(fdf >= 0); |
1209 | assert(fdt >= 0); | |
1210 | ||
ef5924aa | 1211 | #if HAVE_ZSTD |
3fc72d54 | 1212 | _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; |
ef5924aa NL |
1213 | _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; |
1214 | size_t in_allocsize, out_allocsize; | |
1215 | size_t last_result = 0; | |
1216 | uint64_t left = max_bytes, in_bytes = 0; | |
3fc72d54 | 1217 | int r; |
ef5924aa | 1218 | |
3fc72d54 MC |
1219 | r = dlopen_zstd(); |
1220 | if (r < 0) | |
1221 | return r; | |
ef5924aa | 1222 | /* Create the context and buffers */ |
3fc72d54 MC |
1223 | in_allocsize = sym_ZSTD_DStreamInSize(); |
1224 | out_allocsize = sym_ZSTD_DStreamOutSize(); | |
ef5924aa NL |
1225 | in_buff = malloc(in_allocsize); |
1226 | out_buff = malloc(out_allocsize); | |
3fc72d54 | 1227 | dctx = sym_ZSTD_createDCtx(); |
ef5924aa NL |
1228 | if (!dctx || !out_buff || !in_buff) |
1229 | return -ENOMEM; | |
1230 | ||
1231 | /* This loop assumes that the input file is one or more concatenated | |
1232 | * zstd streams. This example won't work if there is trailing non-zstd | |
1233 | * data at the end, but streaming decompression in general handles this | |
1234 | * case. ZSTD_decompressStream() returns 0 exactly when the frame is | |
1235 | * completed, and doesn't consume input after the frame. | |
1236 | */ | |
1237 | for (;;) { | |
1238 | bool has_error = false; | |
1239 | ZSTD_inBuffer input = { | |
1240 | .src = in_buff, | |
1241 | .size = 0, | |
1242 | .pos = 0 | |
1243 | }; | |
1244 | ssize_t red; | |
1245 | ||
1246 | red = loop_read(fdf, in_buff, in_allocsize, true); | |
1247 | if (red < 0) | |
1248 | return red; | |
1249 | if (red == 0) | |
1250 | break; | |
1251 | ||
1252 | in_bytes += (size_t) red; | |
1253 | input.size = (size_t) red; | |
1254 | input.pos = 0; | |
1255 | ||
1256 | /* Given a valid frame, zstd won't consume the last byte of the | |
1257 | * frame until it has flushed all of the decompressed data of | |
1258 | * the frame. So input.pos < input.size means frame is not done | |
1259 | * or there is still output available. | |
1260 | */ | |
1261 | while (input.pos < input.size) { | |
1262 | ZSTD_outBuffer output = { | |
1263 | .dst = out_buff, | |
1264 | .size = out_allocsize, | |
1265 | .pos = 0 | |
1266 | }; | |
1267 | ssize_t wrote; | |
1268 | /* The return code is zero if the frame is complete, but | |
1269 | * there may be multiple frames concatenated together. | |
1270 | * Zstd will automatically reset the context when a | |
1271 | * frame is complete. Still, calling ZSTD_DCtx_reset() | |
1272 | * can be useful to reset the context to a clean state, | |
1273 | * for instance if the last decompression call returned | |
1274 | * an error. | |
1275 | */ | |
3fc72d54 MC |
1276 | last_result = sym_ZSTD_decompressStream(dctx, &output, &input); |
1277 | if (sym_ZSTD_isError(last_result)) { | |
ef5924aa NL |
1278 | has_error = true; |
1279 | break; | |
1280 | } | |
1281 | ||
1282 | if (left < output.pos) | |
1283 | return -EFBIG; | |
1284 | ||
e22c60a9 | 1285 | wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); |
ef5924aa NL |
1286 | if (wrote < 0) |
1287 | return wrote; | |
1288 | ||
1289 | left -= output.pos; | |
1290 | } | |
1291 | if (has_error) | |
1292 | break; | |
1293 | } | |
1294 | ||
1295 | if (in_bytes == 0) | |
1296 | return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoder failed: no data read"); | |
1297 | ||
1298 | if (last_result != 0) { | |
1299 | /* The last return value from ZSTD_decompressStream did not end | |
1300 | * on a frame, but we reached the end of the file! We assume | |
1301 | * this is an error, and the input was truncated. | |
1302 | */ | |
3fc72d54 | 1303 | log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(last_result)); |
ef5924aa NL |
1304 | return zstd_ret_to_errno(last_result); |
1305 | } | |
1306 | ||
d483ac1d LP |
1307 | if (in_bytes == 0) |
1308 | log_debug("ZSTD decompression finished (no input data)"); | |
1309 | else | |
1310 | log_debug("ZSTD decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", | |
1311 | in_bytes, | |
1312 | max_bytes - left, | |
1313 | (double) (max_bytes - left) / in_bytes * 100); | |
ef5924aa NL |
1314 | return 0; |
1315 | #else | |
d7a0f1f4 FS |
1316 | return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), |
1317 | "Cannot decompress file. Compiled without ZSTD support."); | |
ef5924aa NL |
1318 | #endif |
1319 | } | |
1320 | ||
59f448cf | 1321 | int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { |
d89c8fdf ZJS |
1322 | |
1323 | if (endswith(filename, ".lz4")) | |
1324 | return decompress_stream_lz4(fdf, fdt, max_bytes); | |
e74c1e1c | 1325 | if (endswith(filename, ".xz")) |
d89c8fdf | 1326 | return decompress_stream_xz(fdf, fdt, max_bytes); |
e74c1e1c | 1327 | if (endswith(filename, ".zst")) |
ef5924aa | 1328 | return decompress_stream_zstd(fdf, fdt, max_bytes); |
e74c1e1c LP |
1329 | |
1330 | return -EPROTONOSUPPORT; | |
355b59e2 | 1331 | } |