Skip to main content

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