Files
systemd/src/basic/compress.c
T
Daan De Meyer d158595dc7 tree-wide: move static dl handles into their dlopen_*() functions
Each dlopen_*() wrapper kept its dl handle as a file-scope
'static void *xxx_dl = NULL;' even though only the wrapper itself
ever referenced it. Move each one inside the corresponding function
so its scope matches its actual use, leaving the rest of each
translation unit free of the unused file-scope name.

In pcre2-util.c the assert(pcre2_dl) in pattern_matches_and_log()
becomes assert(sym_pcre2_match), which carries the same invariant
(pattern_compile_and_log() ran dlopen_pcre2()).
2026-05-19 19:22:06 +00:00

2557 lines
76 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#if HAVE_XZ
#include <lzma.h>
#endif
#if HAVE_LZ4
#include <lz4.h>
#include <lz4frame.h>
#include <lz4hc.h>
#endif
#if HAVE_ZSTD
#include <zstd.h>
#include <zstd_errors.h>
#endif
#if HAVE_ZLIB
#include <zlib.h>
#endif
#if HAVE_BZIP2
#include <bzlib.h>
#endif
#include "sd-dlopen.h"
#include "alloc-util.h"
#include "bitfield.h"
#include "compress.h"
#include "dlfcn-util.h"
#include "io-util.h"
#include "log.h"
#include "string-table.h"
#include "unaligned.h"
#if HAVE_XZ
static DLSYM_PROTOTYPE(lzma_code) = NULL;
static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL;
static DLSYM_PROTOTYPE(lzma_end) = NULL;
static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL;
static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL;
static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL;
/* We can’t just do _cleanup_(sym_lzma_end) because a compiler bug makes
* this fail with:
* ../src/basic/compress.c: In function ‘decompress_blob_xz’:
* ../src/basic/compress.c:304:9: error: cleanup argument not a function
* 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT;
* | ^~~~~~~~~
*/
static inline void lzma_end_wrapper(lzma_stream *ls) {
sym_lzma_end(ls);
}
#endif
#if HAVE_LZ4
static DLSYM_PROTOTYPE(LZ4F_compressBegin) = NULL;
static DLSYM_PROTOTYPE(LZ4F_compressBound) = NULL;
static DLSYM_PROTOTYPE(LZ4F_compressEnd) = NULL;
static DLSYM_PROTOTYPE(LZ4F_compressUpdate) = NULL;
static DLSYM_PROTOTYPE(LZ4F_createCompressionContext) = NULL;
static DLSYM_PROTOTYPE(LZ4F_createDecompressionContext) = NULL;
static DLSYM_PROTOTYPE(LZ4F_decompress) = NULL;
static DLSYM_PROTOTYPE(LZ4F_freeCompressionContext) = NULL;
static DLSYM_PROTOTYPE(LZ4F_freeDecompressionContext) = NULL;
static DLSYM_PROTOTYPE(LZ4F_isError) = NULL;
static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL;
static DLSYM_PROTOTYPE(LZ4_compress_default) = NULL;
static DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL;
static DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL;
static DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL;
static const LZ4F_preferences_t lz4_preferences = {
.frameInfo.blockSizeID = 5,
};
#endif
#if HAVE_ZSTD
static DLSYM_PROTOTYPE(ZSTD_CCtx_setParameter) = NULL;
static DLSYM_PROTOTYPE(ZSTD_compress) = NULL;
static DLSYM_PROTOTYPE(ZSTD_compressStream2) = NULL;
static DLSYM_PROTOTYPE(ZSTD_createCCtx) = NULL;
static DLSYM_PROTOTYPE(ZSTD_createDCtx) = NULL;
static DLSYM_PROTOTYPE(ZSTD_CStreamInSize) = NULL;
static DLSYM_PROTOTYPE(ZSTD_CStreamOutSize) = NULL;
static DLSYM_PROTOTYPE(ZSTD_decompressStream) = NULL;
static DLSYM_PROTOTYPE(ZSTD_DStreamInSize) = NULL;
static DLSYM_PROTOTYPE(ZSTD_DStreamOutSize) = NULL;
static DLSYM_PROTOTYPE(ZSTD_freeCCtx) = NULL;
static DLSYM_PROTOTYPE(ZSTD_freeDCtx) = NULL;
static DLSYM_PROTOTYPE(ZSTD_getErrorCode) = NULL;
static DLSYM_PROTOTYPE(ZSTD_getErrorName) = NULL;
static DLSYM_PROTOTYPE(ZSTD_getFrameContentSize) = NULL;
static DLSYM_PROTOTYPE(ZSTD_isError) = NULL;
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_DCtx*, sym_ZSTD_freeDCtx, ZSTD_freeDCtxp, NULL);
static int zstd_ret_to_errno(size_t ret) {
switch (sym_ZSTD_getErrorCode(ret)) {
case ZSTD_error_dstSize_tooSmall:
return -ENOBUFS;
case ZSTD_error_memory_allocation:
return -ENOMEM;
default:
return -EBADMSG;
}
}
#endif
#if HAVE_ZLIB
static DLSYM_PROTOTYPE(deflateInit2_) = NULL;
static DLSYM_PROTOTYPE(deflate) = NULL;
static DLSYM_PROTOTYPE(deflateEnd) = NULL;
static DLSYM_PROTOTYPE(inflateInit2_) = NULL;
static DLSYM_PROTOTYPE(inflate) = NULL;
static DLSYM_PROTOTYPE(inflateEnd) = NULL;
static inline void deflateEnd_wrapper(z_stream *s) {
sym_deflateEnd(s);
}
static inline void inflateEnd_wrapper(z_stream *s) {
sym_inflateEnd(s);
}
#endif
#if HAVE_BZIP2
static DLSYM_PROTOTYPE(BZ2_bzCompressInit) = NULL;
static DLSYM_PROTOTYPE(BZ2_bzCompress) = NULL;
static DLSYM_PROTOTYPE(BZ2_bzCompressEnd) = NULL;
static DLSYM_PROTOTYPE(BZ2_bzDecompressInit) = NULL;
static DLSYM_PROTOTYPE(BZ2_bzDecompress) = NULL;
static DLSYM_PROTOTYPE(BZ2_bzDecompressEnd) = NULL;
static inline void BZ2_bzCompressEnd_wrapper(bz_stream *s) {
sym_BZ2_bzCompressEnd(s);
}
static inline void BZ2_bzDecompressEnd_wrapper(bz_stream *s) {
sym_BZ2_bzDecompressEnd(s);
}
#endif
/* Opaque Compressor/Decompressor struct definition */
struct Compressor {
Compression type;
bool encoding;
union {
#if HAVE_XZ
lzma_stream xz;
#endif
#if HAVE_LZ4
struct {
LZ4F_compressionContext_t c_lz4;
void *lz4_header; /* stashed frame header from LZ4F_compressBegin */
size_t lz4_header_size;
};
LZ4F_decompressionContext_t d_lz4;
#endif
#if HAVE_ZSTD
ZSTD_CCtx *c_zstd;
ZSTD_DCtx *d_zstd;
#endif
#if HAVE_ZLIB
z_stream gzip;
#endif
#if HAVE_BZIP2
bz_stream bzip2;
#endif
};
};
#define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t))
/* zlib windowBits value for gzip format: MAX_WBITS (15) + 16 to enable gzip header detection/generation */
#define ZLIB_WBITS_GZIP (15 + 16)
static const char* const compression_table[_COMPRESSION_MAX] = {
[COMPRESSION_NONE] = "uncompressed", /* backwards compatibility with importd */
[COMPRESSION_XZ] = "xz",
[COMPRESSION_LZ4] = "lz4",
[COMPRESSION_ZSTD] = "zstd",
[COMPRESSION_GZIP] = "gzip",
[COMPRESSION_BZIP2] = "bzip2",
};
static const char* const compression_uppercase_table[_COMPRESSION_MAX] = {
[COMPRESSION_NONE] = "NONE", /* backwards compatibility with SYSTEMD_JOURNAL_COMPRESS=NONE */
[COMPRESSION_XZ] = "XZ",
[COMPRESSION_LZ4] = "LZ4",
[COMPRESSION_ZSTD] = "ZSTD",
[COMPRESSION_GZIP] = "GZIP",
[COMPRESSION_BZIP2] = "BZIP2",
};
static const char* const compression_extension_table[_COMPRESSION_MAX] = {
[COMPRESSION_NONE] = "",
[COMPRESSION_XZ] = ".xz",
[COMPRESSION_LZ4] = ".lz4",
[COMPRESSION_ZSTD] = ".zst",
[COMPRESSION_GZIP] = ".gz",
[COMPRESSION_BZIP2] = ".bz2",
};
DEFINE_STRING_TABLE_LOOKUP(compression, Compression);
DEFINE_STRING_TABLE_LOOKUP(compression_uppercase, Compression);
DEFINE_STRING_TABLE_LOOKUP(compression_extension, Compression);
Compression compression_from_string_harder(const char *s) {
Compression c;
assert(s);
c = compression_from_string(s);
if (c >= 0)
return c;
return compression_uppercase_from_string(s);
}
Compression compression_from_filename(const char *filename) {
Compression c;
const char *e;
assert(filename);
e = strrchr(filename, '.');
if (!e)
return COMPRESSION_NONE;
c = compression_extension_from_string(e);
if (c < 0)
return COMPRESSION_NONE;
return c;
}
bool compression_supported(Compression c) {
static const unsigned supported =
(1U << COMPRESSION_NONE) |
(1U << COMPRESSION_XZ) * HAVE_XZ |
(1U << COMPRESSION_LZ4) * HAVE_LZ4 |
(1U << COMPRESSION_ZSTD) * HAVE_ZSTD |
(1U << COMPRESSION_GZIP) * HAVE_ZLIB |
(1U << COMPRESSION_BZIP2) * HAVE_BZIP2;
assert(c >= 0);
assert(c < _COMPRESSION_MAX);
return BIT_SET(supported, c);
}
Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_MAGIC_BYTES_MAX]) {
/* Magic signatures per RFC 1952 (gzip), tukaani.org/xz/xz-file-format.txt (xz),
* RFC 8878 (zstd), lz4/doc/lz4_Frame_format.md (lz4), and the bzip2 file format.
* Make sure to update COMPRESSION_MAGIC_BYTES_MAX if needed when adding a new magic. */
if (memcmp(data, (const uint8_t[]) { 0x1f, 0x8b }, 2) == 0)
return COMPRESSION_GZIP;
if (memcmp(data, (const uint8_t[]) { 0xfd, '7', 'z', 'X', 'Z', 0x00 }, 6) == 0)
return COMPRESSION_XZ;
if (memcmp(data, (const uint8_t[]) { 0x28, 0xb5, 0x2f, 0xfd }, 4) == 0)
return COMPRESSION_ZSTD;
if (memcmp(data, (const uint8_t[]) { 0x04, 0x22, 0x4d, 0x18 }, 4) == 0)
return COMPRESSION_LZ4;
if (memcmp(data, (const uint8_t[]) { 'B', 'Z', 'h' }, 3) == 0)
return COMPRESSION_BZIP2;
return _COMPRESSION_INVALID;
}
int dlopen_xz(int log_level) {
#if HAVE_XZ
static void *lzma_dl = NULL;
SD_ELF_NOTE_DLOPEN(
"lzma",
"Support lzma compression in journal and coredump files",
COMPRESSION_PRIORITY_XZ,
"liblzma.so.5");
return dlopen_many_sym_or_warn(
&lzma_dl,
"liblzma.so.5", log_level,
DLSYM_ARG(lzma_code),
DLSYM_ARG(lzma_easy_encoder),
DLSYM_ARG(lzma_end),
DLSYM_ARG(lzma_stream_buffer_encode),
DLSYM_ARG(lzma_lzma_preset),
DLSYM_ARG(lzma_stream_decoder));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"lzma support is not compiled in.");
#endif
}
int dlopen_lz4(int log_level) {
#if HAVE_LZ4
static void *lz4_dl = NULL;
SD_ELF_NOTE_DLOPEN(
"lz4",
"Support lz4 compression in journal and coredump files",
COMPRESSION_PRIORITY_LZ4,
"liblz4.so.1");
return dlopen_many_sym_or_warn(
&lz4_dl,
"liblz4.so.1", log_level,
DLSYM_ARG(LZ4F_compressBegin),
DLSYM_ARG(LZ4F_compressBound),
DLSYM_ARG(LZ4F_compressEnd),
DLSYM_ARG(LZ4F_compressUpdate),
DLSYM_ARG(LZ4F_createCompressionContext),
DLSYM_ARG(LZ4F_createDecompressionContext),
DLSYM_ARG(LZ4F_decompress),
DLSYM_ARG(LZ4F_freeCompressionContext),
DLSYM_ARG(LZ4F_freeDecompressionContext),
DLSYM_ARG(LZ4F_isError),
DLSYM_ARG(LZ4_compress_default),
DLSYM_ARG(LZ4_compress_HC),
DLSYM_ARG(LZ4_decompress_safe),
DLSYM_ARG(LZ4_decompress_safe_partial),
DLSYM_ARG(LZ4_versionNumber));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"lz4 support is not compiled in.");
#endif
}
int dlopen_zstd(int log_level) {
#if HAVE_ZSTD
static void *zstd_dl = NULL;
SD_ELF_NOTE_DLOPEN(
"zstd",
"Support zstd compression in journal and coredump files",
COMPRESSION_PRIORITY_ZSTD,
"libzstd.so.1");
return dlopen_many_sym_or_warn(
&zstd_dl,
"libzstd.so.1", log_level,
DLSYM_ARG(ZSTD_getErrorCode),
DLSYM_ARG(ZSTD_compress),
DLSYM_ARG(ZSTD_getFrameContentSize),
DLSYM_ARG(ZSTD_decompressStream),
DLSYM_ARG(ZSTD_getErrorName),
DLSYM_ARG(ZSTD_DStreamOutSize),
DLSYM_ARG(ZSTD_CStreamInSize),
DLSYM_ARG(ZSTD_CStreamOutSize),
DLSYM_ARG(ZSTD_CCtx_setParameter),
DLSYM_ARG(ZSTD_compressStream2),
DLSYM_ARG(ZSTD_DStreamInSize),
DLSYM_ARG(ZSTD_freeCCtx),
DLSYM_ARG(ZSTD_freeDCtx),
DLSYM_ARG(ZSTD_isError),
DLSYM_ARG(ZSTD_createDCtx),
DLSYM_ARG(ZSTD_createCCtx));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"zstd support is not compiled in.");
#endif
}
int dlopen_zlib(int log_level) {
#if HAVE_ZLIB
static void *zlib_dl = NULL;
SD_ELF_NOTE_DLOPEN(
"zlib",
"Support gzip compression and decompression",
SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
"libz.so.1");
return dlopen_many_sym_or_warn(
&zlib_dl,
"libz.so.1", log_level,
DLSYM_ARG(deflateInit2_),
DLSYM_ARG(deflate),
DLSYM_ARG(deflateEnd),
DLSYM_ARG(inflateInit2_),
DLSYM_ARG(inflate),
DLSYM_ARG(inflateEnd));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"zlib support is not compiled in.");
#endif
}
int dlopen_bzip2(int log_level) {
#if HAVE_BZIP2
static void *bzip2_dl = NULL;
SD_ELF_NOTE_DLOPEN(
"bzip2",
"Support bzip2 compression and decompression",
SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
"libbz2.so.1");
return dlopen_many_sym_or_warn(
&bzip2_dl,
"libbz2.so.1", log_level,
DLSYM_ARG(BZ2_bzCompressInit),
DLSYM_ARG(BZ2_bzCompress),
DLSYM_ARG(BZ2_bzCompressEnd),
DLSYM_ARG(BZ2_bzDecompressInit),
DLSYM_ARG(BZ2_bzDecompress),
DLSYM_ARG(BZ2_bzDecompressEnd));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"bzip2 support is not compiled in.");
#endif
}
static int compress_blob_xz(
const void *src,
uint64_t src_size,
void *dst,
size_t dst_alloc_size,
size_t *dst_size,
int level) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_alloc_size > 0);
assert(dst_size);
#if HAVE_XZ
lzma_options_lzma opt = {
1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT,
LZMA_PB_DEFAULT, LZMA_MODE_FAST, 128, LZMA_MF_HC3, 4
};
lzma_filter filters[] = {
{ LZMA_FILTER_LZMA2, &opt },
{ LZMA_VLI_UNKNOWN, NULL }
};
lzma_ret ret;
size_t out_pos = 0;
int r;
r = dlopen_xz(LOG_DEBUG);
if (r < 0)
return r;
if (level >= 0) {
r = sym_lzma_lzma_preset(&opt, (uint32_t) level);
if (r < 0)
return r;
}
/* Returns < 0 if we couldn't compress the data or the
* compressed result is longer than the original */
if (src_size < 80)
return -ENOBUFS;
ret = sym_lzma_stream_buffer_encode(filters, LZMA_CHECK_NONE, NULL,
src, src_size, dst, &out_pos, dst_alloc_size);
if (ret != LZMA_OK)
return -ENOBUFS;
*dst_size = out_pos;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int compress_blob_lz4(
const void *src,
uint64_t src_size,
void *dst,
size_t dst_alloc_size,
size_t *dst_size,
int level) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_alloc_size > 0);
assert(dst_size);
#if HAVE_LZ4
int r;
r = dlopen_lz4(LOG_DEBUG);
if (r < 0)
return r;
/* Returns < 0 if we couldn't compress the data or the
* compressed result is longer than the original */
if (src_size < 9)
return -ENOBUFS;
if (src_size > INT_MAX)
return -EFBIG;
if (dst_alloc_size > INT_MAX)
dst_alloc_size = INT_MAX;
if (level <= 0)
r = sym_LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8);
else
r = sym_LZ4_compress_HC(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8, level);
if (r <= 0)
return -ENOBUFS;
unaligned_write_le64(dst, src_size);
*dst_size = r + 8;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int compress_blob_zstd(
const void *src,
uint64_t src_size,
void *dst,
size_t dst_alloc_size,
size_t *dst_size,
int level) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_alloc_size > 0);
assert(dst_size);
#if HAVE_ZSTD
size_t k;
int r;
r = dlopen_zstd(LOG_DEBUG);
if (r < 0)
return r;
k = sym_ZSTD_compress(dst, dst_alloc_size, src, src_size, level < 0 ? 0 : level);
if (sym_ZSTD_isError(k))
return zstd_ret_to_errno(k);
*dst_size = k;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int compress_blob_gzip(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size, int level) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_alloc_size > 0);
assert(dst_size);
#if HAVE_ZLIB
int r;
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
if (dst_alloc_size > UINT_MAX)
dst_alloc_size = UINT_MAX;
_cleanup_(deflateEnd_wrapper) z_stream s = {};
r = sym_deflateInit2_(&s, level < 0 ? Z_DEFAULT_COMPRESSION : level,
/* method= */ Z_DEFLATED,
/* windowBits= */ ZLIB_WBITS_GZIP,
/* memLevel= */ 8,
/* strategy= */ Z_DEFAULT_STRATEGY,
ZLIB_VERSION, (int) sizeof(s));
if (r != Z_OK)
return -ENOMEM;
s.next_in = (void*) src;
s.avail_in = src_size;
s.next_out = dst;
s.avail_out = dst_alloc_size;
r = sym_deflate(&s, Z_FINISH);
if (r != Z_STREAM_END)
return -ENOBUFS;
*dst_size = dst_alloc_size - s.avail_out;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int compress_blob_bzip2(
const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size, int level) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_alloc_size > 0);
assert(dst_size);
#if HAVE_BZIP2
int r;
r = dlopen_bzip2(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
if (dst_alloc_size > UINT_MAX)
dst_alloc_size = UINT_MAX;
_cleanup_(BZ2_bzCompressEnd_wrapper) bz_stream s = {};
r = sym_BZ2_bzCompressInit(&s, level < 0 ? 9 : level, /* verbosity= */ 0, /* workFactor= */ 0);
if (r != BZ_OK)
return -ENOMEM;
s.next_in = (char*) src;
s.avail_in = src_size;
s.next_out = (char*) dst;
s.avail_out = dst_alloc_size;
r = sym_BZ2_bzCompress(&s, BZ_FINISH);
if (r != BZ_STREAM_END)
return -ENOBUFS;
*dst_size = dst_alloc_size - s.avail_out;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
int compress_blob(
Compression compression,
const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size, int level) {
switch (compression) {
case COMPRESSION_XZ:
return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size, level);
case COMPRESSION_LZ4:
return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size, level);
case COMPRESSION_ZSTD:
return compress_blob_zstd(src, src_size, dst, dst_alloc_size, dst_size, level);
case COMPRESSION_GZIP:
return compress_blob_gzip(src, src_size, dst, dst_alloc_size, dst_size, level);
case COMPRESSION_BZIP2:
return compress_blob_bzip2(src, src_size, dst, dst_alloc_size, dst_size, level);
default:
return -EOPNOTSUPP;
}
}
static int decompress_blob_xz(
const void *src,
uint64_t src_size,
void **dst,
size_t *dst_size,
size_t dst_max) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_size);
#if HAVE_XZ
int r;
r = dlopen_xz(LOG_DEBUG);
if (r < 0)
return r;
_cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT;
lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, /* flags= */ 0);
if (ret != LZMA_OK)
return -ENOMEM;
size_t space = MIN(src_size * 2, dst_max ?: SIZE_MAX);
if (!greedy_realloc(dst, space, 1))
return -ENOMEM;
s.next_in = src;
s.avail_in = src_size;
s.next_out = *dst;
s.avail_out = space;
for (;;) {
size_t used;
ret = sym_lzma_code(&s, LZMA_FINISH);
if (ret == LZMA_STREAM_END)
break;
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 -ENOBUFS;
used = space - s.avail_out;
/* Silence static analyzers, space is bounded by allocation size */
assert(space <= SIZE_MAX / 2);
space = MIN(2 * space, dst_max ?: SIZE_MAX);
if (!greedy_realloc(dst, space, 1))
return -ENOMEM;
s.avail_out = space - used;
s.next_out = *(uint8_t**)dst + used;
}
*dst_size = space - s.avail_out;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_blob_lz4(
const void *src,
uint64_t src_size,
void **dst,
size_t *dst_size,
size_t dst_max) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_size);
#if HAVE_LZ4
char* out;
int r, size; /* LZ4 uses int for size */
r = dlopen_lz4(LOG_DEBUG);
if (r < 0)
return r;
if (src_size <= 8)
return -EBADMSG;
if (src_size - 8 > INT_MAX)
return -EFBIG;
size = unaligned_read_le64(src);
if (size < 0 || (unsigned) size != unaligned_read_le64(src))
return -EFBIG;
if (dst_max > 0 && (size_t) size > dst_max)
return -ENOBUFS;
out = greedy_realloc(dst, size, 1);
if (!out)
return -ENOMEM;
r = sym_LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size);
if (r < 0 || r != size)
return -EBADMSG;
*dst_size = size;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_blob_zstd(
const void *src,
uint64_t src_size,
void **dst,
size_t *dst_size,
size_t dst_max) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_size);
#if HAVE_ZSTD
uint64_t size;
int r;
r = dlopen_zstd(LOG_DEBUG);
if (r < 0)
return r;
size = sym_ZSTD_getFrameContentSize(src, src_size);
if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN))
return -EBADMSG;
if (dst_max > 0 && size > dst_max)
size = dst_max;
if (size > SIZE_MAX)
return -E2BIG;
if (!(greedy_realloc(dst, MAX(sym_ZSTD_DStreamOutSize(), size), 1)))
return -ENOMEM;
_cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx();
if (!dctx)
return -ENOMEM;
ZSTD_inBuffer input = {
.src = src,
.size = src_size,
};
ZSTD_outBuffer output = {
.dst = *dst,
.size = MALLOC_SIZEOF_SAFE(*dst),
};
size_t k = sym_ZSTD_decompressStream(dctx, &output, &input);
if (sym_ZSTD_isError(k))
return log_debug_errno(zstd_ret_to_errno(k), "ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k));
if (output.pos < size)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream.");
*dst_size = size;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_blob_gzip(
const void *src,
uint64_t src_size,
void **dst,
size_t *dst_size,
size_t dst_max) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_size);
#if HAVE_ZLIB
int r;
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
_cleanup_(inflateEnd_wrapper) z_stream s = {};
r = sym_inflateInit2_(&s, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(s));
if (r != Z_OK)
return -ENOMEM;
size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX);
if (!greedy_realloc(dst, space, 1))
return -ENOMEM;
s.next_in = (void*) src;
s.avail_in = src_size;
s.next_out = *dst;
s.avail_out = space;
for (;;) {
size_t used;
r = sym_inflate(&s, Z_NO_FLUSH);
if (r == Z_STREAM_END)
break;
if (!IN_SET(r, Z_OK, Z_BUF_ERROR))
return -EBADMSG;
if (dst_max > 0 && (space - s.avail_out) >= dst_max)
break;
if (dst_max > 0 && space == dst_max)
return -ENOBUFS;
used = space - s.avail_out;
space = MIN3(2 * space, dst_max ?: SIZE_MAX, UINT_MAX);
if (!greedy_realloc(dst, space, 1))
return -ENOMEM;
s.avail_out = space - used;
s.next_out = *(uint8_t**)dst + used;
}
*dst_size = space - s.avail_out;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_blob_bzip2(
const void *src,
uint64_t src_size,
void **dst,
size_t *dst_size,
size_t dst_max) {
assert(src);
assert(src_size > 0);
assert(dst);
assert(dst_size);
#if HAVE_BZIP2
int r;
r = dlopen_bzip2(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
_cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {};
r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
return -ENOMEM;
size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX);
if (!greedy_realloc(dst, space, 1))
return -ENOMEM;
s.next_in = (char*) src;
s.avail_in = src_size;
s.next_out = (char*) *dst;
s.avail_out = space;
for (;;) {
size_t used;
r = sym_BZ2_bzDecompress(&s);
if (r == BZ_STREAM_END)
break;
if (r != BZ_OK)
return -EBADMSG;
if (dst_max > 0 && (space - s.avail_out) >= dst_max)
break;
if (dst_max > 0 && space == dst_max)
return -ENOBUFS;
used = space - s.avail_out;
space = MIN3(2 * space, dst_max ?: SIZE_MAX, (size_t) UINT_MAX);
if (!greedy_realloc(dst, space, 1))
return -ENOMEM;
s.avail_out = space - used;
s.next_out = (char*) *dst + used;
}
*dst_size = space - s.avail_out;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
int decompress_blob(
Compression compression,
const void *src,
uint64_t src_size,
void **dst,
size_t *dst_size,
size_t dst_max) {
switch (compression) {
case COMPRESSION_XZ:
return decompress_blob_xz(
src, src_size,
dst, dst_size, dst_max);
case COMPRESSION_LZ4:
return decompress_blob_lz4(
src, src_size,
dst, dst_size, dst_max);
case COMPRESSION_ZSTD:
return decompress_blob_zstd(
src, src_size,
dst, dst_size, dst_max);
case COMPRESSION_GZIP:
return decompress_blob_gzip(
src, src_size,
dst, dst_size, dst_max);
case COMPRESSION_BZIP2:
return decompress_blob_bzip2(
src, src_size,
dst, dst_size, dst_max);
default:
return -EPROTONOSUPPORT;
}
}
int decompress_zlib_raw(
const void *src,
uint64_t src_size,
void *dst,
size_t dst_size,
int wbits) {
#if HAVE_ZLIB
int r;
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
if (dst_size > UINT_MAX)
return -EFBIG;
_cleanup_(inflateEnd_wrapper) z_stream s = {
.next_in = (void*) src,
.avail_in = src_size,
.next_out = dst,
.avail_out = dst_size,
};
r = sym_inflateInit2_(&s, /* windowBits= */ wbits, ZLIB_VERSION, (int) sizeof(s));
if (r != Z_OK)
return -EIO;
r = sym_inflate(&s, Z_FINISH);
size_t produced = (uint8_t*) s.next_out - (uint8_t*) dst;
if (r != Z_STREAM_END || produced != dst_size)
return -EBADMSG;
return 0;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_startswith_xz(
const void *src,
uint64_t src_size,
void **buffer,
const void *prefix,
size_t prefix_len,
uint8_t extra) {
/* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to
* follow the prefix */
assert(src);
assert(src_size > 0);
assert(buffer);
assert(prefix);
#if HAVE_XZ
int r;
r = dlopen_xz(LOG_DEBUG);
if (r < 0)
return r;
_cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT;
lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, /* flags= */ 0);
if (ret != LZMA_OK)
return -EBADMSG;
if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
return -ENOMEM;
size_t allocated = MALLOC_SIZEOF_SAFE(*buffer);
s.next_in = src;
s.avail_in = src_size;
s.next_out = *buffer;
s.avail_out = allocated;
for (;;) {
ret = sym_lzma_code(&s, LZMA_FINISH);
if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END))
return -EBADMSG;
if (allocated - 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 0;
s.avail_out += allocated;
if (!(greedy_realloc(buffer, allocated * 2, 1)))
return -ENOMEM;
allocated = MALLOC_SIZEOF_SAFE(*buffer);
s.next_out = *(uint8_t**)buffer + allocated - s.avail_out;
}
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_startswith_lz4(
const void *src,
uint64_t src_size,
void **buffer,
const void *prefix,
size_t prefix_len,
uint8_t extra) {
/* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to
* follow the prefix */
assert(src);
assert(src_size > 0);
assert(buffer);
assert(prefix);
#if HAVE_LZ4
size_t allocated;
int r;
r = dlopen_lz4(LOG_DEBUG);
if (r < 0)
return r;
if (src_size <= 8)
return -EBADMSG;
if (src_size - 8 > INT_MAX)
return -EFBIG;
if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
return -ENOMEM;
allocated = MALLOC_SIZEOF_SAFE(*buffer);
r = sym_LZ4_decompress_safe_partial(
(char*)src + 8,
*buffer,
src_size - 8,
prefix_len + 1,
allocated);
/* One lz4 < 1.8.3, we might get "failure" (r < 0), or "success" where just a part of the buffer is
* decompressed. But if we get a smaller amount of bytes than requested, we don't know whether there
* isn't enough data to fill the requested size or whether we just got a partial answer.
*/
if (r < 0 || (size_t) r < prefix_len + 1) {
size_t size;
if (sym_LZ4_versionNumber() >= 10803)
/* We trust that the newer lz4 decompresses the number of bytes we
* requested if available in the compressed string. */
return 0;
if (r > 0)
/* Compare what we have first, in case of mismatch we can
* shortcut the full comparison. */
if (memcmp(*buffer, prefix, r) != 0)
return 0;
/* Before version 1.8.3, lz4 always tries to decode full a "sequence",
* so in pathological cases might need to decompress the full field. */
r = decompress_blob_lz4(src, src_size, buffer, &size, 0);
if (r < 0)
return r;
if (size < prefix_len + 1)
return 0;
}
return memcmp(*buffer, prefix, prefix_len) == 0 &&
((const uint8_t*) *buffer)[prefix_len] == extra;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_startswith_zstd(
const void *src,
uint64_t src_size,
void **buffer,
const void *prefix,
size_t prefix_len,
uint8_t extra) {
assert(src);
assert(src_size > 0);
assert(buffer);
assert(prefix);
#if HAVE_ZSTD
int r;
r = dlopen_zstd(LOG_DEBUG);
if (r < 0)
return r;
uint64_t size = sym_ZSTD_getFrameContentSize(src, src_size);
if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN))
return -EBADMSG;
if (size < prefix_len + 1)
return 0; /* Decompressed text too short to match the prefix and extra */
_cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx();
if (!dctx)
return -ENOMEM;
if (!(greedy_realloc(buffer, MAX(sym_ZSTD_DStreamOutSize(), prefix_len + 1), 1)))
return -ENOMEM;
ZSTD_inBuffer input = {
.src = src,
.size = src_size,
};
ZSTD_outBuffer output = {
.dst = *buffer,
.size = MALLOC_SIZEOF_SAFE(*buffer),
};
size_t k;
k = sym_ZSTD_decompressStream(dctx, &output, &input);
if (sym_ZSTD_isError(k))
return log_debug_errno(zstd_ret_to_errno(k), "ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k));
if (output.pos < prefix_len + 1)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream.");
return memcmp(*buffer, prefix, prefix_len) == 0 &&
((const uint8_t*) *buffer)[prefix_len] == extra;
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_startswith_gzip(
const void *src,
uint64_t src_size,
void **buffer,
const void *prefix,
size_t prefix_len,
uint8_t extra) {
assert(src);
assert(src_size > 0);
assert(buffer);
assert(prefix);
#if HAVE_ZLIB
int r;
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
_cleanup_(inflateEnd_wrapper) z_stream s = {};
r = sym_inflateInit2_(&s, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(s));
if (r != Z_OK)
return -EBADMSG;
if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
return -ENOMEM;
size_t allocated = MALLOC_SIZEOF_SAFE(*buffer);
s.next_in = (void*) src;
s.avail_in = src_size;
s.next_out = *buffer;
s.avail_out = MIN(allocated, (size_t) UINT_MAX);
for (;;) {
r = sym_inflate(&s, Z_FINISH);
if (!IN_SET(r, Z_OK, Z_STREAM_END, Z_BUF_ERROR))
return -EBADMSG;
if (allocated - s.avail_out >= prefix_len + 1)
return memcmp(*buffer, prefix, prefix_len) == 0 &&
((const uint8_t*) *buffer)[prefix_len] == extra;
if (r == Z_STREAM_END)
return 0;
size_t used = allocated - s.avail_out;
if (!(greedy_realloc(buffer, allocated * 2, 1)))
return -ENOMEM;
allocated = MALLOC_SIZEOF_SAFE(*buffer);
s.avail_out = MIN(allocated - used, (size_t) UINT_MAX);
s.next_out = *(uint8_t**)buffer + used;
}
#else
return -EPROTONOSUPPORT;
#endif
}
static int decompress_startswith_bzip2(
const void *src,
uint64_t src_size,
void **buffer,
const void *prefix,
size_t prefix_len,
uint8_t extra) {
assert(src);
assert(src_size > 0);
assert(buffer);
assert(prefix);
#if HAVE_BZIP2
int r;
r = dlopen_bzip2(LOG_DEBUG);
if (r < 0)
return r;
if (src_size > UINT_MAX)
return -EFBIG;
_cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {};
r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
return -EBADMSG;
if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
return -ENOMEM;
size_t allocated = MALLOC_SIZEOF_SAFE(*buffer);
s.next_in = (char*) src;
s.avail_in = src_size;
s.next_out = *buffer;
s.avail_out = MIN(allocated, (size_t) UINT_MAX);
for (;;) {
r = sym_BZ2_bzDecompress(&s);
if (!IN_SET(r, BZ_OK, BZ_STREAM_END))
return -EBADMSG;
if (allocated - s.avail_out >= prefix_len + 1)
return memcmp(*buffer, prefix, prefix_len) == 0 &&
((const uint8_t*) *buffer)[prefix_len] == extra;
if (r == BZ_STREAM_END)
return 0;
size_t used = allocated - s.avail_out;
if (!(greedy_realloc(buffer, allocated * 2, 1)))
return -ENOMEM;
allocated = MALLOC_SIZEOF_SAFE(*buffer);
s.avail_out = MIN(allocated - used, (size_t) UINT_MAX);
s.next_out = (char*) *buffer + used;
}
#else
return -EPROTONOSUPPORT;
#endif
}
int decompress_startswith(
Compression compression,
const void *src, uint64_t src_size,
void **buffer,
const void *prefix, size_t prefix_len,
uint8_t extra) {
switch (compression) {
case COMPRESSION_XZ:
return decompress_startswith_xz(src, src_size, buffer, prefix, prefix_len, extra);
case COMPRESSION_LZ4:
return decompress_startswith_lz4(src, src_size, buffer, prefix, prefix_len, extra);
case COMPRESSION_ZSTD:
return decompress_startswith_zstd(src, src_size, buffer, prefix, prefix_len, extra);
case COMPRESSION_GZIP:
return decompress_startswith_gzip(src, src_size, buffer, prefix, prefix_len, extra);
case COMPRESSION_BZIP2:
return decompress_startswith_bzip2(src, src_size, buffer, prefix, prefix_len, extra);
default:
return -EOPNOTSUPP;
}
}
int compress_stream(
Compression type,
int fdf, int fdt,
uint64_t max_bytes,
uint64_t *ret_uncompressed_size) {
_cleanup_(compressor_freep) Compressor *c = NULL;
_cleanup_free_ void *buf = NULL;
_cleanup_free_ uint8_t *input = NULL;
size_t buf_size = 0, buf_alloc = 0;
uint64_t total_in = 0, total_out = 0;
int r;
assert(fdf >= 0);
assert(fdt >= 0);
r = compressor_new(&c, type);
if (r < 0)
return r;
input = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE);
if (!input)
return -ENOMEM;
for (;;) {
size_t m = COMPRESS_PIPE_BUFFER_SIZE;
ssize_t n;
if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes)
m = (size_t) max_bytes;
n = read(fdf, input, m);
if (n < 0)
return -errno;
if (n == 0) {
r = compressor_finish(c, &buf, &buf_size, &buf_alloc);
if (r < 0)
return r;
if (buf_size > 0) {
r = loop_write(fdt, buf, buf_size);
if (r < 0)
return r;
total_out += buf_size;
}
break;
}
total_in += n;
if (max_bytes != UINT64_MAX) {
assert(max_bytes >= (uint64_t) n);
max_bytes -= n;
}
r = compressor_start(c, input, n, &buf, &buf_size, &buf_alloc);
if (r < 0)
return r;
if (buf_size > 0) {
r = loop_write(fdt, buf, buf_size);
if (r < 0)
return r;
total_out += buf_size;
}
}
if (ret_uncompressed_size)
*ret_uncompressed_size = total_in;
if (total_in == 0)
log_debug("%s compression finished (no input data)", compression_to_string(type));
else
log_debug("%s compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
compression_to_string(type), total_in, total_out, (double) total_out / total_in * 100);
return 0;
}
/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on
* regular files without O_APPEND (O_APPEND ignores lseek position, which would collapse holes). */
static int should_sparse(int fd) {
struct stat st;
assert(fd >= 0);
if (fstat(fd, &st) < 0)
return -errno;
int flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
return S_ISREG(st.st_mode) && !FLAGS_SET(flags, O_APPEND);
}
/* After sparse decompression, set the file size to the current position to account for
* trailing holes that sparse_write() created via lseek but never extended the file size for. */
static int finalize_sparse(int fd) {
off_t pos;
assert(fd >= 0);
pos = lseek(fd, 0, SEEK_CUR);
if (pos < 0)
return -errno;
if (ftruncate(fd, pos) < 0)
return -errno;
return 0;
}
/* Common helper for decompress_stream_*() wrappers */
struct decompress_stream_userdata {
int fd;
uint64_t max_bytes;
uint64_t total_out;
bool sparse;
};
static int decompress_stream_write_callback(const void *data, size_t size, void *userdata) {
struct decompress_stream_userdata *u = ASSERT_PTR(userdata);
if (u->max_bytes != UINT64_MAX) {
if (u->max_bytes < size)
return -EFBIG;
u->max_bytes -= size;
}
u->total_out += size;
if (u->sparse) {
/* Note: sparse_write() does not retry on EINTR and converts short writes to -EIO.
* This is fine here since sparse mode is only used on regular files, where short
* writes and EINTR are not expected in practice. */
ssize_t k = sparse_write(u->fd, data, size, 64);
if (k < 0)
return (int) k;
return 0;
}
return loop_write(u->fd, data, size);
}
static int decompressor_new(Decompressor **ret, Compression type) {
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
int r;
#endif
assert(ret);
_cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1);
if (!c)
return -ENOMEM;
c->type = _COMPRESSION_INVALID;
switch (type) {
#if HAVE_XZ
case COMPRESSION_XZ:
r = dlopen_xz(LOG_DEBUG);
if (r < 0)
return r;
if (sym_lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED) != LZMA_OK)
return -EIO;
break;
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4: {
r = dlopen_lz4(LOG_DEBUG);
if (r < 0)
return r;
size_t rc = sym_LZ4F_createDecompressionContext(&c->d_lz4, LZ4F_VERSION);
if (sym_LZ4F_isError(rc))
return -ENOMEM;
break;
}
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD:
r = dlopen_zstd(LOG_DEBUG);
if (r < 0)
return r;
c->d_zstd = sym_ZSTD_createDCtx();
if (!c->d_zstd)
return -ENOMEM;
break;
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP:
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip));
if (r != Z_OK)
return -EIO;
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
r = dlopen_bzip2(LOG_DEBUG);
if (r < 0)
return r;
r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
return -EIO;
break;
#endif
default:
return -EOPNOTSUPP;
}
c->type = type;
c->encoding = false;
*ret = TAKE_PTR(c);
return 0;
}
int decompress_stream(
Compression type,
int fdf, int fdt,
uint64_t max_bytes) {
_cleanup_(compressor_freep) Decompressor *c = NULL;
_cleanup_free_ uint8_t *buf = NULL;
uint64_t total_in = 0;
int r;
assert(fdf >= 0);
assert(fdt >= 0);
r = decompressor_new(&c, type);
if (r < 0)
return r;
struct decompress_stream_userdata userdata = {
.fd = fdt,
.max_bytes = max_bytes,
.sparse = should_sparse(fdt) > 0,
};
buf = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE);
if (!buf)
return -ENOMEM;
for (;;) {
ssize_t n;
n = read(fdf, buf, COMPRESS_PIPE_BUFFER_SIZE);
if (n < 0)
return -errno;
if (n == 0)
break;
total_in += n;
r = decompressor_push(c, buf, n, decompress_stream_write_callback, &userdata);
if (r < 0)
return r;
}
if (total_in == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%s decompression failed: no data read",
compression_to_string(type));
if (userdata.sparse) {
r = finalize_sparse(fdt);
if (r < 0)
return r;
}
log_debug("%s decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
compression_to_string(type), total_in, userdata.total_out,
(double) userdata.total_out / total_in * 100);
return 0;
}
int decompress_stream_by_filename(const char *filename, int fdf, int fdt, uint64_t max_bytes) {
Compression c = compression_from_filename(filename);
if (c == COMPRESSION_NONE)
return -EPROTONOSUPPORT;
return decompress_stream(c, fdf, fdt, max_bytes);
}
/* Push-based streaming compression/decompression context API */
Compressor* compressor_free(Compressor *c) {
if (!c)
return NULL;
switch (c->type) {
#if HAVE_XZ
case COMPRESSION_XZ:
sym_lzma_end(&c->xz);
break;
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4:
if (c->encoding) {
sym_LZ4F_freeCompressionContext(c->c_lz4);
c->c_lz4 = NULL;
c->lz4_header = mfree(c->lz4_header);
} else {
sym_LZ4F_freeDecompressionContext(c->d_lz4);
c->d_lz4 = NULL;
}
break;
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD:
if (c->encoding) {
sym_ZSTD_freeCCtx(c->c_zstd);
c->c_zstd = NULL;
} else {
sym_ZSTD_freeDCtx(c->d_zstd);
c->d_zstd = NULL;
}
break;
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP:
if (c->encoding)
sym_deflateEnd(&c->gzip);
else
sym_inflateEnd(&c->gzip);
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
if (c->encoding)
sym_BZ2_bzCompressEnd(&c->bzip2);
else
sym_BZ2_bzDecompressEnd(&c->bzip2);
break;
#endif
default:
break;
}
return mfree(c);
}
Compression compressor_type(const Compressor *c) {
return c ? c->type : _COMPRESSION_INVALID;
}
int decompressor_detect(Decompressor **ret, const void *data, size_t size) {
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
int r;
#endif
assert(ret);
if (*ret)
return 1;
if (size < COMPRESSION_MAGIC_BYTES_MAX)
return 0;
assert(data);
Compression type = compression_detect_from_magic(data);
_cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1);
if (!c)
return -ENOMEM;
switch (type) {
#if HAVE_XZ
case COMPRESSION_XZ: {
r = dlopen_xz(LOG_DEBUG);
if (r < 0)
return r;
lzma_ret xzr = sym_lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED);
if (xzr != LZMA_OK)
return -EIO;
break;
}
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4: {
r = dlopen_lz4(LOG_DEBUG);
if (r < 0)
return r;
size_t rc = sym_LZ4F_createDecompressionContext(&c->d_lz4, LZ4F_VERSION);
if (sym_LZ4F_isError(rc))
return -ENOMEM;
break;
}
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD: {
r = dlopen_zstd(LOG_DEBUG);
if (r < 0)
return r;
c->d_zstd = sym_ZSTD_createDCtx();
if (!c->d_zstd)
return -ENOMEM;
break;
}
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP: {
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip));
if (r != Z_OK)
return -EIO;
break;
}
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2: {
r = dlopen_bzip2(LOG_DEBUG);
if (r < 0)
return r;
r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
return -EIO;
break;
}
#endif
default:
if (type != _COMPRESSION_INVALID)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Detected %s compression, but support is not compiled in.",
compression_to_string(type));
type = COMPRESSION_NONE;
break;
}
c->type = type;
c->encoding = false;
log_debug("Detected compression type: %s", compression_to_string(c->type));
*ret = TAKE_PTR(c);
return 1;
}
int decompressor_force_off(Decompressor **ret) {
assert(ret);
*ret = compressor_free(*ret);
Decompressor *c = new0(Decompressor, 1);
if (!c)
return -ENOMEM;
c->type = COMPRESSION_NONE;
c->encoding = false;
*ret = c;
return 0;
}
int decompressor_push(Decompressor *c, const void *data, size_t size, DecompressorCallback callback, void *userdata) {
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
_cleanup_free_ uint8_t *buffer = NULL;
#endif
int r;
assert(c);
assert(callback);
if (c->encoding)
return -EINVAL;
if (size == 0)
return 1;
assert(data);
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
if (c->type != COMPRESSION_NONE) {
buffer = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE);
if (!buffer)
return -ENOMEM;
}
#endif
switch (c->type) {
case COMPRESSION_NONE:
r = callback(data, size, userdata);
if (r < 0)
return r;
break;
#if HAVE_XZ
case COMPRESSION_XZ:
c->xz.next_in = data;
c->xz.avail_in = size;
while (c->xz.avail_in > 0) {
c->xz.next_out = buffer;
c->xz.avail_out = COMPRESS_PIPE_BUFFER_SIZE;
lzma_ret lzr = sym_lzma_code(&c->xz, LZMA_RUN);
if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END))
return -EBADMSG;
if (c->xz.avail_out < COMPRESS_PIPE_BUFFER_SIZE) {
r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->xz.avail_out, userdata);
if (r < 0)
return r;
}
}
break;
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4: {
const uint8_t *src = data;
size_t src_remaining = size;
while (src_remaining > 0) {
size_t produced = COMPRESS_PIPE_BUFFER_SIZE;
size_t consumed = src_remaining;
size_t rc = sym_LZ4F_decompress(c->d_lz4, buffer, &produced, src, &consumed, NULL);
if (sym_LZ4F_isError(rc))
return -EBADMSG;
if (consumed == 0 && produced == 0)
break; /* No progress possible with current input */
src += consumed;
src_remaining -= consumed;
if (produced > 0) {
r = callback(buffer, produced, userdata);
if (r < 0)
return r;
}
}
break;
}
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD: {
ZSTD_inBuffer input = {
.src = (void*) data,
.size = size,
};
while (input.pos < input.size) {
ZSTD_outBuffer output = {
.dst = buffer,
.size = COMPRESS_PIPE_BUFFER_SIZE,
};
size_t res = sym_ZSTD_decompressStream(c->d_zstd, &output, &input);
if (sym_ZSTD_isError(res))
return -EBADMSG;
if (output.pos > 0) {
r = callback(output.dst, output.pos, userdata);
if (r < 0)
return r;
}
}
break;
}
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP:
if (size > UINT_MAX)
return -EFBIG;
c->gzip.next_in = (void*) data;
c->gzip.avail_in = size;
while (c->gzip.avail_in > 0) {
c->gzip.next_out = buffer;
c->gzip.avail_out = COMPRESS_PIPE_BUFFER_SIZE;
int zr = sym_inflate(&c->gzip, Z_NO_FLUSH);
if (!IN_SET(zr, Z_OK, Z_STREAM_END))
return -EBADMSG;
if (c->gzip.avail_out < COMPRESS_PIPE_BUFFER_SIZE) {
r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->gzip.avail_out, userdata);
if (r < 0)
return r;
}
if (zr == Z_STREAM_END)
break;
}
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
if (size > UINT_MAX)
return -EFBIG;
c->bzip2.next_in = (char*) data;
c->bzip2.avail_in = size;
while (c->bzip2.avail_in > 0) {
c->bzip2.next_out = (char*) buffer;
c->bzip2.avail_out = COMPRESS_PIPE_BUFFER_SIZE;
int bzr = sym_BZ2_bzDecompress(&c->bzip2);
if (!IN_SET(bzr, BZ_OK, BZ_STREAM_END))
return -EBADMSG;
if (c->bzip2.avail_out < COMPRESS_PIPE_BUFFER_SIZE) {
r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->bzip2.avail_out, userdata);
if (r < 0)
return r;
}
if (bzr == BZ_STREAM_END)
break;
}
break;
#endif
default:
assert_not_reached();
}
return 1;
}
int compressor_new(Compressor **ret, Compression type) {
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
int r;
#endif
assert(ret);
_cleanup_(compressor_freep) Compressor *c = new0(Compressor, 1);
if (!c)
return -ENOMEM;
c->type = _COMPRESSION_INVALID;
/* Set encoding early so that compressor_freep calls the correct cleanup (compression vs
* decompression) if any operation in the switch fails after setting c->type. This is safe
* because _COMPRESSION_INVALID hits the default: break case regardless of the encoding flag. */
c->encoding = true;
switch (type) {
#if HAVE_XZ
case COMPRESSION_XZ: {
r = dlopen_xz(LOG_DEBUG);
if (r < 0)
return r;
lzma_ret xzr = sym_lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64);
if (xzr != LZMA_OK)
return -EIO;
c->type = COMPRESSION_XZ;
break;
}
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4: {
r = dlopen_lz4(LOG_DEBUG);
if (r < 0)
return r;
size_t rc = sym_LZ4F_createCompressionContext(&c->c_lz4, LZ4F_VERSION);
if (sym_LZ4F_isError(rc))
return -ENOMEM;
c->type = COMPRESSION_LZ4;
/* Generate the frame header and stash it for the first compressor_start call */
size_t header_bound = sym_LZ4F_compressBound(0, &lz4_preferences);
c->lz4_header = malloc(header_bound);
if (!c->lz4_header)
return -ENOMEM;
c->lz4_header_size = sym_LZ4F_compressBegin(c->c_lz4, c->lz4_header, header_bound, &lz4_preferences);
if (sym_LZ4F_isError(c->lz4_header_size))
return -EINVAL;
break;
}
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD:
r = dlopen_zstd(LOG_DEBUG);
if (r < 0)
return r;
c->c_zstd = sym_ZSTD_createCCtx();
if (!c->c_zstd)
return -ENOMEM;
c->type = COMPRESSION_ZSTD;
size_t z = sym_ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_compressionLevel, ZSTD_CLEVEL_DEFAULT);
if (sym_ZSTD_isError(z))
return -EIO;
z = sym_ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_checksumFlag, /* enable= */ 1);
if (sym_ZSTD_isError(z))
log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z));
break;
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP:
r = dlopen_zlib(LOG_DEBUG);
if (r < 0)
return r;
r = sym_deflateInit2_(&c->gzip,
Z_DEFAULT_COMPRESSION,
/* method= */ Z_DEFLATED,
/* windowBits= */ ZLIB_WBITS_GZIP,
/* memLevel= */ 8,
/* strategy= */ Z_DEFAULT_STRATEGY,
ZLIB_VERSION, (int) sizeof(c->gzip));
if (r != Z_OK)
return -EIO;
c->type = COMPRESSION_GZIP;
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
r = dlopen_bzip2(LOG_DEBUG);
if (r < 0)
return r;
r = sym_BZ2_bzCompressInit(&c->bzip2, /* blockSize100k= */ 9, /* verbosity= */ 0, /* workFactor= */ 0);
if (r != BZ_OK)
return -EIO;
c->type = COMPRESSION_BZIP2;
break;
#endif
case COMPRESSION_NONE:
c->type = COMPRESSION_NONE;
break;
default:
return -EOPNOTSUPP;
}
*ret = TAKE_PTR(c);
return 0;
}
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated, size_t need) {
assert(buffer);
assert(buffer_size);
assert(buffer_allocated);
need = MAX3(need, *buffer_size + 1, (size_t) COMPRESS_PIPE_BUFFER_SIZE);
if (*buffer_allocated >= need)
return 0;
if (!greedy_realloc(buffer, need, 1))
return -ENOMEM;
*buffer_allocated = MALLOC_SIZEOF_SAFE(*buffer);
return 1;
}
#endif
int compressor_start(
Compressor *c,
const void *data,
size_t size,
void **buffer,
size_t *buffer_size,
size_t *buffer_allocated) {
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
int r;
#endif
assert(c);
assert(buffer);
assert(buffer_size);
assert(buffer_allocated);
if (!c->encoding)
return -EINVAL;
if (size == 0)
return 0;
assert(data);
*buffer_size = 0;
switch (c->type) {
#if HAVE_XZ
case COMPRESSION_XZ:
c->xz.next_in = data;
c->xz.avail_in = size;
while (c->xz.avail_in > 0) {
lzma_ret lzr;
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
c->xz.next_out = (uint8_t*) *buffer + *buffer_size;
c->xz.avail_out = *buffer_allocated - *buffer_size;
lzr = sym_lzma_code(&c->xz, LZMA_RUN);
if (lzr != LZMA_OK)
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out;
}
break;
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4: {
/* Prepend any stashed frame header from compressor_new */
if (c->lz4_header_size > 0) {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, c->lz4_header_size);
if (r < 0)
return r;
memcpy(*buffer, c->lz4_header, c->lz4_header_size);
*buffer_size = c->lz4_header_size;
c->lz4_header = mfree(c->lz4_header);
c->lz4_header_size = 0;
}
size_t bound = sym_LZ4F_compressBound(size, &lz4_preferences);
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, *buffer_size + bound);
if (r < 0)
return r;
size_t n = sym_LZ4F_compressUpdate(c->c_lz4,
(uint8_t*) *buffer + *buffer_size,
*buffer_allocated - *buffer_size,
data, size, NULL);
if (sym_LZ4F_isError(n))
return -EIO;
*buffer_size += n;
break;
}
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD: {
ZSTD_inBuffer input = {
.src = data,
.size = size,
};
while (input.pos < input.size) {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
ZSTD_outBuffer output = {
.dst = ((uint8_t *) *buffer + *buffer_size),
.size = *buffer_allocated - *buffer_size,
};
size_t res = sym_ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_continue);
if (sym_ZSTD_isError(res))
return -EIO;
*buffer_size += output.pos;
}
break;
}
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP:
if (size > UINT_MAX)
return -EFBIG;
c->gzip.next_in = (void*) data;
c->gzip.avail_in = size;
while (c->gzip.avail_in > 0) {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX);
c->gzip.next_out = (uint8_t*) *buffer + *buffer_size;
c->gzip.avail_out = avail;
r = sym_deflate(&c->gzip, Z_NO_FLUSH);
if (r != Z_OK)
return -EIO;
*buffer_size += avail - c->gzip.avail_out;
}
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
if (size > UINT_MAX)
return -EFBIG;
c->bzip2.next_in = (void*) data;
c->bzip2.avail_in = size;
while (c->bzip2.avail_in > 0) {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX);
c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size);
c->bzip2.avail_out = avail;
r = sym_BZ2_bzCompress(&c->bzip2, BZ_RUN);
if (r != BZ_RUN_OK)
return -EIO;
*buffer_size += avail - c->bzip2.avail_out;
}
break;
#endif
case COMPRESSION_NONE:
if (*buffer_allocated < size) {
void *p;
p = realloc(*buffer, size);
if (!p)
return -ENOMEM;
*buffer = p;
*buffer_allocated = size;
}
memcpy(*buffer, data, size);
*buffer_size = size;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
int compressor_finish(Compressor *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) {
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
int r;
#endif
assert(c);
assert(buffer);
assert(buffer_size);
assert(buffer_allocated);
if (!c->encoding)
return -EINVAL;
*buffer_size = 0;
switch (c->type) {
#if HAVE_XZ
case COMPRESSION_XZ: {
lzma_ret lzr;
c->xz.avail_in = 0;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
c->xz.next_out = (uint8_t*) *buffer + *buffer_size;
c->xz.avail_out = *buffer_allocated - *buffer_size;
lzr = sym_lzma_code(&c->xz, LZMA_FINISH);
if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END))
return -EIO;
*buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out;
} while (lzr != LZMA_STREAM_END);
break;
}
#endif
#if HAVE_LZ4
case COMPRESSION_LZ4: {
size_t bound = sym_LZ4F_compressBound(0, &lz4_preferences);
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, bound);
if (r < 0)
return r;
size_t n = sym_LZ4F_compressEnd(c->c_lz4, *buffer, *buffer_allocated, NULL);
if (sym_LZ4F_isError(n))
return -EIO;
*buffer_size = n;
break;
}
#endif
#if HAVE_ZSTD
case COMPRESSION_ZSTD: {
ZSTD_inBuffer input = {};
size_t res;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
ZSTD_outBuffer output = {
.dst = ((uint8_t *) *buffer + *buffer_size),
.size = *buffer_allocated - *buffer_size,
};
res = sym_ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_end);
if (sym_ZSTD_isError(res))
return -EIO;
*buffer_size += output.pos;
} while (res != 0);
break;
}
#endif
#if HAVE_ZLIB
case COMPRESSION_GZIP:
c->gzip.avail_in = 0;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX);
c->gzip.next_out = (uint8_t*) *buffer + *buffer_size;
c->gzip.avail_out = avail;
r = sym_deflate(&c->gzip, Z_FINISH);
if (!IN_SET(r, Z_OK, Z_STREAM_END))
return -EIO;
*buffer_size += avail - c->gzip.avail_out;
} while (r != Z_STREAM_END);
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
c->bzip2.avail_in = 0;
do {
r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0);
if (r < 0)
return r;
size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX);
c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size);
c->bzip2.avail_out = avail;
r = sym_BZ2_bzCompress(&c->bzip2, BZ_FINISH);
if (!IN_SET(r, BZ_FINISH_OK, BZ_STREAM_END))
return -EIO;
*buffer_size += avail - c->bzip2.avail_out;
} while (r != BZ_STREAM_END);
break;
#endif
case COMPRESSION_NONE:
break;
default:
return -EOPNOTSUPP;
}
return 0;
}