1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
36 #include "alloc-util.h"
40 #include "journal-def.h"
42 #include "sparse-endian.h"
43 #include "string-table.h"
44 #include "string-util.h"
48 DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_compressionContext_t
, LZ4F_freeCompressionContext
);
49 DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_decompressionContext_t
, LZ4F_freeDecompressionContext
);
52 #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t))
54 static const char* const object_compressed_table
[_OBJECT_COMPRESSED_MAX
] = {
55 [OBJECT_COMPRESSED_XZ
] = "XZ",
56 [OBJECT_COMPRESSED_LZ4
] = "LZ4",
59 DEFINE_STRING_TABLE_LOOKUP(object_compressed
, int);
61 int compress_blob_xz(const void *src
, uint64_t src_size
,
62 void *dst
, size_t dst_alloc_size
, size_t *dst_size
) {
64 static const lzma_options_lzma opt
= {
65 1u << 20u, NULL
, 0, LZMA_LC_DEFAULT
, LZMA_LP_DEFAULT
,
66 LZMA_PB_DEFAULT
, LZMA_MODE_FAST
, 128, LZMA_MF_HC3
, 4
68 static const lzma_filter filters
[] = {
69 { LZMA_FILTER_LZMA2
, (lzma_options_lzma
*) &opt
},
70 { LZMA_VLI_UNKNOWN
, NULL
}
78 assert(dst_alloc_size
> 0);
81 /* Returns < 0 if we couldn't compress the data or the
82 * compressed result is longer than the original */
87 ret
= lzma_stream_buffer_encode((lzma_filter
*) filters
, LZMA_CHECK_NONE
, NULL
,
88 src
, src_size
, dst
, &out_pos
, dst_alloc_size
);
95 return -EPROTONOSUPPORT
;
99 int compress_blob_lz4(const void *src
, uint64_t src_size
,
100 void *dst
, size_t dst_alloc_size
, size_t *dst_size
) {
105 assert(src_size
> 0);
107 assert(dst_alloc_size
> 0);
110 /* Returns < 0 if we couldn't compress the data or the
111 * compressed result is longer than the original */
116 r
= LZ4_compress_limitedOutput(src
, dst
+ 8, src_size
, (int) dst_alloc_size
- 8);
120 *(le64_t
*) dst
= htole64(src_size
);
125 return -EPROTONOSUPPORT
;
130 int decompress_blob_xz(const void *src
, uint64_t src_size
,
131 void **dst
, size_t *dst_alloc_size
, size_t* dst_size
, size_t dst_max
) {
134 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
139 assert(src_size
> 0);
141 assert(dst_alloc_size
);
143 assert(*dst_alloc_size
== 0 || *dst
);
145 ret
= lzma_stream_decoder(&s
, UINT64_MAX
, 0);
149 space
= MIN(src_size
* 2, dst_max
?: (size_t) -1);
150 if (!greedy_realloc(dst
, dst_alloc_size
, space
, 1))
154 s
.avail_in
= src_size
;
162 ret
= lzma_code(&s
, LZMA_FINISH
);
164 if (ret
== LZMA_STREAM_END
)
166 else if (ret
!= LZMA_OK
)
169 if (dst_max
> 0 && (space
- s
.avail_out
) >= dst_max
)
171 else if (dst_max
> 0 && space
== dst_max
)
174 used
= space
- s
.avail_out
;
175 space
= MIN(2 * space
, dst_max
?: (size_t) -1);
176 if (!greedy_realloc(dst
, dst_alloc_size
, space
, 1))
179 s
.avail_out
= space
- used
;
180 s
.next_out
= *dst
+ used
;
183 *dst_size
= space
- s
.avail_out
;
186 return -EPROTONOSUPPORT
;
190 int decompress_blob_lz4(const void *src
, uint64_t src_size
,
191 void **dst
, size_t *dst_alloc_size
, size_t* dst_size
, size_t dst_max
) {
195 int r
, size
; /* LZ4 uses int for size */
198 assert(src_size
> 0);
200 assert(dst_alloc_size
);
202 assert(*dst_alloc_size
== 0 || *dst
);
207 size
= le64toh( *(le64_t
*)src
);
208 if (size
< 0 || (unsigned) size
!= le64toh(*(le64_t
*)src
))
210 if ((size_t) size
> *dst_alloc_size
) {
211 out
= realloc(*dst
, size
);
215 *dst_alloc_size
= size
;
219 r
= LZ4_decompress_safe(src
+ 8, out
, src_size
- 8, size
);
220 if (r
< 0 || r
!= size
)
226 return -EPROTONOSUPPORT
;
230 int decompress_blob(int compression
,
231 const void *src
, uint64_t src_size
,
232 void **dst
, size_t *dst_alloc_size
, size_t* dst_size
, size_t dst_max
) {
233 if (compression
== OBJECT_COMPRESSED_XZ
)
234 return decompress_blob_xz(src
, src_size
,
235 dst
, dst_alloc_size
, dst_size
, dst_max
);
236 else if (compression
== OBJECT_COMPRESSED_LZ4
)
237 return decompress_blob_lz4(src
, src_size
,
238 dst
, dst_alloc_size
, dst_size
, dst_max
);
244 int decompress_startswith_xz(const void *src
, uint64_t src_size
,
245 void **buffer
, size_t *buffer_size
,
246 const void *prefix
, size_t prefix_len
,
250 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
253 /* Checks whether the decompressed blob starts with the
254 * mentioned prefix. The byte extra needs to follow the
258 assert(src_size
> 0);
262 assert(*buffer_size
== 0 || *buffer
);
264 ret
= lzma_stream_decoder(&s
, UINT64_MAX
, 0);
268 if (!(greedy_realloc(buffer
, buffer_size
, ALIGN_8(prefix_len
+ 1), 1)))
272 s
.avail_in
= src_size
;
274 s
.next_out
= *buffer
;
275 s
.avail_out
= *buffer_size
;
278 ret
= lzma_code(&s
, LZMA_FINISH
);
280 if (ret
!= LZMA_STREAM_END
&& ret
!= LZMA_OK
)
283 if (*buffer_size
- s
.avail_out
>= prefix_len
+ 1)
284 return memcmp(*buffer
, prefix
, prefix_len
) == 0 &&
285 ((const uint8_t*) *buffer
)[prefix_len
] == extra
;
287 if (ret
== LZMA_STREAM_END
)
290 s
.avail_out
+= *buffer_size
;
292 if (!(greedy_realloc(buffer
, buffer_size
, *buffer_size
* 2, 1)))
295 s
.next_out
= *buffer
+ *buffer_size
- s
.avail_out
;
299 return -EPROTONOSUPPORT
;
303 int decompress_startswith_lz4(const void *src
, uint64_t src_size
,
304 void **buffer
, size_t *buffer_size
,
305 const void *prefix
, size_t prefix_len
,
308 /* Checks whether the decompressed blob starts with the
309 * mentioned prefix. The byte extra needs to follow the
316 assert(src_size
> 0);
320 assert(*buffer_size
== 0 || *buffer
);
325 if (!(greedy_realloc(buffer
, buffer_size
, ALIGN_8(prefix_len
+ 1), 1)))
328 r
= LZ4_decompress_safe_partial(src
+ 8, *buffer
, src_size
- 8,
329 prefix_len
+ 1, *buffer_size
);
333 /* lz4 always tries to decode full "sequence", so in
334 * pathological cases might need to decompress the
336 r
= decompress_blob_lz4(src
, src_size
, buffer
, buffer_size
, &size
, 0);
341 if (size
>= prefix_len
+ 1)
342 return memcmp(*buffer
, prefix
, prefix_len
) == 0 &&
343 ((const uint8_t*) *buffer
)[prefix_len
] == extra
;
348 return -EPROTONOSUPPORT
;
352 int decompress_startswith(int compression
,
353 const void *src
, uint64_t src_size
,
354 void **buffer
, size_t *buffer_size
,
355 const void *prefix
, size_t prefix_len
,
357 if (compression
== OBJECT_COMPRESSED_XZ
)
358 return decompress_startswith_xz(src
, src_size
,
362 else if (compression
== OBJECT_COMPRESSED_LZ4
)
363 return decompress_startswith_lz4(src
, src_size
,
371 int compress_stream_xz(int fdf
, int fdt
, uint64_t max_bytes
) {
373 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
375 uint8_t buf
[BUFSIZ
], out
[BUFSIZ
];
376 lzma_action action
= LZMA_RUN
;
381 ret
= lzma_easy_encoder(&s
, LZMA_PRESET_DEFAULT
, LZMA_CHECK_CRC64
);
382 if (ret
!= LZMA_OK
) {
383 log_error("Failed to initialize XZ encoder: code %u", ret
);
388 if (s
.avail_in
== 0 && action
== LZMA_RUN
) {
389 size_t m
= sizeof(buf
);
392 if (max_bytes
!= (uint64_t) -1 && (uint64_t) m
> max_bytes
)
393 m
= (size_t) max_bytes
;
395 n
= read(fdf
, buf
, m
);
399 action
= LZMA_FINISH
;
404 if (max_bytes
!= (uint64_t) -1) {
405 assert(max_bytes
>= (uint64_t) n
);
411 if (s
.avail_out
== 0) {
413 s
.avail_out
= sizeof(out
);
416 ret
= lzma_code(&s
, action
);
417 if (ret
!= LZMA_OK
&& ret
!= LZMA_STREAM_END
) {
418 log_error("Compression failed: code %u", ret
);
422 if (s
.avail_out
== 0 || ret
== LZMA_STREAM_END
) {
425 n
= sizeof(out
) - s
.avail_out
;
427 k
= loop_write(fdt
, out
, n
, false);
431 if (ret
== LZMA_STREAM_END
) {
432 log_debug("XZ compression finished (%"PRIu64
" -> %"PRIu64
" bytes, %.1f%%)",
433 s
.total_in
, s
.total_out
,
434 (double) s
.total_out
/ s
.total_in
* 100);
441 return -EPROTONOSUPPORT
;
445 #define LZ4_BUFSIZE (512*1024u)
447 int compress_stream_lz4(int fdf
, int fdt
, uint64_t max_bytes
) {
451 _cleanup_(LZ4F_freeCompressionContextp
) LZ4F_compressionContext_t ctx
= NULL
;
452 _cleanup_free_
char *buf
= NULL
;
454 size_t size
, n
, total_in
= 0, total_out
, offset
= 0, frame_size
;
457 static const LZ4F_compressOptions_t options
= {
460 static const LZ4F_preferences_t preferences
= {
461 .frameInfo
.blockSizeID
= 5,
464 c
= LZ4F_createCompressionContext(&ctx
, LZ4F_VERSION
);
468 if (fstat(fdf
, &st
) < 0)
469 return log_debug_errno(errno
, "fstat() failed: %m");
471 frame_size
= LZ4F_compressBound(LZ4_BUFSIZE
, &preferences
);
472 size
= frame_size
+ 64*1024; /* add some space for header and trailer */
477 n
= offset
= total_out
= LZ4F_compressBegin(ctx
, buf
, size
, &preferences
);
481 src
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_PRIVATE
, fdf
, 0);
482 if (src
== MAP_FAILED
)
485 log_debug("Buffer size is %zu bytes, header size %zu bytes.", size
, n
);
487 while (total_in
< (size_t) st
.st_size
) {
490 k
= MIN(LZ4_BUFSIZE
, st
.st_size
- total_in
);
491 n
= LZ4F_compressUpdate(ctx
, buf
+ offset
, size
- offset
,
492 src
+ total_in
, k
, &options
);
493 if (LZ4F_isError(n
)) {
494 r
= -ENOTRECOVERABLE
;
502 if (max_bytes
!= (uint64_t) -1 && total_out
> (size_t) max_bytes
) {
503 log_debug("Compressed stream longer than %zd bytes", max_bytes
);
507 if (size
- offset
< frame_size
+ 4) {
508 k
= loop_write(fdt
, buf
, offset
, false);
517 n
= LZ4F_compressEnd(ctx
, buf
+ offset
, size
- offset
, &options
);
518 if (LZ4F_isError(n
)) {
519 r
= -ENOTRECOVERABLE
;
525 r
= loop_write(fdt
, buf
, offset
, false);
529 log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)",
531 (double) total_out
/ total_in
* 100);
533 munmap(src
, st
.st_size
);
536 return -EPROTONOSUPPORT
;
540 int decompress_stream_xz(int fdf
, int fdt
, uint64_t max_bytes
) {
543 _cleanup_(lzma_end
) lzma_stream s
= LZMA_STREAM_INIT
;
546 uint8_t buf
[BUFSIZ
], out
[BUFSIZ
];
547 lzma_action action
= LZMA_RUN
;
552 ret
= lzma_stream_decoder(&s
, UINT64_MAX
, 0);
553 if (ret
!= LZMA_OK
) {
554 log_debug("Failed to initialize XZ decoder: code %u", ret
);
559 if (s
.avail_in
== 0 && action
== LZMA_RUN
) {
562 n
= read(fdf
, buf
, sizeof(buf
));
566 action
= LZMA_FINISH
;
573 if (s
.avail_out
== 0) {
575 s
.avail_out
= sizeof(out
);
578 ret
= lzma_code(&s
, action
);
579 if (ret
!= LZMA_OK
&& ret
!= LZMA_STREAM_END
) {
580 log_debug("Decompression failed: code %u", ret
);
584 if (s
.avail_out
== 0 || ret
== LZMA_STREAM_END
) {
587 n
= sizeof(out
) - s
.avail_out
;
589 if (max_bytes
!= (uint64_t) -1) {
590 if (max_bytes
< (uint64_t) n
)
596 k
= loop_write(fdt
, out
, n
, false);
600 if (ret
== LZMA_STREAM_END
) {
601 log_debug("XZ decompression finished (%"PRIu64
" -> %"PRIu64
" bytes, %.1f%%)",
602 s
.total_in
, s
.total_out
,
603 (double) s
.total_out
/ s
.total_in
* 100);
610 log_debug("Cannot decompress file. Compiled without XZ support.");
611 return -EPROTONOSUPPORT
;
616 static int decompress_stream_lz4_v1(int fdf
, int fdt
, uint64_t max_bytes
) {
618 _cleanup_free_
char *buf
= NULL
, *out
= NULL
;
620 LZ4_streamDecode_t lz4_data
= {};
622 size_t total_in
= sizeof(header
), total_out
= 0;
627 out
= malloc(4*LZ4_BUFSIZE
);
635 r
= loop_read_exact(fdf
, &header
, sizeof(header
), false);
643 /* We refuse to use a bigger decompression buffer than
644 * the one used for compression by 4 times. This means
645 * that compression buffer size can be enlarged 4
646 * times. This can be changed, but old binaries might
647 * not accept buffers compressed by newer binaries then.
649 if (m
> LZ4_COMPRESSBOUND(LZ4_BUFSIZE
* 4)) {
650 log_debug("Compressed stream block too big: %zd bytes", m
);
654 total_in
+= sizeof(header
) + m
;
656 if (!GREEDY_REALLOC(buf
, buf_size
, m
))
659 r
= loop_read_exact(fdf
, buf
, m
, false);
663 r
= LZ4_decompress_safe_continue(&lz4_data
, buf
, out
, m
, 4*LZ4_BUFSIZE
);
665 log_debug("LZ4 decompression failed (legacy format).");
671 if (max_bytes
!= (uint64_t) -1 && (uint64_t) total_out
> max_bytes
) {
672 log_debug("Decompressed stream longer than %" PRIu64
" bytes", max_bytes
);
676 r
= loop_write(fdt
, out
, r
, false);
681 log_debug("LZ4 decompression finished (legacy format, %zu -> %zu bytes, %.1f%%)",
683 (double) total_out
/ total_in
* 100);
688 static int decompress_stream_lz4_v2(int in
, int out
, uint64_t max_bytes
) {
690 _cleanup_(LZ4F_freeDecompressionContextp
) LZ4F_decompressionContext_t ctx
= NULL
;
691 _cleanup_free_
char *buf
= NULL
;
695 size_t total_in
= 0, total_out
= 0;
697 c
= LZ4F_createDecompressionContext(&ctx
, LZ4F_VERSION
);
701 if (fstat(in
, &st
) < 0)
702 return log_debug_errno(errno
, "fstat() failed: %m");
704 buf
= malloc(LZ4_BUFSIZE
);
708 src
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_PRIVATE
, in
, 0);
709 if (src
== MAP_FAILED
)
712 while (total_in
< (size_t) st
.st_size
) {
713 size_t produced
= LZ4_BUFSIZE
;
714 size_t used
= st
.st_size
- total_in
;
716 c
= LZ4F_decompress(ctx
, buf
, &produced
, src
+ total_in
, &used
, NULL
);
717 if (LZ4F_isError(c
)) {
723 total_out
+= produced
;
725 if (max_bytes
!= (uint64_t) -1 && total_out
> (size_t) max_bytes
) {
726 log_debug("Decompressed stream longer than %zd bytes", max_bytes
);
731 r
= loop_write(out
, buf
, produced
, false);
736 log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)",
738 (double) total_out
/ total_in
* 100);
740 munmap(src
, st
.st_size
);
745 int decompress_stream_lz4(int fdf
, int fdt
, uint64_t max_bytes
) {
749 r
= decompress_stream_lz4_v2(fdf
, fdt
, max_bytes
);
751 r
= decompress_stream_lz4_v1(fdf
, fdt
, max_bytes
);
754 log_debug("Cannot decompress file. Compiled without LZ4 support.");
755 return -EPROTONOSUPPORT
;
759 int decompress_stream(const char *filename
, int fdf
, int fdt
, uint64_t max_bytes
) {
761 if (endswith(filename
, ".lz4"))
762 return decompress_stream_lz4(fdf
, fdt
, max_bytes
);
763 else if (endswith(filename
, ".xz"))
764 return decompress_stream_xz(fdf
, fdt
, max_bytes
);
766 return -EPROTONOSUPPORT
;