Files
sausage/userland/archive-tools/src/gzip.rs
T

107 lines
3.3 KiB
Rust

//! 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<dyn std::error::Error>> {
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<dyn Read> = 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());
}
}