//! Compress or decompress files and streams with the gzip, gunzip, and zcat names. use flate2::{Compression, read::MultiGzDecoder, write::GzEncoder}; use std::{ fs::File, io::{self, Read, Write}, }; fn transfer(input: &mut dyn Read, output: &mut dyn Write, decompress: bool) -> io::Result<()> { if decompress { io::copy(&mut MultiGzDecoder::new(input), output)?; } else { let mut encoder = GzEncoder::new(output, Compression::default()); io::copy(input, &mut encoder)?; encoder.finish()?; } Ok(()) } fn run() -> Result<(), Box> { let mut args = std::env::args(); let program = args.next().unwrap_or_default(); let name = std::path::Path::new(&program) .file_stem() .unwrap_or_default() .to_string_lossy(); let mut decompress = name == "gunzip" || name == "zcat"; let mut stdout = name == "zcat"; let mut keep = false; let mut force = false; let mut paths = Vec::new(); while let Some(arg) = args.next() { if arg == "--" { paths.extend(args); break; } if arg.starts_with('-') && arg != "-" { for c in arg[1..].chars() { match c { 'd' => decompress = true, 'c' => stdout = true, 'k' => keep = true, 'f' => force = true, _ => return Err(format!("unsupported option: {c}").into()), } } } else { paths.push(arg); } } if paths.is_empty() { paths.push("-".into()); } for path in paths { let mut input: Box = if path == "-" { Box::new(io::stdin()) } else { Box::new(File::open(&path)?) }; if stdout || path == "-" { transfer(&mut input, &mut io::stdout(), decompress)?; } else { let dest = if decompress { path.strip_suffix(".gz") .ok_or("unknown suffix (expected .gz)")? .to_owned() } else { format!("{path}.gz") }; let mut output = File::options() .write(true) .create(force) .truncate(force) .create_new(!force) .open(&dest)?; let result = transfer(&mut input, &mut output, decompress).and_then(|()| output.flush()); if result.is_err() { drop(output); let _ = std::fs::remove_file(&dest); } result?; if !keep { std::fs::remove_file(path)?; } } } Ok(()) } fn main() { if let Err(e) = run() { eprintln!("gzip: {e}"); std::process::exit(1); } } #[cfg(test)] mod tests { use super::*; #[test] fn binary_round_trip_and_corrupt_input() { let source = [0, 255, 1, 42]; let mut encoded = Vec::new(); transfer(&mut source.as_slice(), &mut encoded, false).unwrap(); let mut decoded = Vec::new(); transfer(&mut encoded.as_slice(), &mut decoded, true).unwrap(); assert_eq!(decoded, source); assert!(transfer(&mut &b"bad gzip"[..], &mut Vec::new(), true).is_err()); } }