]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <stdio.h> | |
4 | #include <sys/mman.h> | |
5 | #include <sys/stat.h> | |
6 | #include <unistd.h> | |
7 | ||
8 | #if HAVE_LZ4 | |
9 | #include <lz4.h> | |
10 | #include <lz4hc.h> | |
11 | #include <lz4frame.h> | |
12 | #endif | |
13 | ||
14 | #if HAVE_XZ | |
15 | #include <lzma.h> | |
16 | #endif | |
17 | ||
18 | #if HAVE_ZSTD | |
19 | #include <zstd.h> | |
20 | #include <zstd_errors.h> | |
21 | #endif | |
22 | ||
23 | #include "alloc-util.h" | |
24 | #include "bitfield.h" | |
25 | #include "compress.h" | |
26 | #include "dlfcn-util.h" | |
27 | #include "fileio.h" | |
28 | #include "io-util.h" | |
29 | #include "log.h" | |
30 | #include "string-table.h" | |
31 | #include "string-util.h" | |
32 | #include "unaligned.h" | |
33 | ||
34 | #if HAVE_LZ4 | |
35 | static void *lz4_dl = NULL; | |
36 | ||
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; | |
47 | static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL; | |
48 | /* These are used in test-compress.c so we don't make them static. */ | |
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; | |
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); | |
56 | #endif | |
57 | ||
58 | #if HAVE_ZSTD | |
59 | static void *zstd_dl = NULL; | |
60 | ||
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; | |
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); | |
80 | ||
81 | static int zstd_ret_to_errno(size_t ret) { | |
82 | switch (sym_ZSTD_getErrorCode(ret)) { | |
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 | ||
93 | #if HAVE_XZ | |
94 | static void *lzma_dl = NULL; | |
95 | ||
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; | |
101 | static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL; | |
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 | ||
115 | #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) | |
116 | ||
117 | static const char* const compression_table[_COMPRESSION_MAX] = { | |
118 | [COMPRESSION_NONE] = "NONE", | |
119 | [COMPRESSION_XZ] = "XZ", | |
120 | [COMPRESSION_LZ4] = "LZ4", | |
121 | [COMPRESSION_ZSTD] = "ZSTD", | |
122 | }; | |
123 | ||
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 | ||
131 | DEFINE_STRING_TABLE_LOOKUP(compression, Compression); | |
132 | DEFINE_STRING_TABLE_LOOKUP(compression_lowercase, Compression); | |
133 | ||
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 | ||
141 | assert(c >= 0); | |
142 | assert(c < _COMPRESSION_MAX); | |
143 | ||
144 | return BIT_SET(supported, c); | |
145 | } | |
146 | ||
147 | #if HAVE_XZ | |
148 | int dlopen_lzma(void) { | |
149 | ELF_NOTE_DLOPEN("lzma", | |
150 | "Support lzma compression in journal and coredump files", | |
151 | COMPRESSION_PRIORITY_XZ, | |
152 | "liblzma.so.5"); | |
153 | ||
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), | |
161 | DLSYM_ARG(lzma_lzma_preset), | |
162 | DLSYM_ARG(lzma_stream_decoder)); | |
163 | } | |
164 | #endif | |
165 | ||
166 | int compress_blob_xz(const void *src, uint64_t src_size, | |
167 | void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { | |
168 | ||
169 | assert(src); | |
170 | assert(src_size > 0); | |
171 | assert(dst); | |
172 | assert(dst_alloc_size > 0); | |
173 | assert(dst_size); | |
174 | ||
175 | #if HAVE_XZ | |
176 | lzma_options_lzma opt = { | |
177 | 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, | |
178 | LZMA_PB_DEFAULT, LZMA_MODE_FAST, 128, LZMA_MF_HC3, 4 | |
179 | }; | |
180 | lzma_filter filters[] = { | |
181 | { LZMA_FILTER_LZMA2, &opt }, | |
182 | { LZMA_VLI_UNKNOWN, NULL } | |
183 | }; | |
184 | lzma_ret ret; | |
185 | size_t out_pos = 0; | |
186 | int r; | |
187 | ||
188 | r = dlopen_lzma(); | |
189 | if (r < 0) | |
190 | return r; | |
191 | ||
192 | if (level >= 0) { | |
193 | r = sym_lzma_lzma_preset(&opt, (uint32_t) level); | |
194 | if (r < 0) | |
195 | return r; | |
196 | } | |
197 | ||
198 | /* Returns < 0 if we couldn't compress the data or the | |
199 | * compressed result is longer than the original */ | |
200 | ||
201 | if (src_size < 80) | |
202 | return -ENOBUFS; | |
203 | ||
204 | ret = sym_lzma_stream_buffer_encode(filters, LZMA_CHECK_NONE, NULL, | |
205 | src, src_size, dst, &out_pos, dst_alloc_size); | |
206 | if (ret != LZMA_OK) | |
207 | return -ENOBUFS; | |
208 | ||
209 | *dst_size = out_pos; | |
210 | return 0; | |
211 | #else | |
212 | return -EPROTONOSUPPORT; | |
213 | #endif | |
214 | } | |
215 | ||
216 | #if HAVE_LZ4 | |
217 | int dlopen_lz4(void) { | |
218 | ELF_NOTE_DLOPEN("lz4", | |
219 | "Support lz4 compression in journal and coredump files", | |
220 | COMPRESSION_PRIORITY_LZ4, | |
221 | "liblz4.so.1"); | |
222 | ||
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), | |
237 | DLSYM_ARG(LZ4_compress_HC), | |
238 | DLSYM_ARG(LZ4_decompress_safe), | |
239 | DLSYM_ARG(LZ4_decompress_safe_partial), | |
240 | DLSYM_ARG(LZ4_versionNumber)); | |
241 | } | |
242 | #endif | |
243 | ||
244 | int compress_blob_lz4(const void *src, uint64_t src_size, | |
245 | void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { | |
246 | ||
247 | assert(src); | |
248 | assert(src_size > 0); | |
249 | assert(dst); | |
250 | assert(dst_alloc_size > 0); | |
251 | assert(dst_size); | |
252 | ||
253 | #if HAVE_LZ4 | |
254 | int r; | |
255 | ||
256 | r = dlopen_lz4(); | |
257 | if (r < 0) | |
258 | return r; | |
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; | |
264 | ||
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); | |
269 | if (r <= 0) | |
270 | return -ENOBUFS; | |
271 | ||
272 | unaligned_write_le64(dst, src_size); | |
273 | *dst_size = r + 8; | |
274 | ||
275 | return 0; | |
276 | #else | |
277 | return -EPROTONOSUPPORT; | |
278 | #endif | |
279 | } | |
280 | ||
281 | #if HAVE_ZSTD | |
282 | int dlopen_zstd(void) { | |
283 | ELF_NOTE_DLOPEN("zstd", | |
284 | "Support zstd compression in journal and coredump files", | |
285 | COMPRESSION_PRIORITY_ZSTD, | |
286 | "libzstd.so.1"); | |
287 | ||
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 | ||
310 | int compress_blob_zstd( | |
311 | const void *src, uint64_t src_size, | |
312 | void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { | |
313 | ||
314 | assert(src); | |
315 | assert(src_size > 0); | |
316 | assert(dst); | |
317 | assert(dst_alloc_size > 0); | |
318 | assert(dst_size); | |
319 | ||
320 | #if HAVE_ZSTD | |
321 | size_t k; | |
322 | int r; | |
323 | ||
324 | r = dlopen_zstd(); | |
325 | if (r < 0) | |
326 | return r; | |
327 | ||
328 | k = sym_ZSTD_compress(dst, dst_alloc_size, src, src_size, level < 0 ? 0 : level); | |
329 | if (sym_ZSTD_isError(k)) | |
330 | return zstd_ret_to_errno(k); | |
331 | ||
332 | *dst_size = k; | |
333 | return 0; | |
334 | #else | |
335 | return -EPROTONOSUPPORT; | |
336 | #endif | |
337 | } | |
338 | ||
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) { | |
345 | ||
346 | assert(src); | |
347 | assert(src_size > 0); | |
348 | assert(dst); | |
349 | assert(dst_size); | |
350 | ||
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); | |
362 | if (ret != LZMA_OK) | |
363 | return -ENOMEM; | |
364 | ||
365 | space = MIN(src_size * 2, dst_max ?: SIZE_MAX); | |
366 | if (!greedy_realloc(dst, space, 1)) | |
367 | return -ENOMEM; | |
368 | ||
369 | s.next_in = src; | |
370 | s.avail_in = src_size; | |
371 | ||
372 | s.next_out = *dst; | |
373 | s.avail_out = space; | |
374 | ||
375 | for (;;) { | |
376 | size_t used; | |
377 | ||
378 | ret = sym_lzma_code(&s, LZMA_FINISH); | |
379 | if (ret == LZMA_STREAM_END) | |
380 | break; | |
381 | if (ret != LZMA_OK) | |
382 | return -ENOMEM; | |
383 | ||
384 | if (dst_max > 0 && (space - s.avail_out) >= dst_max) | |
385 | break; | |
386 | if (dst_max > 0 && space == dst_max) | |
387 | return -ENOBUFS; | |
388 | ||
389 | used = space - s.avail_out; | |
390 | space = MIN(2 * space, dst_max ?: SIZE_MAX); | |
391 | if (!greedy_realloc(dst, space, 1)) | |
392 | return -ENOMEM; | |
393 | ||
394 | s.avail_out = space - used; | |
395 | s.next_out = *(uint8_t**)dst + used; | |
396 | } | |
397 | ||
398 | *dst_size = space - s.avail_out; | |
399 | return 0; | |
400 | #else | |
401 | return -EPROTONOSUPPORT; | |
402 | #endif | |
403 | } | |
404 | ||
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) { | |
411 | ||
412 | assert(src); | |
413 | assert(src_size > 0); | |
414 | assert(dst); | |
415 | assert(dst_size); | |
416 | ||
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 | ||
425 | if (src_size <= 8) | |
426 | return -EBADMSG; | |
427 | ||
428 | size = unaligned_read_le64(src); | |
429 | if (size < 0 || (unsigned) size != unaligned_read_le64(src)) | |
430 | return -EFBIG; | |
431 | out = greedy_realloc(dst, size, 1); | |
432 | if (!out) | |
433 | return -ENOMEM; | |
434 | ||
435 | r = sym_LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size); | |
436 | if (r < 0 || r != size) | |
437 | return -EBADMSG; | |
438 | ||
439 | *dst_size = size; | |
440 | return 0; | |
441 | #else | |
442 | return -EPROTONOSUPPORT; | |
443 | #endif | |
444 | } | |
445 | ||
446 | int decompress_blob_zstd( | |
447 | const void *src, | |
448 | uint64_t src_size, | |
449 | void **dst, | |
450 | size_t *dst_size, | |
451 | size_t dst_max) { | |
452 | ||
453 | assert(src); | |
454 | assert(src_size > 0); | |
455 | assert(dst); | |
456 | assert(dst_size); | |
457 | ||
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); | |
467 | if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN)) | |
468 | return -EBADMSG; | |
469 | ||
470 | if (dst_max > 0 && size > dst_max) | |
471 | size = dst_max; | |
472 | if (size > SIZE_MAX) | |
473 | return -E2BIG; | |
474 | ||
475 | if (!(greedy_realloc(dst, MAX(sym_ZSTD_DStreamOutSize(), size), 1))) | |
476 | return -ENOMEM; | |
477 | ||
478 | _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx(); | |
479 | if (!dctx) | |
480 | return -ENOMEM; | |
481 | ||
482 | ZSTD_inBuffer input = { | |
483 | .src = src, | |
484 | .size = src_size, | |
485 | }; | |
486 | ZSTD_outBuffer output = { | |
487 | .dst = *dst, | |
488 | .size = MALLOC_SIZEOF_SAFE(*dst), | |
489 | }; | |
490 | ||
491 | size_t k = sym_ZSTD_decompressStream(dctx, &output, &input); | |
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."); | |
496 | ||
497 | *dst_size = size; | |
498 | return 0; | |
499 | #else | |
500 | return -EPROTONOSUPPORT; | |
501 | #endif | |
502 | } | |
503 | ||
504 | int decompress_blob( | |
505 | Compression compression, | |
506 | const void *src, | |
507 | uint64_t src_size, | |
508 | void **dst, | |
509 | size_t* dst_size, | |
510 | size_t dst_max) { | |
511 | ||
512 | switch (compression) { | |
513 | case COMPRESSION_XZ: | |
514 | return decompress_blob_xz( | |
515 | src, src_size, | |
516 | dst, dst_size, dst_max); | |
517 | case COMPRESSION_LZ4: | |
518 | return decompress_blob_lz4( | |
519 | src, src_size, | |
520 | dst, dst_size, dst_max); | |
521 | case COMPRESSION_ZSTD: | |
522 | return decompress_blob_zstd( | |
523 | src, src_size, | |
524 | dst, dst_size, dst_max); | |
525 | default: | |
526 | return -EPROTONOSUPPORT; | |
527 | } | |
528 | } | |
529 | ||
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) { | |
537 | ||
538 | /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to | |
539 | * follow the prefix */ | |
540 | ||
541 | assert(src); | |
542 | assert(src_size > 0); | |
543 | assert(buffer); | |
544 | assert(prefix); | |
545 | ||
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); | |
557 | if (ret != LZMA_OK) | |
558 | return -EBADMSG; | |
559 | ||
560 | if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) | |
561 | return -ENOMEM; | |
562 | ||
563 | allocated = MALLOC_SIZEOF_SAFE(*buffer); | |
564 | ||
565 | s.next_in = src; | |
566 | s.avail_in = src_size; | |
567 | ||
568 | s.next_out = *buffer; | |
569 | s.avail_out = allocated; | |
570 | ||
571 | for (;;) { | |
572 | ret = sym_lzma_code(&s, LZMA_FINISH); | |
573 | ||
574 | if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) | |
575 | return -EBADMSG; | |
576 | ||
577 | if (allocated - s.avail_out >= prefix_len + 1) | |
578 | return memcmp(*buffer, prefix, prefix_len) == 0 && | |
579 | ((const uint8_t*) *buffer)[prefix_len] == extra; | |
580 | ||
581 | if (ret == LZMA_STREAM_END) | |
582 | return 0; | |
583 | ||
584 | s.avail_out += allocated; | |
585 | ||
586 | if (!(greedy_realloc(buffer, allocated * 2, 1))) | |
587 | return -ENOMEM; | |
588 | ||
589 | allocated = MALLOC_SIZEOF_SAFE(*buffer); | |
590 | s.next_out = *(uint8_t**)buffer + allocated - s.avail_out; | |
591 | } | |
592 | ||
593 | #else | |
594 | return -EPROTONOSUPPORT; | |
595 | #endif | |
596 | } | |
597 | ||
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 | ||
606 | /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to | |
607 | * follow the prefix */ | |
608 | ||
609 | assert(src); | |
610 | assert(src_size > 0); | |
611 | assert(buffer); | |
612 | assert(prefix); | |
613 | ||
614 | #if HAVE_LZ4 | |
615 | size_t allocated; | |
616 | int r; | |
617 | ||
618 | r = dlopen_lz4(); | |
619 | if (r < 0) | |
620 | return r; | |
621 | ||
622 | if (src_size <= 8) | |
623 | return -EBADMSG; | |
624 | ||
625 | if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) | |
626 | return -ENOMEM; | |
627 | allocated = MALLOC_SIZEOF_SAFE(*buffer); | |
628 | ||
629 | r = sym_LZ4_decompress_safe_partial( | |
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. | |
639 | */ | |
640 | if (r < 0 || (size_t) r < prefix_len + 1) { | |
641 | size_t size; | |
642 | ||
643 | if (sym_LZ4_versionNumber() >= 10803) | |
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. */ | |
656 | r = decompress_blob_lz4(src, src_size, buffer, &size, 0); | |
657 | if (r < 0) | |
658 | return r; | |
659 | ||
660 | if (size < prefix_len + 1) | |
661 | return 0; | |
662 | } | |
663 | ||
664 | return memcmp(*buffer, prefix, prefix_len) == 0 && | |
665 | ((const uint8_t*) *buffer)[prefix_len] == extra; | |
666 | #else | |
667 | return -EPROTONOSUPPORT; | |
668 | #endif | |
669 | } | |
670 | ||
671 | int decompress_startswith_zstd( | |
672 | const void *src, | |
673 | uint64_t src_size, | |
674 | void **buffer, | |
675 | const void *prefix, | |
676 | size_t prefix_len, | |
677 | uint8_t extra) { | |
678 | ||
679 | assert(src); | |
680 | assert(src_size > 0); | |
681 | assert(buffer); | |
682 | assert(prefix); | |
683 | ||
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); | |
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 | ||
698 | _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx(); | |
699 | if (!dctx) | |
700 | return -ENOMEM; | |
701 | ||
702 | if (!(greedy_realloc(buffer, MAX(sym_ZSTD_DStreamOutSize(), prefix_len + 1), 1))) | |
703 | return -ENOMEM; | |
704 | ||
705 | ZSTD_inBuffer input = { | |
706 | .src = src, | |
707 | .size = src_size, | |
708 | }; | |
709 | ZSTD_outBuffer output = { | |
710 | .dst = *buffer, | |
711 | .size = MALLOC_SIZEOF_SAFE(*buffer), | |
712 | }; | |
713 | size_t k; | |
714 | ||
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)); | |
718 | return zstd_ret_to_errno(k); | |
719 | } | |
720 | assert(output.pos >= prefix_len + 1); | |
721 | ||
722 | return memcmp(*buffer, prefix, prefix_len) == 0 && | |
723 | ((const uint8_t*) *buffer)[prefix_len] == extra; | |
724 | #else | |
725 | return -EPROTONOSUPPORT; | |
726 | #endif | |
727 | } | |
728 | ||
729 | int decompress_startswith( | |
730 | Compression compression, | |
731 | const void *src, | |
732 | uint64_t src_size, | |
733 | void **buffer, | |
734 | const void *prefix, | |
735 | size_t prefix_len, | |
736 | uint8_t extra) { | |
737 | ||
738 | switch (compression) { | |
739 | ||
740 | case COMPRESSION_XZ: | |
741 | return decompress_startswith_xz( | |
742 | src, src_size, | |
743 | buffer, | |
744 | prefix, prefix_len, | |
745 | extra); | |
746 | ||
747 | case COMPRESSION_LZ4: | |
748 | return decompress_startswith_lz4( | |
749 | src, src_size, | |
750 | buffer, | |
751 | prefix, prefix_len, | |
752 | extra); | |
753 | case COMPRESSION_ZSTD: | |
754 | return decompress_startswith_zstd( | |
755 | src, src_size, | |
756 | buffer, | |
757 | prefix, prefix_len, | |
758 | extra); | |
759 | default: | |
760 | return -EBADMSG; | |
761 | } | |
762 | } | |
763 | ||
764 | int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { | |
765 | assert(fdf >= 0); | |
766 | assert(fdt >= 0); | |
767 | ||
768 | #if HAVE_XZ | |
769 | _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; | |
770 | lzma_ret ret; | |
771 | uint8_t buf[BUFSIZ], out[BUFSIZ]; | |
772 | lzma_action action = LZMA_RUN; | |
773 | int r; | |
774 | ||
775 | r = dlopen_lzma(); | |
776 | if (r < 0) | |
777 | return r; | |
778 | ||
779 | ret = sym_lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); | |
780 | if (ret != LZMA_OK) | |
781 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
782 | "Failed to initialize XZ encoder: code %u", | |
783 | ret); | |
784 | ||
785 | for (;;) { | |
786 | if (s.avail_in == 0 && action == LZMA_RUN) { | |
787 | size_t m = sizeof(buf); | |
788 | ssize_t n; | |
789 | ||
790 | if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes) | |
791 | m = (size_t) max_bytes; | |
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 | ||
802 | if (max_bytes != UINT64_MAX) { | |
803 | assert(max_bytes >= (uint64_t) n); | |
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 | ||
814 | ret = sym_lzma_code(&s, action); | |
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); | |
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 | ||
825 | k = loop_write(fdt, out, n); | |
826 | if (k < 0) | |
827 | return k; | |
828 | ||
829 | if (ret == LZMA_STREAM_END) { | |
830 | if (ret_uncompressed_size) | |
831 | *ret_uncompressed_size = s.total_in; | |
832 | ||
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); | |
839 | ||
840 | return 0; | |
841 | } | |
842 | } | |
843 | } | |
844 | #else | |
845 | return -EPROTONOSUPPORT; | |
846 | #endif | |
847 | } | |
848 | ||
849 | #define LZ4_BUFSIZE (512*1024u) | |
850 | ||
851 | int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { | |
852 | ||
853 | #if HAVE_LZ4 | |
854 | LZ4F_errorCode_t c; | |
855 | _cleanup_(sym_LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; | |
856 | _cleanup_free_ void *in_buff = NULL; | |
857 | _cleanup_free_ char *out_buff = NULL; | |
858 | size_t out_allocsize, n, offset = 0, frame_size; | |
859 | uint64_t total_in = 0, total_out; | |
860 | int r; | |
861 | static const LZ4F_preferences_t preferences = { | |
862 | .frameInfo.blockSizeID = 5, | |
863 | }; | |
864 | ||
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)) | |
871 | return -ENOMEM; | |
872 | ||
873 | frame_size = sym_LZ4F_compressBound(LZ4_BUFSIZE, &preferences); | |
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) | |
881 | return -ENOMEM; | |
882 | ||
883 | n = offset = total_out = sym_LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences); | |
884 | if (sym_LZ4F_isError(n)) | |
885 | return -EINVAL; | |
886 | ||
887 | log_debug("Buffer size is %zu bytes, header size %zu bytes.", out_allocsize, n); | |
888 | ||
889 | for (;;) { | |
890 | ssize_t k; | |
891 | ||
892 | k = loop_read(fdf, in_buff, LZ4_BUFSIZE, true); | |
893 | if (k < 0) | |
894 | return k; | |
895 | if (k == 0) | |
896 | break; | |
897 | n = sym_LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset, | |
898 | in_buff, k, NULL); | |
899 | if (sym_LZ4F_isError(n)) | |
900 | return -ENOTRECOVERABLE; | |
901 | ||
902 | total_in += k; | |
903 | offset += n; | |
904 | total_out += n; | |
905 | ||
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); | |
909 | ||
910 | if (out_allocsize - offset < frame_size + 4) { | |
911 | k = loop_write(fdt, out_buff, offset); | |
912 | if (k < 0) | |
913 | return k; | |
914 | offset = 0; | |
915 | } | |
916 | } | |
917 | ||
918 | n = sym_LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL); | |
919 | if (sym_LZ4F_isError(n)) | |
920 | return -ENOTRECOVERABLE; | |
921 | ||
922 | offset += n; | |
923 | total_out += n; | |
924 | r = loop_write(fdt, out_buff, offset); | |
925 | if (r < 0) | |
926 | return r; | |
927 | ||
928 | if (ret_uncompressed_size) | |
929 | *ret_uncompressed_size = total_in; | |
930 | ||
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%%)", | |
935 | total_in, total_out, | |
936 | (double) total_out / total_in * 100); | |
937 | ||
938 | return 0; | |
939 | #else | |
940 | return -EPROTONOSUPPORT; | |
941 | #endif | |
942 | } | |
943 | ||
944 | int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { | |
945 | assert(fdf >= 0); | |
946 | assert(fdt >= 0); | |
947 | ||
948 | #if HAVE_XZ | |
949 | _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; | |
950 | lzma_ret ret; | |
951 | ||
952 | uint8_t buf[BUFSIZ], out[BUFSIZ]; | |
953 | lzma_action action = LZMA_RUN; | |
954 | int r; | |
955 | ||
956 | r = dlopen_lzma(); | |
957 | if (r < 0) | |
958 | return r; | |
959 | ||
960 | ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); | |
961 | if (ret != LZMA_OK) | |
962 | return log_debug_errno(SYNTHETIC_ERRNO(ENOMEM), | |
963 | "Failed to initialize XZ decoder: code %u", | |
964 | ret); | |
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 | ||
986 | ret = sym_lzma_code(&s, action); | |
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); | |
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 | ||
997 | if (max_bytes != UINT64_MAX) { | |
998 | if (max_bytes < (uint64_t) n) | |
999 | return -EFBIG; | |
1000 | ||
1001 | max_bytes -= n; | |
1002 | } | |
1003 | ||
1004 | k = loop_write(fdt, out, n); | |
1005 | if (k < 0) | |
1006 | return k; | |
1007 | ||
1008 | if (ret == LZMA_STREAM_END) { | |
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); | |
1015 | ||
1016 | return 0; | |
1017 | } | |
1018 | } | |
1019 | } | |
1020 | #else | |
1021 | return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), | |
1022 | "Cannot decompress file. Compiled without XZ support."); | |
1023 | #endif | |
1024 | } | |
1025 | ||
1026 | int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { | |
1027 | #if HAVE_LZ4 | |
1028 | size_t c; | |
1029 | _cleanup_(sym_LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; | |
1030 | _cleanup_free_ char *buf = NULL; | |
1031 | char *src; | |
1032 | struct stat st; | |
1033 | int r; | |
1034 | size_t total_in = 0, total_out = 0; | |
1035 | ||
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)) | |
1042 | return -ENOMEM; | |
1043 | ||
1044 | if (fstat(in, &st) < 0) | |
1045 | return log_debug_errno(errno, "fstat() failed: %m"); | |
1046 | ||
1047 | if (file_offset_beyond_memory_size(st.st_size)) | |
1048 | return -EFBIG; | |
1049 | ||
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); | |
1055 | if (src == MAP_FAILED) | |
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 | ||
1062 | c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); | |
1063 | if (sym_LZ4F_isError(c)) { | |
1064 | r = -EBADMSG; | |
1065 | goto cleanup; | |
1066 | } | |
1067 | ||
1068 | total_in += used; | |
1069 | total_out += produced; | |
1070 | ||
1071 | if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) { | |
1072 | log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes); | |
1073 | r = -EFBIG; | |
1074 | goto cleanup; | |
1075 | } | |
1076 | ||
1077 | r = loop_write(out, buf, produced); | |
1078 | if (r < 0) | |
1079 | goto cleanup; | |
1080 | } | |
1081 | ||
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); | |
1088 | r = 0; | |
1089 | cleanup: | |
1090 | munmap(src, st.st_size); | |
1091 | return r; | |
1092 | #else | |
1093 | return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), | |
1094 | "Cannot decompress file. Compiled without LZ4 support."); | |
1095 | #endif | |
1096 | } | |
1097 | ||
1098 | int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { | |
1099 | assert(fdf >= 0); | |
1100 | assert(fdt >= 0); | |
1101 | ||
1102 | #if HAVE_ZSTD | |
1103 | _cleanup_(sym_ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; | |
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; | |
1108 | int r; | |
1109 | ||
1110 | r = dlopen_zstd(); | |
1111 | if (r < 0) | |
1112 | return r; | |
1113 | ||
1114 | /* Create the context and buffers */ | |
1115 | in_allocsize = sym_ZSTD_CStreamInSize(); | |
1116 | out_allocsize = sym_ZSTD_CStreamOutSize(); | |
1117 | in_buff = malloc(in_allocsize); | |
1118 | out_buff = malloc(out_allocsize); | |
1119 | cctx = sym_ZSTD_createCCtx(); | |
1120 | if (!cctx || !out_buff || !in_buff) | |
1121 | return -ENOMEM; | |
1122 | ||
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)); | |
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 | */ | |
1160 | remaining = sym_ZSTD_compressStream2( | |
1161 | cctx, &output, &input, | |
1162 | is_last_chunk ? ZSTD_e_end : ZSTD_e_continue); | |
1163 | ||
1164 | if (sym_ZSTD_isError(remaining)) { | |
1165 | log_debug("ZSTD encoder failed: %s", sym_ZSTD_getErrorName(remaining)); | |
1166 | return zstd_ret_to_errno(remaining); | |
1167 | } | |
1168 | ||
1169 | if (left < output.pos) | |
1170 | return -EFBIG; | |
1171 | ||
1172 | wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); | |
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 | ||
1192 | if (ret_uncompressed_size) | |
1193 | *ret_uncompressed_size = in_bytes; | |
1194 | ||
1195 | if (in_bytes == 0) | |
1196 | log_debug("ZSTD compression finished (no input data)"); | |
1197 | else | |
1198 | log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", | |
1199 | in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100); | |
1200 | ||
1201 | return 0; | |
1202 | #else | |
1203 | return -EPROTONOSUPPORT; | |
1204 | #endif | |
1205 | } | |
1206 | ||
1207 | int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { | |
1208 | assert(fdf >= 0); | |
1209 | assert(fdt >= 0); | |
1210 | ||
1211 | #if HAVE_ZSTD | |
1212 | _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; | |
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; | |
1217 | int r; | |
1218 | ||
1219 | r = dlopen_zstd(); | |
1220 | if (r < 0) | |
1221 | return r; | |
1222 | /* Create the context and buffers */ | |
1223 | in_allocsize = sym_ZSTD_DStreamInSize(); | |
1224 | out_allocsize = sym_ZSTD_DStreamOutSize(); | |
1225 | in_buff = malloc(in_allocsize); | |
1226 | out_buff = malloc(out_allocsize); | |
1227 | dctx = sym_ZSTD_createDCtx(); | |
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 | */ | |
1276 | last_result = sym_ZSTD_decompressStream(dctx, &output, &input); | |
1277 | if (sym_ZSTD_isError(last_result)) { | |
1278 | has_error = true; | |
1279 | break; | |
1280 | } | |
1281 | ||
1282 | if (left < output.pos) | |
1283 | return -EFBIG; | |
1284 | ||
1285 | wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); | |
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 | */ | |
1303 | log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(last_result)); | |
1304 | return zstd_ret_to_errno(last_result); | |
1305 | } | |
1306 | ||
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); | |
1314 | return 0; | |
1315 | #else | |
1316 | return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), | |
1317 | "Cannot decompress file. Compiled without ZSTD support."); | |
1318 | #endif | |
1319 | } | |
1320 | ||
1321 | int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { | |
1322 | ||
1323 | if (endswith(filename, ".lz4")) | |
1324 | return decompress_stream_lz4(fdf, fdt, max_bytes); | |
1325 | if (endswith(filename, ".xz")) | |
1326 | return decompress_stream_xz(fdf, fdt, max_bytes); | |
1327 | if (endswith(filename, ".zst")) | |
1328 | return decompress_stream_zstd(fdf, fdt, max_bytes); | |
1329 | ||
1330 | return -EPROTONOSUPPORT; | |
1331 | } |