chiark / gitweb /
journal: add LZ4 as optional compressor
[elogind.git] / src / journal / compress.c
index 37c55a872829f73de9ac00694f23a44788c0521b..49d694ac28273f751f909c3bbf7dfe17bf3eb6e4 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <lzma.h>
+
+#ifdef HAVE_XZ
+#  include <lzma.h>
+#endif
+
+#ifdef HAVE_LZ4
+#  include <lz4.h>
+#endif
 
 #include "compress.h"
 #include "macro.h"
 #include "util.h"
+#include "sparse-endian.h"
+#include "journal-def.h"
+
+#define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t))
+
+static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = {
+        [OBJECT_COMPRESSED_XZ] = "XZ",
+        [OBJECT_COMPRESSED_LZ4] = "LZ4",
+};
 
-bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size) {
+DEFINE_STRING_TABLE_LOOKUP(object_compressed, int);
+
+int compress_blob_xz(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size) {
+#ifdef HAVE_XZ
         lzma_ret ret;
         size_t out_pos = 0;
 
@@ -38,25 +57,54 @@ bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_
         assert(dst);
         assert(dst_size);
 
-        /* Returns false if we couldn't compress the data or the
+        /* Returns < 0 if we couldn't compress the data or the
          * compressed result is longer than the original */
 
         ret = lzma_easy_buffer_encode(LZMA_PRESET_DEFAULT, LZMA_CHECK_NONE, NULL,
-                                      src, src_size, dst, &out_pos, src_size);
+                                      src, src_size, dst, &out_pos, src_size - 1);
         if (ret != LZMA_OK)
-                return false;
-
-        /* Is it actually shorter? */
-        if (out_pos == src_size)
-                return false;
+                return -ENOBUFS;
 
         *dst_size = out_pos;
-        return true;
+        return 0;
+#else
+        return -EPROTONOSUPPORT;
+#endif
 }
 
-bool uncompress_blob(const void *src, uint64_t src_size,
-                     void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size, uint64_t dst_max) {
+int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size) {
+#ifdef HAVE_LZ4
+        int r;
+
+        assert(src);
+        assert(src_size > 0);
+        assert(dst);
+        assert(dst_size);
+
+        /* Returns < 0 if we couldn't compress the data or the
+         * compressed result is longer than the original */
+
+        if (src_size < 9)
+                return -ENOBUFS;
 
+        r = LZ4_compress_limitedOutput(src, dst + 8, src_size, src_size - 8 - 1);
+        if (r <= 0)
+                return -ENOBUFS;
+
+        *(le64_t*) dst = htole64(src_size);
+        *dst_size = r + 8;
+
+        return 0;
+#else
+        return -EPROTONOSUPPORT;
+#endif
+}
+
+
+int decompress_blob_xz(const void *src, uint64_t src_size,
+                       void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size, uint64_t dst_max) {
+
+#ifdef HAVE_XZ
         _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
         lzma_ret ret;
         uint64_t space;
@@ -70,7 +118,7 @@ bool uncompress_blob(const void *src, uint64_t src_size,
 
         ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
         if (ret != LZMA_OK)
-                return false;
+                return -ENOMEM;
 
         space = MIN(src_size * 2, dst_max ?: (uint64_t) -1);
         if (!greedy_realloc(dst, dst_alloc_size, space, 1))
@@ -89,15 +137,13 @@ bool uncompress_blob(const void *src, uint64_t src_size,
 
                 if (ret == LZMA_STREAM_END)
                         break;
-
-                if (ret != LZMA_OK)
-                        return false;
+                else if (ret != LZMA_OK)
+                        return -ENOMEM;
 
                 if (dst_max > 0 && (space - s.avail_out) >= dst_max)
                         break;
-
-                if (dst_max > 0 && space == dst_max)
-                        return false;
+                else if (dst_max > 0 && space == dst_max)
+                        return -ENOBUFS;
 
                 used = space - s.avail_out;
                 space = MIN(2 * space, dst_max ?: (uint64_t) -1);
@@ -109,18 +155,75 @@ bool uncompress_blob(const void *src, uint64_t src_size,
         }
 
         *dst_size = space - s.avail_out;
-        return true;
+        return 0;
+#else
+        return -EPROTONOSUPPORT;
+#endif
 }
 
-bool uncompress_startswith(const void *src, uint64_t src_size,
-                           void **buffer, uint64_t *buffer_size,
-                           const void *prefix, uint64_t prefix_len,
-                           uint8_t extra) {
+int decompress_blob_lz4(const void *src, uint64_t src_size,
+                        void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size, uint64_t dst_max) {
+
+#ifdef HAVE_LZ4
+        char* out;
+        uint64_t size;
+        int r;
 
+        assert(src);
+        assert(src_size > 0);
+        assert(dst);
+        assert(dst_alloc_size);
+        assert(dst_size);
+        assert(*dst_alloc_size == 0 || *dst);
+
+        if (src_size <= 8)
+                return -EBADMSG;
+
+        size = le64toh( *(le64_t*)src );
+        if (size > *dst_alloc_size) {
+                out = realloc(*dst, size);
+                if (!out)
+                        return -ENOMEM;
+                *dst = out;
+                *dst_alloc_size = size;
+        } else
+                out = *dst;
+
+        r = LZ4_decompress_safe(src + 8, out, src_size - 8, size);
+        if (r < 0 || (uint64_t) r != size)
+                return -EBADMSG;
+
+        *dst_size = size;
+        return 0;
+#else
+        return -EPROTONOSUPPORT;
+#endif
+}
+
+int decompress_blob(int compression,
+                    const void *src, uint64_t src_size,
+                    void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size, uint64_t dst_max) {
+        if (compression == OBJECT_COMPRESSED_XZ)
+                return decompress_blob_xz(src, src_size,
+                                          dst, dst_alloc_size, dst_size, dst_max);
+        else if (compression == OBJECT_COMPRESSED_LZ4)
+                return decompress_blob_lz4(src, src_size,
+                                           dst, dst_alloc_size, dst_size, dst_max);
+        else
+                return -EBADMSG;
+}
+
+
+int decompress_startswith_xz(const void *src, uint64_t src_size,
+                             void **buffer, uint64_t *buffer_size,
+                             const void *prefix, uint64_t prefix_len,
+                             uint8_t extra) {
+
+#ifdef HAVE_XZ
         _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
         lzma_ret ret;
 
-        /* Checks whether the uncompressed blob starts with the
+        /* Checks whether the decompressed blob starts with the
          * mentioned prefix. The byte extra needs to follow the
          * prefix */
 
@@ -133,10 +236,10 @@ bool uncompress_startswith(const void *src, uint64_t src_size,
 
         ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
         if (ret != LZMA_OK)
-                return false;
+                return -EBADMSG;
 
-        if (!(greedy_realloc(buffer, buffer_size, prefix_len + 1, 1)))
-                return false;
+        if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1)))
+                return -ENOMEM;
 
         s.next_in = src;
         s.avail_in = src_size;
@@ -148,25 +251,88 @@ bool uncompress_startswith(const void *src, uint64_t src_size,
                 ret = lzma_code(&s, LZMA_FINISH);
 
                 if (ret != LZMA_STREAM_END && ret != LZMA_OK)
-                        return false;
+                        return -EBADMSG;
 
                 if (*buffer_size - s.avail_out >= prefix_len + 1)
                         return memcmp(*buffer, prefix, prefix_len) == 0 &&
                                 ((const uint8_t*) *buffer)[prefix_len] == extra;
 
                 if (ret == LZMA_STREAM_END)
-                        return false;
+                        return 0;
 
                 s.avail_out += *buffer_size;
 
                 if (!(greedy_realloc(buffer, buffer_size, *buffer_size * 2, 1)))
-                        return false;
+                        return -ENOMEM;
 
                 s.next_out = *buffer + *buffer_size - s.avail_out;
         }
+
+#else
+        return -EPROTONOSUPPORT;
+#endif
+}
+
+int decompress_startswith_lz4(const void *src, uint64_t src_size,
+                              void **buffer, uint64_t *buffer_size,
+                              const void *prefix, uint64_t prefix_len,
+                              uint8_t extra) {
+#ifdef HAVE_LZ4
+        /* Checks whether the decompressed blob starts with the
+         * mentioned prefix. The byte extra needs to follow the
+         * prefix */
+
+        int r;
+
+        assert(src);
+        assert(src_size > 0);
+        assert(buffer);
+        assert(buffer_size);
+        assert(prefix);
+        assert(*buffer_size == 0 || *buffer);
+
+        if (src_size <= 8)
+                return -EBADMSG;
+
+        if (!(greedy_realloc(buffer, buffer_size, ALIGN_8(prefix_len + 1), 1)))
+                return -ENOMEM;
+
+        r = LZ4_decompress_safe_partial(src + 8, *buffer, src_size - 8,
+                                        prefix_len + 1, *buffer_size);
+
+        if (r < 0)
+                return -EBADMSG;
+        if ((unsigned) r >= prefix_len + 1)
+                return memcmp(*buffer, prefix, prefix_len) == 0 &&
+                        ((const uint8_t*) *buffer)[prefix_len] == extra;
+        else
+                return 0;
+
+#else
+        return -EPROTONOSUPPORT;
+#endif
 }
 
-int compress_stream(int fdf, int fdt, uint32_t preset, off_t max_bytes) {
+int decompress_startswith(int compression,
+                          const void *src, uint64_t src_size,
+                          void **buffer, uint64_t *buffer_size,
+                          const void *prefix, uint64_t prefix_len,
+                          uint8_t extra) {
+        if (compression == OBJECT_COMPRESSED_XZ)
+                return decompress_startswith_xz(src, src_size,
+                                                buffer, buffer_size,
+                                                prefix, prefix_len,
+                                                extra);
+        else if (compression == OBJECT_COMPRESSED_LZ4)
+                return decompress_startswith_lz4(src, src_size,
+                                                 buffer, buffer_size,
+                                                 prefix, prefix_len,
+                                                 extra);
+        else
+                return -EBADMSG;
+}
+
+int compress_stream_xz(int fdf, int fdt, off_t max_bytes) {
         _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
         lzma_ret ret;
 
@@ -176,7 +342,7 @@ int compress_stream(int fdf, int fdt, uint32_t preset, off_t max_bytes) {
         assert(fdf >= 0);
         assert(fdt >= 0);
 
-        ret = lzma_easy_encoder(&s, preset, LZMA_CHECK_CRC64);
+        ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64);
         if (ret != LZMA_OK) {
                 log_error("Failed to initialize XZ encoder: code %d", ret);
                 return -EINVAL;
@@ -230,7 +396,7 @@ int compress_stream(int fdf, int fdt, uint32_t preset, off_t max_bytes) {
                                 return errno ? -errno : -EIO;
 
                         if (ret == LZMA_STREAM_END) {
-                                log_debug("Compression finished (%zu -> %zu bytes, %.1f%%)",
+                                log_debug("XZ compression finished (%zu -> %zu bytes, %.1f%%)",
                                           s.total_in, s.total_out,
                                           (double) s.total_out / s.total_in * 100);
 
@@ -240,7 +406,91 @@ int compress_stream(int fdf, int fdt, uint32_t preset, off_t max_bytes) {
         }
 }
 
-int decompress_stream(int fdf, int fdt, off_t max_bytes) {
+#define LZ4_BUFSIZE (512*1024)
+
+int compress_stream_lz4(int fdf, int fdt, off_t max_bytes) {
+
+#ifdef HAVE_LZ4
+
+        _cleanup_free_ char *buf1 = NULL, *buf2 = NULL, *out = NULL;
+        char *buf;
+        LZ4_stream_t lz4_data = {};
+        le32_t header;
+        size_t total_in = 0, total_out = sizeof(header);
+        ssize_t n;
+
+        assert(fdf >= 0);
+        assert(fdt >= 0);
+
+        buf1 = malloc(LZ4_BUFSIZE);
+        buf2 = malloc(LZ4_BUFSIZE);
+        out = malloc(LZ4_COMPRESSBOUND(LZ4_BUFSIZE));
+        if (!buf1 || !buf2 || !out)
+                return log_oom();
+
+        buf = buf1;
+        for (;;) {
+                size_t m;
+                int r;
+
+                m = LZ4_BUFSIZE;
+                if (max_bytes != -1 && m > (size_t) max_bytes - total_in)
+                        m = max_bytes - total_in;
+
+                n = read(fdf, buf, m);
+                if (n < 0)
+                        return -errno;
+                if (n == 0)
+                        break;
+
+                total_in += n;
+
+                r = LZ4_compress_limitedOutput_continue(&lz4_data, buf, out, n, n);
+                if (r == 0) {
+                        log_debug("Compressed size exceeds original, aborting compression.");
+                        return -ENOBUFS;
+                }
+
+                header = htole32(r);
+                errno = 0;
+
+                n = write(fdt, &header, sizeof(header));
+                if (n < 0)
+                        return -errno;
+                if (n != sizeof(header))
+                        return errno ? -errno : -EIO;
+
+                n = loop_write(fdt, out, r, false);
+                if (n < 0)
+                        return n;
+                if (n != r)
+                        return errno ? -errno : -EIO;
+
+                total_out += sizeof(header) + r;
+
+                buf = buf == buf1 ? buf2 : buf1;
+        }
+
+        header = htole32(0);
+        n = write(fdt, &header, sizeof(header));
+        if (n < 0)
+                return -errno;
+        if (n != sizeof(header))
+                return errno ? -errno : -EIO;
+
+        log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)",
+                  total_in, total_out,
+                  (double) total_out / total_in * 100);
+
+        return 0;
+#else
+        return -EPROTONOSUPPORT;
+#endif
+}
+
+int decompress_stream_xz(int fdf, int fdt, off_t max_bytes) {
+
+#ifdef HAVE_XZ
         _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
         lzma_ret ret;
 
@@ -253,7 +503,7 @@ int decompress_stream(int fdf, int fdt, off_t max_bytes) {
         ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
         if (ret != LZMA_OK) {
                 log_error("Failed to initialize XZ decoder: code %d", ret);
-                return -EINVAL;
+                return -ENOMEM;
         }
 
         for (;;) {
@@ -289,7 +539,7 @@ int decompress_stream(int fdf, int fdt, off_t max_bytes) {
 
                         if (max_bytes != -1) {
                                 if (max_bytes < n)
-                                        return -E2BIG;
+                                        return -EFBIG;
 
                                 max_bytes -= n;
                         }
@@ -302,7 +552,7 @@ int decompress_stream(int fdf, int fdt, off_t max_bytes) {
                                 return errno ? -errno : -EIO;
 
                         if (ret == LZMA_STREAM_END) {
-                                log_debug("Decompression finished (%zu -> %zu bytes, %.1f%%)",
+                                log_debug("XZ decompression finished (%zu -> %zu bytes, %.1f%%)",
                                           s.total_in, s.total_out,
                                           (double) s.total_out / s.total_in * 100);
 
@@ -310,4 +560,101 @@ int decompress_stream(int fdf, int fdt, off_t max_bytes) {
                         }
                 }
         }
+#else
+        log_error("Cannot decompress file. Compiled without XZ support.");
+        return -EPROTONOSUPPORT;
+#endif
+}
+
+int decompress_stream_lz4(int fdf, int fdt, off_t max_bytes) {
+
+#ifdef HAVE_LZ4
+        _cleanup_free_ char *buf = NULL, *out = NULL;
+        size_t buf_size = 0;
+        LZ4_streamDecode_t lz4_data = {};
+        le32_t header;
+        size_t total_in = sizeof(header), total_out = 0;
+
+        assert(fdf >= 0);
+        assert(fdt >= 0);
+
+        out = malloc(4*LZ4_BUFSIZE);
+        if (!out)
+                return log_oom();
+
+        for (;;) {
+                ssize_t n, m;
+                int r;
+
+                n = read(fdf, &header, sizeof(header));
+                if (n < 0)
+                        return -errno;
+                if (n != sizeof(header))
+                        return errno ? -errno : -EIO;
+
+                m = le32toh(header);
+                if (m == 0)
+                        break;
+
+                /* We refuse to use a bigger decompression buffer than
+                 * the one used for compression by 4 times. This means
+                 * that compression buffer size can be enlarged 4
+                 * times. This can be changed, but old binaries might
+                 * not accept buffers compressed by newer binaries then.
+                 */
+                if (m > LZ4_COMPRESSBOUND(LZ4_BUFSIZE * 4)) {
+                        log_error("Compressed stream block too big: %zd bytes", m);
+                        return -EBADMSG;
+                }
+
+                total_in += sizeof(header) + m;
+
+                if (!GREEDY_REALLOC(buf, buf_size, m))
+                        return log_oom();
+
+                errno = 0;
+                n = loop_read(fdf, buf, m, false);
+                if (n < 0)
+                        return n;
+                if (n != m)
+                        return errno ? -errno : -EIO;
+
+                r = LZ4_decompress_safe_continue(&lz4_data, buf, out, m, 4*LZ4_BUFSIZE);
+                if (r <= 0)
+                        log_error("LZ4 decompression failed.");
+
+                total_out += r;
+
+                if (max_bytes != -1 && total_out > (size_t) max_bytes) {
+                        log_debug("Decompressed stream longer than %zd bytes", max_bytes);
+                        return -EFBIG;
+                }
+
+                errno = 0;
+                n = loop_write(fdt, out, r, false);
+                if (n < 0)
+                        return n;
+                if (n != r)
+                        return errno ? -errno : -EIO;
+        }
+
+        log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)",
+                  total_in, total_out,
+                  (double) total_out / total_in * 100);
+
+        return 0;
+#else
+        log_error("Cannot decompress file. Compiled without LZ4 support.");
+        return -EPROTONOSUPPORT;
+#endif
+}
+
+int decompress_stream(const char *filename, int fdf, int fdt, off_t max_bytes) {
+
+        if (endswith(filename, ".lz4"))
+                return decompress_stream_lz4(fdf, fdt, max_bytes);
+        else if (endswith(filename, ".xz"))
+                return decompress_stream_xz(fdf, fdt, max_bytes);
+        else
+                return -EPROTONOSUPPORT;
 }