Skip to main content

Module lz4

Module lz4 

Source
Expand description

The lz4 family (lz4, unlz4, lz4cat, lz4c): stuffr behaving as the LZ4 command line interface v1.10.0.

A clean-room implementation (spec decision 3): lz4’s programs/ is GPL-2.0-or-later and was never read. Every rule comes from the man page (lz4.1), NEWS, README.md, lz4 -h/-H and probes of the 1.10.0 binary, and carries its source in a comment; every string is in messages.rs. liblz4 itself (BSD-2-Clause, LICENSE-lz4) is reached through stuffr::lz4_opts. cli.rs holds the command line and the decision table for where output goes.

Names (spec section 1, R12): dispatch registers the four exact names. The presets come from the invoked name’s last path component (after / or \), matched as lz4 1.10.0 matches it, probed through exec -a: the name itself, or the name followed by . and anything (unlz4.exe, lz4cat.1), case-sensitively; unlz4-1, xunlz4 and UNLZ4 match nothing. unlz4 decompresses; lz4cat is -dcfm at display level 1; lz4c reads the legacy arguments (-c0, -c1, -c2, -hc, -y) and lists them in its help.

The machine (R4): the automatic thread count is the logical cores less an eighth, less one (cli::resolve_threads); the real process counts its cores (host::cores), a sandboxed run ([run_in_counted]) sees 8 logical and 4 physical.

Signals (ruling T2-a): lz4 installs no handler; SIGINT, SIGTERM and SIGHUP kill it and the partial output stays under its name (probed). So nothing is armed here, and outputs are written directly to their names (io.rs), never to a temporary file: an interrupt leaves what lz4 leaves. run restores SIGPIPE’s default action, as the earlier families do (deviation 4).

Files (io.rs, decode.rs): compression, decompression and test over files and streams, with lz4’s order of opening, its overwrite question, -m/-r, --rm, sparse output and its display level 2 (ruling P3).

Known deviations (the source of truth; the README and the spec mirror this list):

  1. The banner names stuffr wherever lz4 prints it (-V, the first line of -h/-H, -v): *** lz4 v1.10.0 64-bit multithread (stuffr X.Y.Z), by Yann Collet ***. The prefix *** lz4 v1.10.0 is unchanged.
  2. Timing and host fields vary, as between two runs of lz4: the Done in line’s seconds, speed and cpu load, the progress refreshes, and the Using N threads count where two programs count the host’s cores differently. Refreshes are drawn at most every 200 ms (the first one always; at display level 4 every chunk), which display.rs pins from recordings; under a sandbox the clock is frozen at zero. While compressing, a refresh that both draw shows the same figures (each job’s refresh is over the input read before that job, inf for the first, and a one-shot frame’s over all of it; the pure tier’s ratio is of its own bytes, deviation 6). While decoding, stuffr offers one refresh per MiB decoded, while lz4’s count and figures vary from run to run with its writer thread (probed: the same legacy file drew 14, 14 or 8, 14 MiB, a -B4 frame 4, 7, 8), so those are timing too.
  3. An input that is also the output is never lost. Where the output named is the input file itself (the same device and inode: lz4 -f a a, lz4 -df s.lz4 s.lz4, a hard link, standard input redirected from it), lz4 truncates it before reading it, and the data is gone; stuffr opens nothing, prints NAME is the input file; not overwritten (stuffr) and counts that input as failed (exit 1, or one more in -m’s count), whatever -f says.
  4. Closed standard streams and a parent that ignores SIGPIPE behave as in the earlier families: a closed pipe kills stuffr by SIGPIPE, as it kills lz4 under the default disposition; where the parent ignores SIGPIPE, lz4 answers Error 70 (decoding) or Error 38 (compressing) and stuffr still dies of the signal (the Rust runtime ignores SIGPIPE before main, so the inherited disposition cannot be told apart). A closed standard input or output is reopened on /dev/null by the runtime before main, so lz4 -c <&- (lz4: Error 40) compresses an empty input.
  5. Out of scope and refused: -b# prints Benchmark mode is not available in this build (stuffr) and exits 1, once the whole command line has parsed (a usage error, -h or -V still answers first). -e# and -i#, the benchmark’s own settings, are accepted and have no effect without it, as in lz4. -h and -H still list all three (R8). The hidden -S (a benchmark switch with no effect elsewhere) is accepted and has no effect, as in lz4.
  6. The pure tier (lz4_flex): levels 3 to 12, --best and --favor-decSpeed compress with its single level; a corrupt frame’s Error N text may differ, and output written before it may be a strict prefix of lz4’s. The summary’s compressed size and ratio are those of its own bytes.
  7. A decode that fails keeps everything decoded before the fault in its output (a file or standard output). lz4 writes on another thread and exits from the failing one, so it leaves nothing or all of that, run to run (probed: 0 or 8800 bytes of 8800 for the same input).
  8. --list of a file that ends inside a block-size word: lz4 completes the word from an uninitialised buffer, which only the -v size of that last frame shows. On macOS that is garbage once a frame or skippable frame came before (probed: the same run to run, about 4.4 million, for a given shape) and zero before; on Linux it is garbage even in the first frame, different on every run (probed: the tarball build, lz4 --list -v of a header and one byte gave sizes from 1.0 to 2.1 billion; CI run 39166896170). stuffr completes it from the frame’s previous block-size word, as lz4 does for a second block (zero for the first).
  9. --list of a regular file that cannot be opened (mode 000): lz4 aborts on an assertion (exit 134); stuffr names the error (NAME: Permission denied) and exits 1.
  10. Hardening (ruling T5 of the 2026-10-31 security audit): a symlink at an output name is never written through. lz4 checks for an existing output with stat and opens it with fopen(.., "wb"), so a dangling symlink looks absent and a live one is truncated, and the data lands wherever the link points. stuffr checks with lstat: a symlink, dangling or not, is an existing file (already exists / the overwrite question); overwriting it (-f, or y) unlinks the link and creates a fresh file, its target untouched, and every output is opened O_NOFOLLOW. An output that takes the input’s mode (a frame from a regular file) is created 0600 and given that mode, owner and time through its own descriptor at the end, where lz4 creates it 0666 less the umask and sets them by name; one whose run fails before that is given lz4’s 0666 less the umask intersected with the input’s permission bits (lz4’s mode for an ordinary 0644 input; a private input’s partial output stays private). Why: a planted link must not redirect a write, and a private input must not sit readable while it is written.

Functions§

run
Entry point for the four names; name is the canonical invoked name, argv0 the program as invoked (its last component is the program name the usage texts print, and picks the presets) and args the arguments after it.