]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/compress.c
tree-wide: drop 'This file is part of systemd' blurb
[thirdparty/systemd.git] / src / journal / compress.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright 2011 Lennart Poettering
4 ***/
5
6 #include <inttypes.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/mman.h>
10 #include <unistd.h>
11
12 #if HAVE_XZ
13 #include <lzma.h>
14 #endif
15
16 #if HAVE_LZ4
17 #include <lz4.h>
18 #include <lz4frame.h>
19 #endif
20
21 #include "alloc-util.h"
22 #include "compress.h"
23 #include "fd-util.h"
24 #include "io-util.h"
25 #include "journal-def.h"
26 #include "macro.h"
27 #include "sparse-endian.h"
28 #include "string-table.h"
29 #include "string-util.h"
30 #include "util.h"
31
32 #if HAVE_LZ4
33 DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_compressionContext_t, LZ4F_freeCompressionContext);
34 DEFINE_TRIVIAL_CLEANUP_FUNC(LZ4F_decompressionContext_t, LZ4F_freeDecompressionContext);
35 #endif
36
37 #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t))
38
39 static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = {
40 [OBJECT_COMPRESSED_XZ] = "XZ",
41 [OBJECT_COMPRESSED_LZ4] = "LZ4",
42 };
43
44 DEFINE_STRING_TABLE_LOOKUP(object_compressed, int);
45
46 int compress_blob_xz(const void *src, uint64_t src_size,
47 void *dst, size_t dst_alloc_size, size_t *dst_size) {
48 #if HAVE_XZ
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
52 };
53 static const lzma_filter filters[] = {
54 { LZMA_FILTER_LZMA2, (lzma_options_lzma*) &opt },
55 { LZMA_VLI_UNKNOWN, NULL }
56 };
57 lzma_ret ret;
58 size_t out_pos = 0;
59
60 assert(src);
61 assert(src_size > 0);
62 assert(dst);
63 assert(dst_alloc_size > 0);
64 assert(dst_size);
65
66 /* Returns < 0 if we couldn't compress the data or the
67 * compressed result is longer than the original */
68
69 if (src_size < 80)
70 return -ENOBUFS;
71
72 ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL,
73 src, src_size, dst, &out_pos, dst_alloc_size);
74 if (ret != LZMA_OK)
75 return -ENOBUFS;
76
77 *dst_size = out_pos;
78 return 0;
79 #else
80 return -EPROTONOSUPPORT;
81 #endif
82 }
83
84 int compress_blob_lz4(const void *src, uint64_t src_size,
85 void *dst, size_t dst_alloc_size, size_t *dst_size) {
86 #if HAVE_LZ4
87 int r;
88
89 assert(src);
90 assert(src_size > 0);
91 assert(dst);
92 assert(dst_alloc_size > 0);
93 assert(dst_size);
94
95 /* Returns < 0 if we couldn't compress the data or the
96 * compressed result is longer than the original */
97
98 if (src_size < 9)
99 return -ENOBUFS;
100
101 #if LZ4_VERSION_NUMBER >= 10700
102 r = LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8);
103 #else
104 r = LZ4_compress_limitedOutput(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8);
105 #endif
106 if (r <= 0)
107 return -ENOBUFS;
108
109 *(le64_t*) dst = htole64(src_size);
110 *dst_size = r + 8;
111
112 return 0;
113 #else
114 return -EPROTONOSUPPORT;
115 #endif
116 }
117
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) {
120
121 #if HAVE_XZ
122 _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
123 lzma_ret ret;
124 size_t space;
125
126 assert(src);
127 assert(src_size > 0);
128 assert(dst);
129 assert(dst_alloc_size);
130 assert(dst_size);
131 assert(*dst_alloc_size == 0 || *dst);
132
133 ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
134 if (ret != LZMA_OK)
135 return -ENOMEM;
136
137 space = MIN(src_size * 2, dst_max ?: (size_t) -1);
138 if (!greedy_realloc(dst, dst_alloc_size, space, 1))
139 return -ENOMEM;
140
141 s.next_in = src;
142 s.avail_in = src_size;
143
144 s.next_out = *dst;
145 s.avail_out = space;
146
147 for (;;) {
148 size_t used;
149
150 ret = lzma_code(&s, LZMA_FINISH);
151
152 if (ret == LZMA_STREAM_END)
153 break;
154 else if (ret != LZMA_OK)
155 return -ENOMEM;
156
157 if (dst_max > 0 && (space - s.avail_out) >= dst_max)
158 break;
159 else if (dst_max > 0 && space == dst_max)
160 return -ENOBUFS;
161
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))
165 return -ENOMEM;
166
167 s.avail_out = space - used;
168 s.next_out = *(uint8_t**)dst + used;
169 }
170
171 *dst_size = space - s.avail_out;
172 return 0;
173 #else
174 return -EPROTONOSUPPORT;
175 #endif
176 }
177
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) {
180
181 #if HAVE_LZ4
182 char* out;
183 int r, size; /* LZ4 uses int for size */
184
185 assert(src);
186 assert(src_size > 0);
187 assert(dst);
188 assert(dst_alloc_size);
189 assert(dst_size);
190 assert(*dst_alloc_size == 0 || *dst);
191
192 if (src_size <= 8)
193 return -EBADMSG;
194
195 size = le64toh( *(le64_t*)src );
196 if (size < 0 || (unsigned) size != le64toh(*(le64_t*)src))
197 return -EFBIG;
198 if ((size_t) size > *dst_alloc_size) {
199 out = realloc(*dst, size);
200 if (!out)
201 return -ENOMEM;
202 *dst = out;
203 *dst_alloc_size = size;
204 } else
205 out = *dst;
206
207 r = LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size);
208 if (r < 0 || r != size)
209 return -EBADMSG;
210
211 *dst_size = size;
212 return 0;
213 #else
214 return -EPROTONOSUPPORT;
215 #endif
216 }
217
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);
227 else
228 return -EBADMSG;
229 }
230
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,
234 uint8_t extra) {
235
236 #if HAVE_XZ
237 _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
238 lzma_ret ret;
239
240 /* Checks whether the decompressed blob starts with the
241 * mentioned prefix. The byte extra needs to follow the
242 * prefix */
243
244 assert(src);
245 assert(src_size > 0);
246 assert(buffer);
247 assert(buffer_size);
248 assert(prefix);
249 assert(*buffer_size == 0 || *buffer);
250
251 ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
252 if (ret != LZMA_OK)
253 return -EBADMSG;
254
255 if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1)))
256 return -ENOMEM;
257
258 s.next_in = src;
259 s.avail_in = src_size;
260
261 s.next_out = *buffer;
262 s.avail_out = *buffer_size;
263
264 for (;;) {
265 ret = lzma_code(&s, LZMA_FINISH);
266
267 if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END))
268 return -EBADMSG;
269
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;
273
274 if (ret == LZMA_STREAM_END)
275 return 0;
276
277 s.avail_out += *buffer_size;
278
279 if (!(greedy_realloc(buffer, buffer_size, *buffer_size * 2, 1)))
280 return -ENOMEM;
281
282 s.next_out = *(uint8_t**)buffer + *buffer_size - s.avail_out;
283 }
284
285 #else
286 return -EPROTONOSUPPORT;
287 #endif
288 }
289
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,
293 uint8_t extra) {
294 #if HAVE_LZ4
295 /* Checks whether the decompressed blob starts with the
296 * mentioned prefix. The byte extra needs to follow the
297 * prefix */
298
299 int r;
300 size_t size;
301
302 assert(src);
303 assert(src_size > 0);
304 assert(buffer);
305 assert(buffer_size);
306 assert(prefix);
307 assert(*buffer_size == 0 || *buffer);
308
309 if (src_size <= 8)
310 return -EBADMSG;
311
312 if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1)))
313 return -ENOMEM;
314
315 r = LZ4_decompress_safe_partial((char*)src + 8, *buffer, src_size - 8,
316 prefix_len + 1, *buffer_size);
317 if (r >= 0)
318 size = (unsigned) r;
319 else {
320 /* lz4 always tries to decode full "sequence", so in
321 * pathological cases might need to decompress the
322 * full field. */
323 r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0);
324 if (r < 0)
325 return r;
326 }
327
328 if (size >= prefix_len + 1)
329 return memcmp(*buffer, prefix, prefix_len) == 0 &&
330 ((const uint8_t*) *buffer)[prefix_len] == extra;
331 else
332 return 0;
333
334 #else
335 return -EPROTONOSUPPORT;
336 #endif
337 }
338
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,
343 uint8_t extra) {
344 if (compression == OBJECT_COMPRESSED_XZ)
345 return decompress_startswith_xz(src, src_size,
346 buffer, buffer_size,
347 prefix, prefix_len,
348 extra);
349 else if (compression == OBJECT_COMPRESSED_LZ4)
350 return decompress_startswith_lz4(src, src_size,
351 buffer, buffer_size,
352 prefix, prefix_len,
353 extra);
354 else
355 return -EBADMSG;
356 }
357
358 int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) {
359 #if HAVE_XZ
360 _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
361 lzma_ret ret;
362 uint8_t buf[BUFSIZ], out[BUFSIZ];
363 lzma_action action = LZMA_RUN;
364
365 assert(fdf >= 0);
366 assert(fdt >= 0);
367
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);
371 return -EINVAL;
372 }
373
374 for (;;) {
375 if (s.avail_in == 0 && action == LZMA_RUN) {
376 size_t m = sizeof(buf);
377 ssize_t n;
378
379 if (max_bytes != (uint64_t) -1 && (uint64_t) m > max_bytes)
380 m = (size_t) max_bytes;
381
382 n = read(fdf, buf, m);
383 if (n < 0)
384 return -errno;
385 if (n == 0)
386 action = LZMA_FINISH;
387 else {
388 s.next_in = buf;
389 s.avail_in = n;
390
391 if (max_bytes != (uint64_t) -1) {
392 assert(max_bytes >= (uint64_t) n);
393 max_bytes -= n;
394 }
395 }
396 }
397
398 if (s.avail_out == 0) {
399 s.next_out = out;
400 s.avail_out = sizeof(out);
401 }
402
403 ret = lzma_code(&s, action);
404 if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) {
405 log_error("Compression failed: code %u", ret);
406 return -EBADMSG;
407 }
408
409 if (s.avail_out == 0 || ret == LZMA_STREAM_END) {
410 ssize_t n, k;
411
412 n = sizeof(out) - s.avail_out;
413
414 k = loop_write(fdt, out, n, false);
415 if (k < 0)
416 return k;
417
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);
422
423 return 0;
424 }
425 }
426 }
427 #else
428 return -EPROTONOSUPPORT;
429 #endif
430 }
431
432 #define LZ4_BUFSIZE (512*1024u)
433
434 int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) {
435
436 #if HAVE_LZ4
437 LZ4F_errorCode_t c;
438 _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL;
439 _cleanup_free_ char *buf = NULL;
440 char *src = NULL;
441 size_t size, n, total_in = 0, total_out, offset = 0, frame_size;
442 struct stat st;
443 int r;
444 static const LZ4F_compressOptions_t options = {
445 .stableSrc = 1,
446 };
447 static const LZ4F_preferences_t preferences = {
448 .frameInfo.blockSizeID = 5,
449 };
450
451 c = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
452 if (LZ4F_isError(c))
453 return -ENOMEM;
454
455 if (fstat(fdf, &st) < 0)
456 return log_debug_errno(errno, "fstat() failed: %m");
457
458 frame_size = LZ4F_compressBound(LZ4_BUFSIZE, &preferences);
459 size = frame_size + 64*1024; /* add some space for header and trailer */
460 buf = malloc(size);
461 if (!buf)
462 return -ENOMEM;
463
464 n = offset = total_out = LZ4F_compressBegin(ctx, buf, size, &preferences);
465 if (LZ4F_isError(n))
466 return -EINVAL;
467
468 src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0);
469 if (src == MAP_FAILED)
470 return -errno;
471
472 log_debug("Buffer size is %zu bytes, header size %zu bytes.", size, n);
473
474 while (total_in < (size_t) st.st_size) {
475 ssize_t k;
476
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;
482 goto cleanup;
483 }
484
485 total_in += k;
486 offset += n;
487 total_out += n;
488
489 if (max_bytes != (uint64_t) -1 && total_out > (size_t) max_bytes) {
490 log_debug("Compressed stream longer than %"PRIu64" bytes", max_bytes);
491 return -EFBIG;
492 }
493
494 if (size - offset < frame_size + 4) {
495 k = loop_write(fdt, buf, offset, false);
496 if (k < 0) {
497 r = k;
498 goto cleanup;
499 }
500 offset = 0;
501 }
502 }
503
504 n = LZ4F_compressEnd(ctx, buf + offset, size - offset, &options);
505 if (LZ4F_isError(n)) {
506 r = -ENOTRECOVERABLE;
507 goto cleanup;
508 }
509
510 offset += n;
511 total_out += n;
512 r = loop_write(fdt, buf, offset, false);
513 if (r < 0)
514 goto cleanup;
515
516 log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)",
517 total_in, total_out,
518 (double) total_out / total_in * 100);
519 cleanup:
520 munmap(src, st.st_size);
521 return r;
522 #else
523 return -EPROTONOSUPPORT;
524 #endif
525 }
526
527 int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) {
528
529 #if HAVE_XZ
530 _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
531 lzma_ret ret;
532
533 uint8_t buf[BUFSIZ], out[BUFSIZ];
534 lzma_action action = LZMA_RUN;
535
536 assert(fdf >= 0);
537 assert(fdt >= 0);
538
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);
542 return -ENOMEM;
543 }
544
545 for (;;) {
546 if (s.avail_in == 0 && action == LZMA_RUN) {
547 ssize_t n;
548
549 n = read(fdf, buf, sizeof(buf));
550 if (n < 0)
551 return -errno;
552 if (n == 0)
553 action = LZMA_FINISH;
554 else {
555 s.next_in = buf;
556 s.avail_in = n;
557 }
558 }
559
560 if (s.avail_out == 0) {
561 s.next_out = out;
562 s.avail_out = sizeof(out);
563 }
564
565 ret = lzma_code(&s, action);
566 if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) {
567 log_debug("Decompression failed: code %u", ret);
568 return -EBADMSG;
569 }
570
571 if (s.avail_out == 0 || ret == LZMA_STREAM_END) {
572 ssize_t n, k;
573
574 n = sizeof(out) - s.avail_out;
575
576 if (max_bytes != (uint64_t) -1) {
577 if (max_bytes < (uint64_t) n)
578 return -EFBIG;
579
580 max_bytes -= n;
581 }
582
583 k = loop_write(fdt, out, n, false);
584 if (k < 0)
585 return k;
586
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);
591
592 return 0;
593 }
594 }
595 }
596 #else
597 log_debug("Cannot decompress file. Compiled without XZ support.");
598 return -EPROTONOSUPPORT;
599 #endif
600 }
601
602 int decompress_stream_lz4(int in, int out, uint64_t max_bytes) {
603 #if HAVE_LZ4
604 size_t c;
605 _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL;
606 _cleanup_free_ char *buf = NULL;
607 char *src;
608 struct stat st;
609 int r = 0;
610 size_t total_in = 0, total_out = 0;
611
612 c = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
613 if (LZ4F_isError(c))
614 return -ENOMEM;
615
616 if (fstat(in, &st) < 0)
617 return log_debug_errno(errno, "fstat() failed: %m");
618
619 buf = malloc(LZ4_BUFSIZE);
620 if (!buf)
621 return -ENOMEM;
622
623 src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, in, 0);
624 if (src == MAP_FAILED)
625 return -errno;
626
627 while (total_in < (size_t) st.st_size) {
628 size_t produced = LZ4_BUFSIZE;
629 size_t used = st.st_size - total_in;
630
631 c = LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL);
632 if (LZ4F_isError(c)) {
633 r = -EBADMSG;
634 goto cleanup;
635 }
636
637 total_in += used;
638 total_out += produced;
639
640 if (max_bytes != (uint64_t) -1 && total_out > (size_t) max_bytes) {
641 log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes);
642 r = -EFBIG;
643 goto cleanup;
644 }
645
646 r = loop_write(out, buf, produced, false);
647 if (r < 0)
648 goto cleanup;
649 }
650
651 log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)",
652 total_in, total_out,
653 total_in > 0 ? (double) total_out / total_in * 100 : 0.0);
654 cleanup:
655 munmap(src, st.st_size);
656 return r;
657 #else
658 log_debug("Cannot decompress file. Compiled without LZ4 support.");
659 return -EPROTONOSUPPORT;
660 #endif
661 }
662
663 int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) {
664
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);
669 else
670 return -EPROTONOSUPPORT;
671 }