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