. "$BST_LIB" # LZ4 is a byte-oriented LZ77 compressor tuned for speed over ratio. Three # layers ship: the raw block codec (lz4.h), the high-compression coder that # trades encode time for ratio at the same decode speed (lz4hc.h), and the # framing layer that wraps blocks into a self-describing stream with magic, # block-size descriptor and content checksum (lz4frame.h). The distro is # static-only, so the shipped payload is four headers plus a pkg-config file # (-dev) and the archive liblz4.a (-staticdev). # # Like xxhash, lz4 brings its OWN consumer: the lz4 CLI links the library # statically and is installed beside it. So this suite asserts in two registers # -- the payload is real (below), and the codec actually RUNS (the second half). # For a compressor the strongest available runtime property is a round trip: # data that survives compress->decompress byte-identical pins the encoder and # the decoder against each other, which no header-and-symbols check can reach. inc=/usr/include/lz4.h inchc=/usr/include/lz4hc.h incf=/usr/include/lz4frame.h incfs=/usr/include/lz4frame_static.h pc=/usr/lib/pkgconfig/liblz4.pc lib=/usr/lib/liblz4.a check lz4/header-present test -f "$inc" check lz4/header-nonempty test -s "$inc" check_out lz4/header-guard "#define LZ4_H_2983827168210" cat "$inc" check lz4/hc-header-present test -f "$inchc" check_out lz4/hc-header-guard "#define LZ4_HC_H_19834876238432" cat "$inchc" check lz4/frame-header-present test -f "$incf" check_out lz4/frame-header-guard "#define LZ4F_H_09782039843" cat "$incf" # lz4frame_static.h is installed only by upstream's BUILD_STATIC branch, so its # presence is direct evidence the static install path ran rather than the shared # one. Its declarations were folded into lz4frame.h behind a macro; the shim # defines that macro and includes it. check lz4/frame-static-header-present test -f "$incfs" check_out lz4/frame-static-guard "#define LZ4FRAME_STATIC_H_0398209384" cat "$incfs" check_out lz4/frame-static-macro "#define LZ4F_STATIC_LINKING_ONLY" cat "$incfs" # 1.9.4, matching the pkg-config file. The header pads a macro name from its # value with runs of spaces, so these match whitespace-agnostically. check lz4/ver-major grep -qE "^#define[[:space:]]+LZ4_VERSION_MAJOR[[:space:]]+1" "$inc" check lz4/ver-minor grep -qE "^#define[[:space:]]+LZ4_VERSION_MINOR[[:space:]]+9" "$inc" check lz4/ver-release grep -qE "^#define[[:space:]]+LZ4_VERSION_RELEASE[[:space:]]+4" "$inc" check lz4/ver-number grep -qE "^#define[[:space:]]+LZ4_VERSION_NUMBER" "$inc" check lz4/ver-string grep -qE "^#define[[:space:]]+LZ4_VERSION_STRING" "$inc" # Every API assertion below is whitespace-agnostic on the paren: upstream # declares some entry points as `LZ4_versionNumber (void)`, with a space, so a # literal "NAME(" needle silently misses exactly those. # # The block codec: the one-shot pair, the bound a caller sizes its output buffer # with, and the full streaming lifecycle on both the compress and decompress # side (the dictionary calls are what make the stream continuable across # buffers). for f in LZ4_compress_default LZ4_decompress_safe LZ4_compressBound \ LZ4_compress_fast LZ4_compress_fast_extState LZ4_sizeofState \ LZ4_decompress_safe_partial \ LZ4_createStream LZ4_freeStream LZ4_resetStream_fast \ LZ4_loadDict LZ4_compress_fast_continue LZ4_saveDict \ LZ4_createStreamDecode LZ4_freeStreamDecode LZ4_setStreamDecode \ LZ4_decompress_safe_continue LZ4_decompress_safe_usingDict \ LZ4_versionNumber LZ4_versionString; do check "lz4/api-$f" grep -qE "$f[[:space:]]*\(" "$inc" done # The high-compression coder: same output format, slower encoder, so it has its # own state type and its own streaming lifecycle. for f in LZ4_compress_HC LZ4_compress_HC_extStateHC LZ4_sizeofStateHC \ LZ4_createStreamHC LZ4_freeStreamHC LZ4_resetStreamHC_fast \ LZ4_loadDictHC LZ4_compress_HC_continue LZ4_saveDictHC; do check "lz4/api-$f" grep -qE "$f[[:space:]]*\(" "$inchc" done # The level range the CLI's -1..-12 maps onto. check lz4/hc-clevel-min grep -qE "^#define[[:space:]]+LZ4HC_CLEVEL_MIN[[:space:]]+3" "$inchc" check lz4/hc-clevel-default grep -qE "^#define[[:space:]]+LZ4HC_CLEVEL_DEFAULT[[:space:]]+9" "$inchc" check lz4/hc-clevel-max grep -qE "^#define[[:space:]]+LZ4HC_CLEVEL_MAX[[:space:]]+12" "$inchc" # The framing layer: the one-shot frame call, both context lifecycles, the # begin/update/end compress sequence, the getFrameInfo/decompress read sequence, # and the error-code pair every LZ4F_ call reports through. for f in LZ4F_compressFrame LZ4F_compressBound \ LZ4F_createCompressionContext LZ4F_freeCompressionContext \ LZ4F_compressBegin LZ4F_compressUpdate LZ4F_compressEnd \ LZ4F_createDecompressionContext LZ4F_freeDecompressionContext \ LZ4F_getFrameInfo LZ4F_decompress LZ4F_isError LZ4F_getErrorName; do check "lz4/api-$f" grep -qE "$f[[:space:]]*\(" "$incf" done check_out lz4/frame-type-errorcode "LZ4F_errorCode_t" cat "$incf" check lz4/frame-version grep -qE "^#define[[:space:]]+LZ4F_VERSION" "$incf" # pkg-config: what a future recipe DEPENDS-ing on lz4 actually consumes. check lz4/pc-present test -f "$pc" check_out lz4/pc-name "Name: lz4" cat "$pc" check_out lz4/pc-version "Version: 1.9.4" cat "$pc" check_out lz4/pc-libs "-llz4" cat "$pc" check_out lz4/pc-cflags "Cflags: -I" cat "$pc" check_out lz4/pc-libdir "libdir=" cat "$pc" check lz4/lib-present test -f "$lib" check lz4/lib-nonempty test -s "$lib" check_out lz4/lib-archive "!" head -c 8 "$lib" # Flatten the archive to printable text once: GNU grep suppresses matches inside # binary data, and busybox tr does not handle [:print:] under -c, so the octal # range is the formulation both greps and both trs agree on. flat=${TMPDIR:-/tmp}/bst-lz4-flat.$$ tr -c '\40-\176\n' ' ' < "$lib" > "$flat" 2>/dev/null # One member per library source. All five must be there: a partial build would # still archive and still link the CLI, because the CLI compiles its own copies. for m in lz4.o lz4file.o lz4frame.o lz4hc.o xxhash.o; do check "lz4/member-$m" grep -q "$m" "$flat" done # Exported symbols sampled across all three layers, so the archive's index is # evidence the wasm32 compile really produced each one. for s in LZ4_compress_default LZ4_decompress_safe LZ4_compressBound \ LZ4_compress_fast LZ4_createStream LZ4_decompress_safe_continue \ LZ4_compress_HC LZ4_createStreamHC LZ4_compress_HC_continue \ LZ4F_compressFrame LZ4F_compressBegin LZ4F_decompress \ LZ4F_getFrameInfo LZ4F_isError LZ4_versionString; do check "lz4/sym-$s" grep -q "$s" "$flat" done # lz4 vendors its own copy of xxHash for the frame checksum, and this image now # ALSO ships the real libxxhash.a (BRINTOS-164). Upstream avoids the collision # by building its copy under -DXXH_NAMESPACE=LZ4_, so the symbols are LZ4_XXH32 # rather than XXH32. Assert both halves: the prefixed name is present, and no # bare XXH32 entry point escaped. The second is the one that would actually # break a link, and it is invisible to a presence-only check. check lz4/xxh-namespaced grep -q "LZ4_XXH32" "$flat" if grep -qE "(^|[^A-Za-z0-9_])XXH32_createState" "$flat"; then bst_notok lz4/xxh-no-bare-symbol "un-namespaced XXH32_createState in liblz4.a" else bst_ok lz4/xxh-no-bare-symbol fi # No shared object and no libtool archive: static-only is distro policy, and # this recipe needed a bbappend to hold upstream to it. check lz4/no-shared sh -c '! ls /usr/lib/liblz4.so* >/dev/null 2>&1' check lz4/no-libtool sh -c '! ls /usr/lib/liblz4.la >/dev/null 2>&1' # --------------------------------------------------------------------------- # Runtime register: the codec executing on wasm32, through the shipped CLI. # --------------------------------------------------------------------------- require lz4 lz4/cli-present check lz4/cli-alias-lz4cat test -e /usr/bin/lz4cat check lz4/cli-alias-unlz4 test -e /usr/bin/unlz4 check lz4/cli-alias-lz4c test -e /usr/bin/lz4c # The banner embeds the pointer width ("32-bits" here, "64-bits" on a host # build), so only the version is asserted -- matching the whole banner would # fail for a reason that has nothing to do with lz4 working. check_out lz4/version "v1.9.4" lz4 --version d=${TMPDIR:-/tmp}/bst-lz4.$$ mkdir -p "$d" # A small fixed vector for the round-trip and framing assertions, and a larger # generated one for the level and multi-block work. Both are reproducible from # tools already on the image, so the suite stays a single file with no payload # to keep in sync. seq's output is compressible but not degenerately so, which # is what makes the level gradient below meaningful: `yes`-style input hits the # same size at every level and would assert nothing. printf 'brintOS lz4 round-trip vector 0123456789\n' > "$d/small.txt" seq 1 20000 > "$d/seq.txt" check lz4/compress-file lz4 -q -f "$d/small.txt" "$d/small.lz4" check lz4/compressed-exists test -s "$d/small.lz4" # The LZ4 frame magic, 04 22 4d 18 little-endian. This is what makes the output # a self-describing frame rather than a bare block. check_out lz4/frame-magic "04 22 4d 18" sh -c "od -An -tx1 -N4 '$d/small.lz4' | tr -s ' '" check lz4/decompress-file lz4 -d -q -f "$d/small.lz4" "$d/small.out" check lz4/roundtrip-identical cmp -s "$d/small.txt" "$d/small.out" # -t decodes without writing, and reports the decoded size. The report goes to # stderr (stdout carries decompressed data in -c mode), which check_out folds in. check_out lz4/test-mode "decoded 41 bytes" lz4 -t "$d/small.lz4" # The pipe route: compress to stdout, decompress from stdin, never touching an # intermediate file. This is the path a shell pipeline takes and it exercises # the streaming API rather than the whole-file one. lz4 -c -q "$d/small.txt" 2>/dev/null | lz4 -dc -q 2>/dev/null > "$d/pipe.out" check lz4/pipe-roundtrip cmp -s "$d/small.txt" "$d/pipe.out" # argv[0] dispatch: the installed symlinks select the mode by the name they are # invoked under, with no flag. lz4cat "$d/small.lz4" > "$d/cat.out" 2>/dev/null check lz4/alias-lz4cat cmp -s "$d/small.txt" "$d/cat.out" unlz4 -q -f "$d/small.lz4" "$d/unlz4.out" 2>/dev/null check lz4/alias-unlz4 cmp -s "$d/small.txt" "$d/unlz4.out" lz4c -q -f "$d/small.txt" "$d/lz4c.lz4" 2>/dev/null lz4c -d -q -f "$d/lz4c.lz4" "$d/lz4c.out" 2>/dev/null check lz4/alias-lz4c cmp -s "$d/small.txt" "$d/lz4c.out" # Compression levels. -1 is the fast block codec, -9 and -12 route through the # HC coder, so a strictly decreasing size across the three is evidence the HC # path is real and not aliased back to the fast one -- and each must still # round-trip, because a smaller output that does not decode is not compression. orig=$(wc -c < "$d/seq.txt") for lvl in 1 9 12; do lz4 -q -f "-$lvl" "$d/seq.txt" "$d/seq$lvl.lz4" 2>/dev/null check "lz4/level$lvl-compressed" test -s "$d/seq$lvl.lz4" lz4 -d -q -f "$d/seq$lvl.lz4" "$d/seq$lvl.out" 2>/dev/null check "lz4/level$lvl-roundtrip" cmp -s "$d/seq.txt" "$d/seq$lvl.out" done s1=$(wc -c < "$d/seq1.lz4") s9=$(wc -c < "$d/seq9.lz4") s12=$(wc -c < "$d/seq12.lz4") check "lz4/level1-smaller-than-input" test "$s1" -lt "$orig" check "lz4/level9-smaller-than-level1" test "$s9" -lt "$s1" check "lz4/level12-smaller-than-level9" test "$s12" -lt "$s9" # Multi-block: -B4 selects 64 KiB blocks, so the ~109 KB input is split across # two of them and the frame's block loop runs more than once. --list reads the # frame descriptor back and names the block-size id (B4) and independence (I). lz4 -q -f -B4 "$d/seq.txt" "$d/b4.lz4" 2>/dev/null check lz4/multiblock-compressed test -s "$d/b4.lz4" check_out lz4/multiblock-descriptor "B4I" lz4 --list "$d/b4.lz4" lz4 -d -q -f "$d/b4.lz4" "$d/b4.out" 2>/dev/null check lz4/multiblock-roundtrip cmp -s "$d/seq.txt" "$d/b4.out" # -BD makes each block reference the previous one (linked), which compresses # better but forbids independent block decode; the descriptor records the # difference as D rather than I. lz4 -q -f -B4 -BD "$d/seq.txt" "$d/bd.lz4" 2>/dev/null check_out lz4/linked-blocks-descriptor "B4D" lz4 --list "$d/bd.lz4" lz4 -d -q -f "$d/bd.lz4" "$d/bd.out" 2>/dev/null check lz4/linked-blocks-roundtrip cmp -s "$d/seq.txt" "$d/bd.out" # --content-size writes the uncompressed length into the frame header, so # --list can report it; without it the column reads as unknown. --list scales to # KiB, so the 108,894-byte input reads as 106.34K. lz4 -q -f --content-size "$d/seq.txt" "$d/cs.lz4" 2>/dev/null check_out lz4/content-size-listed "106.34K" lz4 --list "$d/cs.lz4" # The legacy (pre-frame) container, magic 02 21 4c 18, still written under -l # for compatibility with old readers. lz4 -q -f -l "$d/small.txt" "$d/legacy.lz4" 2>/dev/null check_out lz4/legacy-magic "02 21 4c 18" sh -c "od -An -tx1 -N4 '$d/legacy.lz4' | tr -s ' '" # -k keeps the source file; the default is to keep it too, but -k is what a # caller writes when it matters, so the flag is pinned. cp "$d/small.txt" "$d/keep.txt" lz4 -q -f -k "$d/keep.txt" "$d/keep.lz4" 2>/dev/null check lz4/keep-source test -f "$d/keep.txt" # -m compresses several files in one invocation, each to its own .lz4. cp "$d/small.txt" "$d/m1.txt"; cp "$d/small.txt" "$d/m2.txt" lz4 -q -f -m "$d/m1.txt" "$d/m2.txt" 2>/dev/null check lz4/multifile-first test -s "$d/m1.txt.lz4" check lz4/multifile-second test -s "$d/m2.txt.lz4" # Corruption must be DETECTED, not silently decoded: this is what separates # comparing from echoing. Overwriting eight bytes inside the compressed payload # makes the decoder fail with lz4's ERROR_decompressionFailed, exit 66. cp "$d/seq1.lz4" "$d/corrupt.lz4" printf 'ZZZZZZZZ' | dd of="$d/corrupt.lz4" bs=1 seek=1000 conv=notrunc 2>/dev/null check_rc lz4/corrupt-detected 66 lz4 -t "$d/corrupt.lz4" # Two distinct error paths, each with its own documented status: a file that is # not there, and a file that is there but is not lz4 data. check_rc lz4/err-missing-file 1 lz4 -t "$d/nosuch.lz4" check_rc lz4/err-not-lz4 44 lz4 -d -q -c "$d/small.txt" rm -rf "$d" "$flat"