use std::ffi::OsString;
use std::io::Write;
use crate::compat::host::Host;
pub(super) mod args;
mod lzw;
mod messages;
mod run;
pub(super) fn main(
name: &'static str,
argv0: &[u8],
args: &[OsString],
host: &mut Host,
err: &mut dyn Write,
) -> u8 {
match args::parse(name, argv0, args, err) {
args::Parsed::Exit(code) => code,
args::Parsed::Run(s) => run::execute(name, s, host, err),
}
}
#[cfg(test)]
mod tests {
use crate::compat::compress::{Flavour, run_in_counted};
use std::ffi::OsString;
use crate::compat::compress::test_scratch::scratch;
fn run(
d: &std::path::Path,
name: &'static str,
a: &[&str],
stdin: &[u8],
) -> (i32, Vec<u8>, String) {
let args: Vec<OsString> = a.iter().map(OsString::from).collect();
let (code, out, err, _) = run_in_counted(Flavour::Apple, d, name, &args, stdin, &|_| None);
(code, out, String::from_utf8(err).unwrap())
}
fn text(n: usize) -> Vec<u8> {
(0..n).map(|i| b"the quick brown fox "[i % 20]).collect()
}
#[test]
fn files_round_trip_in_the_sandbox() {
let d = scratch("trip");
let data = text(100_000);
std::fs::write(d.join("f"), &data).unwrap();
let args: Vec<OsString> = vec!["f".into()];
let (code, _, err, reads) =
run_in_counted(Flavour::Apple, &d, "compress", &args, b"", &|_| None);
assert_eq!(code, 0, "{}", String::from_utf8_lossy(&err));
let f = reads[&d.join("f")];
assert_eq!((f.opens, f.bytes, f.len), (1, 100_000, 100_000));
assert!(!d.join("f").exists());
let (code, _, err) = run(&d, "uncompress", &["f"], b"");
assert_eq!(code, 0, "{err}");
assert_eq!(std::fs::read(d.join("f")).unwrap(), data);
assert!(!d.join("f.Z").exists());
}
#[test]
fn a_failed_write_keeps_the_input() {
let d = scratch("full");
let mut x = 0x9e37_79b9_7f4a_7c15u64;
let noise: Vec<u8> = (0..9 << 20)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x >> 24) as u8
})
.collect();
std::fs::write(d.join("big"), &noise).unwrap();
let (code, _, err) = run(&d, "compress", &["-f", "big"], b"");
assert_eq!(
(code, err.as_str()),
(1, "compress: big.Z: No space left on device\n")
);
assert_eq!(std::fs::read(d.join("big")).unwrap(), noise);
assert!(!d.join("big.Z").exists());
let (code, out, err) = run(&d, "compress", &["-c", "big"], b"");
assert_eq!(
(code, err.as_str()),
(1, "compress: /dev/stdout: No space left on device\n")
);
assert!(!out.is_empty());
}
#[test]
fn a_failed_decompressing_write_keeps_the_input() {
let d = scratch("full-d");
let (code, z, err) = run(&d, "compress", &[], &vec![0u8; 9 << 20]);
assert_eq!(code, 0, "{err}");
std::fs::write(d.join("z.Z"), &z).unwrap();
let (code, _, err) = run(&d, "uncompress", &["z.Z"], b"");
assert_eq!(
(code, err.as_str()),
(1, "uncompress: z: No space left on device\n")
);
assert_eq!(std::fs::read(d.join("z.Z")).unwrap(), z);
assert!(!d.join("z").exists());
let (code, out, err) = run(&d, "uncompress", &["-c", "z.Z"], b"");
assert_eq!(
(code, err.as_str()),
(1, "uncompress: /dev/stdout: No space left on device\n")
);
assert!(!out.is_empty() && out.iter().all(|&b| b == 0));
assert_eq!(std::fs::read_dir(&*d).unwrap().count(), 1);
}
#[cfg(unix)]
#[test]
fn an_output_that_is_the_input_is_never_lost() {
let d = scratch("same");
let data = text(50_000);
std::fs::write(d.join("f"), &data).unwrap();
std::fs::hard_link(d.join("f"), d.join("f.Z")).unwrap();
let (code, _, err) = run(&d, "compress", &["-f", "f"], b"");
assert_eq!((code, err.as_str()), (0, ""));
assert!(!d.join("f").exists());
let (code, out, _) = run(&d, "uncompress", &["-c", "f.Z"], b"");
assert_eq!((code, out), (0, data.clone()));
std::os::unix::fs::symlink("f.Z", d.join("f")).unwrap();
let (code, _, err) = run(&d, "uncompress", &["-f", "f.Z"], b"");
assert_eq!((code, err.as_str()), (0, ""));
assert_eq!(std::fs::read(d.join("f")).unwrap(), data);
assert!(!d.join("f.Z").exists());
let left = std::fs::read_dir(&*d).unwrap().count();
assert_eq!(left, 1, "a temporary was left");
}
#[test]
fn hostile_streams_never_panic() {
let d = scratch("hostile");
let data = text(20_000);
let (_, good, _) = run(&d, "compress", &[], &data);
let mut x = 0x2545_f491_4f6c_dd1du64;
let mut next = move || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x
};
for round in 0..300 {
let mut b = good.clone();
match round % 3 {
0 => {
for _ in 0..1 + next() % 4 {
let at = (next() as usize) % b.len();
b[at] = next() as u8;
}
}
1 => b.truncate((next() as usize) % b.len()),
_ => {
b.truncate(3 + (next() as usize) % 64);
for v in &mut b[3..] {
*v = next() as u8;
}
b[2] = next() as u8;
}
}
let (code, _, _) = run(&d, "uncompress", &["-c"], &b);
assert!(matches!(code, 0 | 1), "round {round}: exit {code}");
}
}
}