stuffr_cli/compat/bzip2/mod.rs
1//! The `bzip2` / `bunzip2` / `bzcat` family: stuffr behaving as bzip2 1.0.8.
2//!
3//! Ported from bzip2 1.0.8's `bzip2.c`, copyright (C) 1996-2019 Julian R
4//! Seward, under the bzip2 licence reproduced in `LICENSE-bzip2`. Its flag
5//! handling, checks, messages and exit codes are followed line by line; the
6//! compression itself is stuffr's bzip2 codec, whose output is byte-identical
7//! to bzip2's at every level.
8//!
9//! Known deviations from bzip2 1.0.8:
10//!
11//! - `--version`/`-V`/`-L`/`--license` name stuffr and its version on the
12//! licence's first line (the rest is verbatim).
13//! - Verbosity 2 and above (`-vv`) behaves as `-v`: libbzip2's internal
14//! trace (block CRCs, sorting statistics) is not reachable through stuffr's
15//! codec API, and bzip2.c's own extra blank lines at that level only frame
16//! that trace, so they are left out with it.
17//! - Output is written under a temporary name and renamed into place, so an
18//! interrupted run leaves a `.stuffr-bzip2-*.tmp` file where bzip2's signal
19//! handler would have deleted its partial output.
20//! - When the parent ignores `SIGPIPE` (`SIG_IGN` inherited across exec),
21//! bzip2 reports a Broken pipe I/O error and exits 1; stuffr dies of
22//! `SIGPIPE`, because the Rust runtime sets `SIG_IGN` before `main` and the
23//! inherited disposition cannot be read on stable Rust.
24//! - The `perror` text after a cut-short stream reproduces the `errno` that
25//! bzip2 happens to leave behind as measured on macOS; elsewhere it prints
26//! `strerror(0)`.
27
28use std::ffi::OsString;
29use std::io::{self, Write};
30use std::path::Path;
31use std::process::ExitCode;
32use std::sync::atomic::AtomicU64;
33use std::sync::{Arc, Mutex};
34
35use super::host::{Host, SANDBOX_OUTPUT_CAP, Sandbox, reset_sigpipe};
36
37mod args;
38mod messages;
39mod run;
40
41/// Entry point for `bzip2`, `bunzip2` and `bzcat`; `name` is the canonical
42/// invoked name and `args` the arguments after `argv[0]`.
43pub fn run(name: &'static str, args: Vec<OsString>) -> ExitCode {
44 // bzip2 dies of SIGPIPE writing to a closed pipe.
45 reset_sigpipe();
46 let env = |k: &str| std::env::var_os(k);
47 ExitCode::from(main(name, &args, &env, &mut Host::Real, &mut io::stderr()))
48}
49
50/// bzip2.c's `main` after the program name: the flags, then the run.
51fn main(
52 name: &'static str,
53 args: &[OsString],
54 env: &dyn Fn(&str) -> Option<OsString>,
55 host: &mut Host,
56 err: &mut dyn Write,
57) -> u8 {
58 match args::parse(name, args, env, err) {
59 args::Parsed::Exit(c) => c,
60 args::Parsed::Run(s) => run::execute(name, s, host, err),
61 }
62}
63
64/// [`run`] without the process: operands resolve against `dir`, standard
65/// input is `stdin` (read as a pipe), standard output and error are
66/// captured and returned with the exit code, neither is a terminal, `$BZIP2`
67/// and `$BZIP` are unset, and `SIGPIPE` is left alone. Output is capped at
68/// [`SANDBOX_OUTPUT_CAP`], and the bytes `-t` decodes (which go nowhere)
69/// are charged to the same cap. For the fuzz target; not a stable interface.
70#[doc(hidden)]
71pub fn run_in(
72 dir: &Path,
73 name: &'static str,
74 args: &[OsString],
75 stdin: &[u8],
76) -> (i32, Vec<u8>, Vec<u8>) {
77 let stdout = Arc::new(Mutex::new(Vec::new()));
78 let mut host = Host::Sandbox(Sandbox {
79 dir: dir.to_path_buf(),
80 stdin: Some(stdin.to_vec()),
81 stdout: stdout.clone(),
82 budget: Arc::new(AtomicU64::new(SANDBOX_OUTPUT_CAP)),
83 reads: Default::default(),
84 });
85 let mut err = Vec::new();
86 let code = main(name, args, &|_| None, &mut host, &mut err);
87 drop(host);
88 let out = std::mem::take(&mut *stdout.lock().unwrap_or_else(|p| p.into_inner()));
89 (i32::from(code), out, err)
90}