107 lines
3.3 KiB
Rust
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());
|
|
}
|
|
}
|