use std::io;
use std::path::Path;
use fs_err as fs;
use tar::{Archive, EntryType};
pub fn tar_unpack_file(path: &Path, dst: &Path) -> Result<(), io::Error> {
let reader = io::BufReader::new(fs::File::open(path)?);
tar_unpack_reader(reader, dst)?;
Ok(())
}
pub fn tar_unpack_reader<R: io::Read>(reader: R, dst: &Path) -> Result<R, io::Error> {
let mut archive = Archive::new(reader);
archive.set_overwrite(false);
fs::create_dir_all(dst)?;
let dst = &fs::canonicalize(dst).unwrap_or(dst.to_path_buf());
for entry in archive.entries().map_err(|err| {
io::Error::new(
err.kind(),
#[cfg(not(debug_assertions))]
format!("Malformed tar archive, unable to read entries"),
#[cfg(debug_assertions)]
format!("Malformed tar archive, unable to read entries: {err}"),
)
})? {
let mut entry = entry.map_err(|err| {
io::Error::new(
err.kind(),
#[cfg(not(debug_assertions))]
format!("Malformed tar archive, reached unknown entry"),
#[cfg(debug_assertions)]
format!("Malformed tar archive, reached unknown entry: {err}"),
)
})?;
#[expect(clippy::wildcard_enum_match_arm, reason = "#[non_exhaustive] enum")]
match entry.header().entry_type() {
EntryType::Directory | EntryType::Regular | EntryType::GNUSparse => (),
entry_type => {
return Err(io::Error::other(format!(
"Forbidden entry type in tar archive: {entry_type:?}"
)));
}
}
entry.unpack_in(dst)?;
}
Ok(archive.into_inner())
}