Move the push-based streaming compression API from import-compress.c into compress.c and delete import-compress.c/h. This consolidates all compression code in one place and makes all compression libraries (liblzma, liblz4, libzstd, libz, libbz2) runtime-loaded via dlopen instead of directly linked. Introduce opaque Compressor/Decompressor types backed by a heap- allocated struct defined only in compress.c, keeping all third-party library headers out of compress.h. Rewrite the per-codec fd-to-fd stream functions as thin wrappers around the push API via generic compress_stream()/decompress_stream() taking a Compression type parameter. Integrate LZ4 into this framework using the LZ4 Frame API, eliminating all LZ4 special-casing. Extend the Compression enum with COMPRESSION_GZIP and COMPRESSION_BZIP2 and add the corresponding blob, startswith, and stream functions for both. Rename the ImportCompress types and functions: ImportCompressType becomes the existing Compression enum, ImportCompress becomes Compressor (with Decompressor typedef), and all import_compress_*/import_uncompress_* become compressor_*/decompressor_*. Rename dlopen_lzma() to dlopen_xz() for consistency. Make compression_to_string() return lowercase by default. Add INT_MAX/UINT_MAX overflow checks for LZ4, zlib, and bzip2 blob functions where the codec API uses narrower integer types than our uint64_t parameters. Migrate test-compress.c and test-compress-benchmark.c to the TEST() macro framework, new assertion macros, and codec-generic loops instead of per-codec duplication. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
164 lines
5.7 KiB
C
164 lines
5.7 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "alloc-util.h"
|
|
#include "argv-util.h"
|
|
#include "bcd.h"
|
|
#include "compress.h"
|
|
#include "fileio.h"
|
|
#include "tests.h"
|
|
#include "utf8.h"
|
|
|
|
/* Include the implementation directly, so we can poke at some internals. */
|
|
#include "bcd.c"
|
|
|
|
static void load_bcd(const char *path, void **ret_bcd, size_t *ret_bcd_len) {
|
|
size_t len;
|
|
_cleanup_free_ char *fn = NULL, *compressed = NULL;
|
|
|
|
assert_se(get_testdata_dir(path, &fn) >= 0);
|
|
assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &compressed, &len) >= 0);
|
|
assert_se(decompress_blob(COMPRESSION_ZSTD, compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0);
|
|
}
|
|
|
|
static void test_get_bcd_title_one(
|
|
const char *path,
|
|
const char16_t *title_expect,
|
|
size_t title_len_expect) {
|
|
|
|
size_t len;
|
|
_cleanup_free_ void *bcd = NULL;
|
|
|
|
log_info("/* %s(%s) */", __func__, path);
|
|
|
|
load_bcd(path, &bcd, &len);
|
|
|
|
char16_t *title = get_bcd_title(bcd, len);
|
|
if (title_expect) {
|
|
assert_se(title);
|
|
assert_se(memcmp(title, title_expect, title_len_expect) == 0);
|
|
} else
|
|
assert_se(!title);
|
|
}
|
|
|
|
TEST(get_bcd_title) {
|
|
test_get_bcd_title_one("test-bcd/win10.bcd.zst", u"Windows 10", sizeof(u"Windows 10"));
|
|
|
|
test_get_bcd_title_one("test-bcd/description-bad-type.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/description-empty.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/description-missing.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/description-too-small.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/displayorder-bad-name.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/displayorder-bad-size.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/displayorder-bad-type.bcd.zst", NULL, 0);
|
|
test_get_bcd_title_one("test-bcd/empty.bcd.zst", NULL, 0);
|
|
}
|
|
|
|
TEST(base_block) {
|
|
size_t len;
|
|
BaseBlock backup;
|
|
uint8_t *bcd_base;
|
|
_cleanup_free_ BaseBlock *bcd = NULL;
|
|
|
|
load_bcd("test-bcd/win10.bcd.zst", (void **) &bcd, &len);
|
|
backup = *bcd;
|
|
bcd_base = (uint8_t *) bcd;
|
|
|
|
assert_se(get_bcd_title(bcd_base, len));
|
|
|
|
/* Try various "corruptions" of the base block. */
|
|
|
|
assert_se(!get_bcd_title(bcd_base, sizeof(BaseBlock) - 1));
|
|
|
|
bcd->sig = 0;
|
|
assert_se(!get_bcd_title(bcd_base, len));
|
|
*bcd = backup;
|
|
|
|
bcd->version_minor = 2;
|
|
assert_se(!get_bcd_title(bcd_base, len));
|
|
*bcd = backup;
|
|
|
|
bcd->version_major = 4;
|
|
assert_se(!get_bcd_title(bcd_base, len));
|
|
*bcd = backup;
|
|
|
|
bcd->type = 1;
|
|
assert_se(!get_bcd_title(bcd_base, len));
|
|
*bcd = backup;
|
|
|
|
bcd->primary_seqnum++;
|
|
assert_se(!get_bcd_title(bcd_base, len));
|
|
*bcd = backup;
|
|
}
|
|
|
|
TEST(bad_bcd) {
|
|
size_t len;
|
|
uint8_t *hbins;
|
|
uint32_t offset;
|
|
_cleanup_free_ void *bcd = NULL;
|
|
|
|
/* This BCD hive has been manipulated to have bad offsets/sizes at various places. */
|
|
load_bcd("test-bcd/corrupt.bcd.zst", &bcd, &len);
|
|
|
|
assert_se(len >= HIVE_CELL_OFFSET);
|
|
hbins = (uint8_t *) bcd + HIVE_CELL_OFFSET;
|
|
len -= HIVE_CELL_OFFSET;
|
|
offset = ((BaseBlock *) bcd)->root_cell_offset;
|
|
|
|
const Key *root = get_key(hbins, len, offset, "\0");
|
|
assert_se(root);
|
|
assert_se(!get_key(hbins, sizeof(Key) - 1, offset, "\0"));
|
|
|
|
assert_se(!get_key(hbins, len, offset, "\0BadOffset\0"));
|
|
assert_se(!get_key(hbins, len, offset, "\0BadSig\0"));
|
|
assert_se(!get_key(hbins, len, offset, "\0BadKeyNameLen\0"));
|
|
assert_se(!get_key(hbins, len, offset, "\0SubkeyBadOffset\0Dummy\0"));
|
|
assert_se(!get_key(hbins, len, offset, "\0SubkeyBadSig\0Dummy\0"));
|
|
assert_se(!get_key(hbins, len, offset, "\0SubkeyBadNEntries\0Dummy\0"));
|
|
|
|
assert_se(!get_key_value(hbins, len, root, "Dummy"));
|
|
|
|
const Key *kv_bad_offset = get_key(hbins, len, offset, "\0KeyValuesBadOffset\0");
|
|
assert_se(kv_bad_offset);
|
|
assert_se(!get_key_value(hbins, len, kv_bad_offset, "Dummy"));
|
|
|
|
const Key *kv_bad_n_key_values = get_key(hbins, len, offset, "\0KeyValuesBadNKeyValues\0");
|
|
assert_se(kv_bad_n_key_values);
|
|
assert_se(!get_key_value(hbins, len, kv_bad_n_key_values, "Dummy"));
|
|
|
|
const Key *kv = get_key(hbins, len, offset, "\0KeyValues\0");
|
|
assert_se(kv);
|
|
|
|
assert_se(!get_key_value(hbins, len, kv, "BadOffset"));
|
|
assert_se(!get_key_value(hbins, len, kv, "BadSig"));
|
|
assert_se(!get_key_value(hbins, len, kv, "BadNameLen"));
|
|
assert_se(!get_key_value(hbins, len, kv, "InlineData"));
|
|
assert_se(!get_key_value(hbins, len, kv, "BadDataOffset"));
|
|
assert_se(!get_key_value(hbins, len, kv, "BadDataSize"));
|
|
}
|
|
|
|
TEST(argv_bcds) {
|
|
for (int i = 1; i < saved_argc; i++) {
|
|
size_t len;
|
|
_cleanup_free_ void *bcd = NULL;
|
|
|
|
assert_se(read_full_file_full(
|
|
AT_FDCWD,
|
|
saved_argv[i],
|
|
UINT64_MAX,
|
|
SIZE_MAX,
|
|
0,
|
|
NULL,
|
|
(char **) &bcd,
|
|
&len) >= 0);
|
|
|
|
char16_t *title = get_bcd_title(bcd, len);
|
|
if (title) {
|
|
_cleanup_free_ char *title_utf8 = utf16_to_utf8(title, SIZE_MAX);
|
|
log_info("%s: \"%s\"", saved_argv[i], title_utf8);
|
|
} else
|
|
log_info("%s: Bad BCD", saved_argv[i]);
|
|
}
|
|
}
|
|
|
|
DEFINE_TEST_MAIN(LOG_INFO);
|