use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use crate::sniff::{sniff, Format, SNIFF_LEN};
#[derive(Debug, Default)]
pub struct Outcome {
pub members_seen: usize,
pub members_searched: usize,
pub errors: Vec<String>,
pub matched: bool,
pub notes: Vec<String>,
}
struct CacheGuard {
file: File,
drop_cache: bool,
}
impl Drop for CacheGuard {
fn drop(&mut self) {
crate::resource::finish(&self.file, self.drop_cache);
}
}
struct Guarded<R> {
inner: R,
_guard: CacheGuard,
}
impl<R: Read> Read for Guarded<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<R: Read + Send> Guarded<R> {
fn boxed(inner: R, guard: CacheGuard) -> Box<dyn Read + Send>
where
R: 'static,
{
Box::new(Guarded { inner, _guard: guard })
}
}
pub fn open_decoded(path: &Path) -> anyhow::Result<Box<dyn Read + Send>> {
open_stream(path, false)
}
pub fn open_decoded_dropping(path: &Path, drop_cache: bool) -> anyhow::Result<Box<dyn Read + Send>> {
open_stream(path, drop_cache)
}
fn open_stream(path: &Path, drop_cache: bool) -> anyhow::Result<Box<dyn Read + Send>> {
let f = File::open(path)?;
let dup = f.try_clone()?;
let rdr = io::BufReader::with_capacity(64 * 1024, f);
let (head, body) = peek(rdr)?;
let format = sniff(&head);
let drop_cache = drop_cache && format != Format::Plain;
crate::resource::prepare(&dup, drop_cache);
let guard = CacheGuard { file: dup, drop_cache };
Ok(match format {
Format::Gzip => Guarded::boxed(flate2::read::MultiGzDecoder::new(body), guard),
Format::Zstd => Guarded::boxed(zstd_decoder(body)?, guard),
Format::Xz => Guarded::boxed(liblzma::read::XzDecoder::new_multi_decoder(body), guard),
Format::Bzip2 => Guarded::boxed(bzip2::read::MultiBzDecoder::new(body), guard),
Format::Lz4 => Guarded::boxed(lz4_decoder(&head, body)?, guard),
Format::Tar | Format::Plain => Guarded::boxed(body, guard),
})
}
fn zstd_decoder<'a, R: Read>(r: R) -> io::Result<zstd::stream::read::Decoder<'a, io::BufReader<R>>> {
let mut d = zstd::stream::read::Decoder::new(r)?;
d.window_log_max(31)?;
Ok(d)
}
fn lz4_decoder<R: Read>(head: &[u8], mut r: R) -> io::Result<Lz4Frames<R>> {
let skip = crate::sniff::skippable_prefix(head).unwrap_or(0) as u64;
io::copy(&mut (&mut r).take(skip), &mut io::sink())?;
Ok(Lz4Frames(lz4_flex::frame::FrameDecoder::new(io::BufReader::new(r))))
}
struct Lz4Frames<R: Read>(lz4_flex::frame::FrameDecoder<io::BufReader<R>>);
impl<R: Read> Read for Lz4Frames<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
use io::BufRead;
loop {
let n = self.0.read(buf)?;
if n > 0 || buf.is_empty() || self.0.get_mut().fill_buf()?.is_empty() {
return Ok(n);
}
}
}
}
type Peeked<R> = (Vec<u8>, io::Chain<io::Cursor<Vec<u8>>, R>);
fn peek<R: Read>(mut rdr: R) -> io::Result<Peeked<R>> {
let mut head = vec![0u8; SNIFF_LEN];
let mut filled = 0;
while filled < head.len() {
match rdr.read(&mut head[filled..])? {
0 => break,
n => filled += n,
}
}
head.truncate(filled);
let replay = io::Cursor::new(head.clone());
Ok((head, replay.chain(rdr)))
}
pub fn for_each_member<R, F>(rdr: R, globs: Option<&crate::source::Globs>, mut f: F) -> Outcome
where
R: Read,
F: FnMut(&str, &mut dyn Read) -> io::Result<()>,
{
let mut out = Outcome::default();
let (head, mut body) = match peek(rdr) {
Ok(v) => v,
Err(e) => {
out.errors.push(format!("read failed: {e}"));
return out;
}
};
if sniff(&head) != Format::Tar {
out.members_seen = 1;
match f("", &mut body) {
Ok(()) => out.members_searched = 1,
Err(e) => out.errors.push(format!("search failed: {e}")),
}
return out;
}
let mut ar = tar::Archive::new(body);
let entries = match ar.entries() {
Ok(e) => e,
Err(e) => {
out.errors.push(format!("not a readable tar: {e}"));
return out;
}
};
for entry in entries {
let mut entry = match entry {
Ok(e) => e,
Err(e) => {
out.errors.push(format!("tar entry: {e}"));
break;
}
};
if !entry.header().entry_type().is_file() {
continue;
}
let name = match entry.path() {
Ok(p) => p.to_string_lossy().into_owned(),
Err(e) => {
out.errors.push(format!("bad member path: {e}"));
continue;
}
};
out.members_seen += 1;
if globs.is_some_and(|set| !set.is_match(&name)) {
continue;
}
let (inner_head, mut inner) = match peek(&mut entry) {
Ok(v) => v,
Err(e) => {
out.errors.push(format!("{name}: {e}"));
continue;
}
};
let res = match sniff(&inner_head) {
Format::Gzip => {
let mut dec = flate2::read::MultiGzDecoder::new(&mut inner);
f(&name, &mut dec)
}
Format::Zstd => match zstd_decoder(&mut inner) {
Ok(mut dec) => f(&name, &mut dec),
Err(e) => Err(e),
},
Format::Xz => {
let mut dec = liblzma::read::XzDecoder::new_multi_decoder(&mut inner);
f(&name, &mut dec)
}
Format::Bzip2 => {
let mut dec = bzip2::read::MultiBzDecoder::new(&mut inner);
f(&name, &mut dec)
}
Format::Lz4 => match lz4_decoder(&inner_head, &mut inner) {
Ok(mut dec) => f(&name, &mut dec),
Err(e) => Err(e),
},
_ => f(&name, &mut inner),
};
match res {
Ok(()) => out.members_searched += 1,
Err(e) => out.errors.push(format!("{name}: {e}")),
}
}
out
}