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