1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright 2011 Lennart Poettering
21 #include "alloc-util.h"
25 #include "journal-def.h"
27 #include "sparse-endian.h"
28 #include "string-table.h"
29 #include "string-util.h"
33 DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_compressionContext_t
, LZ4F_freeCompressionContext
);
34 DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_decompressionContext_t
, LZ4F_freeDecompressionContext
);
37 #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t))
39 static const char* const object_compressed_table
[_OBJECT_COMPRESSED_MAX
] = {
40 [OBJECT_COMPRESSED_XZ
] = "XZ",
41 [OBJECT_COMPRESSED_LZ4
] = "LZ4",
44 DEFINE_STRING_TABLE_LOOKUP(object_compressed
, int);
46 int compress_blob_xz(const void *src
, uint64_t src_size
,
47 void *dst
, size_t dst_alloc_size
, size_t *dst_size
) {
49 static const lzma_options_lzma opt
= {
50 1u << 20u, NULL
, 0, LZMA_LC_DEFAULT
, LZMA_LP_DEFAULT
,
51 LZMA_PB_DEFAULT
, LZMA_MODE_FAST
, 128, LZMA_MF_HC3
, 4
53 static const lzma_filter filters
[] = {
54 { LZMA_FILTER_LZMA2
, (lzma_options_lzma
*) &opt
},
55 { LZMA_VLI_UNKNOWN
, NULL
}
63 assert(dst_alloc_size
> 0);
66 /* Returns < 0 if we couldn't compress the data or the
67 * compressed result is longer than the original */
72 ret
= lzma_stream_buffer_encode((lzma_filter
*) filters
, LZMA_CHECK_NONE
, NULL
,
73 src
, src_size
, dst
, &out_pos
, dst_alloc_size
);
80 return -EPROTONOSUPPORT
;
84 int compress_blob_lz4(const void *src
, uint64_t src_size
,
85 void *dst
, size_t dst_alloc_size
, size_t *dst_size
) {
92 assert(dst_alloc_size
> 0);
95 /* Returns < 0 if we couldn't compress the data or the
96 * compressed result is longer than the original */
101 #if LZ4_VERSION_NUMBER >= 10700
102 r
= LZ4_compress_default(src
, (char*)dst
+ 8, src_size
, (int) dst_alloc_size
- 8);
104 r
= LZ4_compress_limitedOutput(src
, (char*)dst
+ 8, src_size
, (int) dst_alloc_size
- 8);
109 *(le64_t
*) dst
= htole64(src_size
);
114 return -EPROTONOSUPPORT
;
118 int decompress_blob_xz(const void *src
, uint64_t src_size
,
119 void **dst
, size_t *dst_alloc_size
, size_t* dst_size
, size_t dst_max
) {
122 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
127 assert(src_size
> 0);
129 assert(dst_alloc_size
);
131 assert(*dst_alloc_size
== 0 || *dst
);
133 ret
= lzma_stream_decoder(&s
, UINT64_MAX
, 0);
137 space
= MIN(src_size
* 2, dst_max
?: (size_t) -1);
138 if (!greedy_realloc(dst
, dst_alloc_size
, space
, 1))
142 s
.avail_in
= src_size
;
150 ret
= lzma_code(&s
, LZMA_FINISH
);
152 if (ret
== LZMA_STREAM_END
)
154 else if (ret
!= LZMA_OK
)
157 if (dst_max
> 0 && (space
- s
.avail_out
) >= dst_max
)
159 else if (dst_max
> 0 && space
== dst_max
)
162 used
= space
- s
.avail_out
;
163 space
= MIN(2 * space
, dst_max
?: (size_t) -1);
164 if (!greedy_realloc(dst
, dst_alloc_size
, space
, 1))
167 s
.avail_out
= space
- used
;
168 s
.next_out
= *(uint8_t**)dst
+ used
;
171 *dst_size
= space
- s
.avail_out
;
174 return -EPROTONOSUPPORT
;
178 int decompress_blob_lz4(const void *src
, uint64_t src_size
,
179 void **dst
, size_t *dst_alloc_size
, size_t* dst_size
, size_t dst_max
) {
183 int r
, size
; /* LZ4 uses int for size */
186 assert(src_size
> 0);
188 assert(dst_alloc_size
);
190 assert(*dst_alloc_size
== 0 || *dst
);
195 size
= le64toh( *(le64_t
*)src
);
196 if (size
< 0 || (unsigned) size
!= le64toh(*(le64_t
*)src
))
198 if ((size_t) size
> *dst_alloc_size
) {
199 out
= realloc(*dst
, size
);
203 *dst_alloc_size
= size
;
207 r
= LZ4_decompress_safe((char*)src
+ 8, out
, src_size
- 8, size
);
208 if (r
< 0 || r
!= size
)
214 return -EPROTONOSUPPORT
;
218 int decompress_blob(int compression
,
219 const void *src
, uint64_t src_size
,
220 void **dst
, size_t *dst_alloc_size
, size_t* dst_size
, size_t dst_max
) {
221 if (compression
== OBJECT_COMPRESSED_XZ
)
222 return decompress_blob_xz(src
, src_size
,
223 dst
, dst_alloc_size
, dst_size
, dst_max
);
224 else if (compression
== OBJECT_COMPRESSED_LZ4
)
225 return decompress_blob_lz4(src
, src_size
,
226 dst
, dst_alloc_size
, dst_size
, dst_max
);
231 int decompress_startswith_xz(const void *src
, uint64_t src_size
,
232 void **buffer
, size_t *buffer_size
,
233 const void *prefix
, size_t prefix_len
,
237 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
240 /* Checks whether the decompressed blob starts with the
241 * mentioned prefix. The byte extra needs to follow the
245 assert(src_size
> 0);
249 assert(*buffer_size
== 0 || *buffer
);
251 ret
= lzma_stream_decoder(&s
, UINT64_MAX
, 0);
255 if (!(greedy_realloc(buffer
, buffer_size
, ALIGN_8(prefix_len
+ 1), 1)))
259 s
.avail_in
= src_size
;
261 s
.next_out
= *buffer
;
262 s
.avail_out
= *buffer_size
;
265 ret
= lzma_code(&s
, LZMA_FINISH
);
267 if (!IN_SET(ret
, LZMA_OK
, LZMA_STREAM_END
))
270 if (*buffer_size
- s
.avail_out
>= prefix_len
+ 1)
271 return memcmp(*buffer
, prefix
, prefix_len
) == 0 &&
272 ((const uint8_t*) *buffer
)[prefix_len
] == extra
;
274 if (ret
== LZMA_STREAM_END
)
277 s
.avail_out
+= *buffer_size
;
279 if (!(greedy_realloc(buffer
, buffer_size
, *buffer_size
* 2, 1)))
282 s
.next_out
= *(uint8_t**)buffer
+ *buffer_size
- s
.avail_out
;
286 return -EPROTONOSUPPORT
;
290 int decompress_startswith_lz4(const void *src
, uint64_t src_size
,
291 void **buffer
, size_t *buffer_size
,
292 const void *prefix
, size_t prefix_len
,
295 /* Checks whether the decompressed blob starts with the
296 * mentioned prefix. The byte extra needs to follow the
303 assert(src_size
> 0);
307 assert(*buffer_size
== 0 || *buffer
);
312 if (!(greedy_realloc(buffer
, buffer_size
, ALIGN_8(prefix_len
+ 1), 1)))
315 r
= LZ4_decompress_safe_partial((char*)src
+ 8, *buffer
, src_size
- 8,
316 prefix_len
+ 1, *buffer_size
);
320 /* lz4 always tries to decode full "sequence", so in
321 * pathological cases might need to decompress the
323 r
= decompress_blob_lz4(src
, src_size
, buffer
, buffer_size
, &size
, 0);
328 if (size
>= prefix_len
+ 1)
329 return memcmp(*buffer
, prefix
, prefix_len
) == 0 &&
330 ((const uint8_t*) *buffer
)[prefix_len
] == extra
;
335 return -EPROTONOSUPPORT
;
339 int decompress_startswith(int compression
,
340 const void *src
, uint64_t src_size
,
341 void **buffer
, size_t *buffer_size
,
342 const void *prefix
, size_t prefix_len
,
344 if (compression
== OBJECT_COMPRESSED_XZ
)
345 return decompress_startswith_xz(src
, src_size
,
349 else if (compression
== OBJECT_COMPRESSED_LZ4
)
350 return decompress_startswith_lz4(src
, src_size
,
358 int compress_stream_xz(int fdf
, int fdt
, uint64_t max_bytes
) {
360 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
362 uint8_t buf
[BUFSIZ
], out
[BUFSIZ
];
363 lzma_action action
= LZMA_RUN
;
368 ret
= lzma_easy_encoder(&s
, LZMA_PRESET_DEFAULT
, LZMA_CHECK_CRC64
);
369 if (ret
!= LZMA_OK
) {
370 log_error("Failed to initialize XZ encoder: code %u", ret
);
375 if (s
.avail_in
== 0 && action
== LZMA_RUN
) {
376 size_t m
= sizeof(buf
);
379 if (max_bytes
!= (uint64_t) -1 && (uint64_t) m
> max_bytes
)
380 m
= (size_t) max_bytes
;
382 n
= read(fdf
, buf
, m
);
386 action
= LZMA_FINISH
;
391 if (max_bytes
!= (uint64_t) -1) {
392 assert(max_bytes
>= (uint64_t) n
);
398 if (s
.avail_out
== 0) {
400 s
.avail_out
= sizeof(out
);
403 ret
= lzma_code(&s
, action
);
404 if (!IN_SET(ret
, LZMA_OK
, LZMA_STREAM_END
)) {
405 log_error("Compression failed: code %u", ret
);
409 if (s
.avail_out
== 0 || ret
== LZMA_STREAM_END
) {
412 n
= sizeof(out
) - s
.avail_out
;
414 k
= loop_write(fdt
, out
, n
, false);
418 if (ret
== LZMA_STREAM_END
) {
419 log_debug("XZ compression finished (%"PRIu64
" -> %"PRIu64
" bytes, %.1f%%)",
420 s
.total_in
, s
.total_out
,
421 (double) s
.total_out
/ s
.total_in
* 100);
428 return -EPROTONOSUPPORT
;
432 #define LZ4_BUFSIZE (512*1024u)
434 int compress_stream_lz4(int fdf
, int fdt
, uint64_t max_bytes
) {
438 _cleanup_(LZ4F_freeCompressionContextp
) LZ4F_compressionContext_t ctx
= NULL
;
439 _cleanup_free_
char *buf
= NULL
;
441 size_t size
, n
, total_in
= 0, total_out
, offset
= 0, frame_size
;
444 static const LZ4F_compressOptions_t options
= {
447 static const LZ4F_preferences_t preferences
= {
448 .frameInfo
.blockSizeID
= 5,
451 c
= LZ4F_createCompressionContext(&ctx
, LZ4F_VERSION
);
455 if (fstat(fdf
, &st
) < 0)
456 return log_debug_errno(errno
, "fstat() failed: %m");
458 frame_size
= LZ4F_compressBound(LZ4_BUFSIZE
, &preferences
);
459 size
= frame_size
+ 64*1024; /* add some space for header and trailer */
464 n
= offset
= total_out
= LZ4F_compressBegin(ctx
, buf
, size
, &preferences
);
468 src
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_PRIVATE
, fdf
, 0);
469 if (src
== MAP_FAILED
)
472 log_debug("Buffer size is %zu bytes, header size %zu bytes.", size
, n
);
474 while (total_in
< (size_t) st
.st_size
) {
477 k
= MIN(LZ4_BUFSIZE
, st
.st_size
- total_in
);
478 n
= LZ4F_compressUpdate(ctx
, buf
+ offset
, size
- offset
,
479 src
+ total_in
, k
, &options
);
480 if (LZ4F_isError(n
)) {
481 r
= -ENOTRECOVERABLE
;
489 if (max_bytes
!= (uint64_t) -1 && total_out
> (size_t) max_bytes
) {
490 log_debug("Compressed stream longer than %"PRIu64
" bytes", max_bytes
);
494 if (size
- offset
< frame_size
+ 4) {
495 k
= loop_write(fdt
, buf
, offset
, false);
504 n
= LZ4F_compressEnd(ctx
, buf
+ offset
, size
- offset
, &options
);
505 if (LZ4F_isError(n
)) {
506 r
= -ENOTRECOVERABLE
;
512 r
= loop_write(fdt
, buf
, offset
, false);
516 log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)",
518 (double) total_out
/ total_in
* 100);
520 munmap(src
, st
.st_size
);
523 return -EPROTONOSUPPORT
;
527 int decompress_stream_xz(int fdf
, int fdt
, uint64_t max_bytes
) {
530 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
533 uint8_t buf
[BUFSIZ
], out
[BUFSIZ
];
534 lzma_action action
= LZMA_RUN
;
539 ret
= lzma_stream_decoder(&s
, UINT64_MAX
, 0);
540 if (ret
!= LZMA_OK
) {
541 log_debug("Failed to initialize XZ decoder: code %u", ret
);
546 if (s
.avail_in
== 0 && action
== LZMA_RUN
) {
549 n
= read(fdf
, buf
, sizeof(buf
));
553 action
= LZMA_FINISH
;
560 if (s
.avail_out
== 0) {
562 s
.avail_out
= sizeof(out
);
565 ret
= lzma_code(&s
, action
);
566 if (!IN_SET(ret
, LZMA_OK
, LZMA_STREAM_END
)) {
567 log_debug("Decompression failed: code %u", ret
);
571 if (s
.avail_out
== 0 || ret
== LZMA_STREAM_END
) {
574 n
= sizeof(out
) - s
.avail_out
;
576 if (max_bytes
!= (uint64_t) -1) {
577 if (max_bytes
< (uint64_t) n
)
583 k
= loop_write(fdt
, out
, n
, false);
587 if (ret
== LZMA_STREAM_END
) {
588 log_debug("XZ decompression finished (%"PRIu64
" -> %"PRIu64
" bytes, %.1f%%)",
589 s
.total_in
, s
.total_out
,
590 (double) s
.total_out
/ s
.total_in
* 100);
597 log_debug("Cannot decompress file. Compiled without XZ support.");
598 return -EPROTONOSUPPORT
;
602 int decompress_stream_lz4(int in
, int out
, uint64_t max_bytes
) {
605 _cleanup_(LZ4F_freeDecompressionContextp
) LZ4F_decompressionContext_t ctx
= NULL
;
606 _cleanup_free_
char *buf
= NULL
;
610 size_t total_in
= 0, total_out
= 0;
612 c
= LZ4F_createDecompressionContext(&ctx
, LZ4F_VERSION
);
616 if (fstat(in
, &st
) < 0)
617 return log_debug_errno(errno
, "fstat() failed: %m");
619 buf
= malloc(LZ4_BUFSIZE
);
623 src
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_PRIVATE
, in
, 0);
624 if (src
== MAP_FAILED
)
627 while (total_in
< (size_t) st
.st_size
) {
628 size_t produced
= LZ4_BUFSIZE
;
629 size_t used
= st
.st_size
- total_in
;
631 c
= LZ4F_decompress(ctx
, buf
, &produced
, src
+ total_in
, &used
, NULL
);
632 if (LZ4F_isError(c
)) {
638 total_out
+= produced
;
640 if (max_bytes
!= (uint64_t) -1 && total_out
> (size_t) max_bytes
) {
641 log_debug("Decompressed stream longer than %"PRIu64
" bytes", max_bytes
);
646 r
= loop_write(out
, buf
, produced
, false);
651 log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)",
653 total_in
> 0 ? (double) total_out
/ total_in
* 100 : 0.0);
655 munmap(src
, st
.st_size
);
658 log_debug("Cannot decompress file. Compiled without LZ4 support.");
659 return -EPROTONOSUPPORT
;
663 int decompress_stream(const char *filename
, int fdf
, int fdt
, uint64_t max_bytes
) {
665 if (endswith(filename
, ".lz4"))
666 return decompress_stream_lz4(fdf
, fdt
, max_bytes
);
667 else if (endswith(filename
, ".xz"))
668 return decompress_stream_xz(fdf
, fdt
, max_bytes
);
670 return -EPROTONOSUPPORT
;