use std::path::Path;
use async_compression::tokio::bufread::ZstdDecoder;
use futures_util::stream::StreamExt;
use crate::ExtractError;
pub(super) const DEFAULT_BUF_SIZE: usize = 128 * 1024;
#[cfg(unix)]
const EXECUTABLE_MODE_BITS: u32 = 0o111;
pub(super) async fn unpack_tar_archive<R: tokio::io::AsyncRead + Unpin>(
mut archive: tokio_tar::Archive<R>,
destination: &Path,
) -> Result<(), ExtractError> {
let destination = tokio::fs::canonicalize(destination)
.await
.map_err(ExtractError::IoError)?;
let mut entries = archive.entries().map_err(ExtractError::IoError)?;
#[allow(clippy::default_trait_access)] let mut memo = Default::default();
while let Some(entry) = entries.next().await {
let mut file = entry.map_err(ExtractError::IoError)?;
#[cfg_attr(not(unix), allow(unused_variables))]
let unpacked_path = match file.unpack_in_raw(&destination, &mut memo).await {
Ok(path) => path,
Err(e) => {
#[cfg(windows)]
if file.header().entry_type().is_symlink() {
tracing::warn!(
"skipping symlink in tar archive: {}: {e}",
file.path().map_err(ExtractError::IoError)?.display()
);
continue;
}
return Err(ExtractError::IoError(e));
}
};
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let entry_type = file.header().entry_type();
if entry_type.is_file() || entry_type.is_hard_link() {
let mode = file.header().mode().map_err(ExtractError::IoError)?;
let has_any_executable_bit = mode & EXECUTABLE_MODE_BITS;
if has_any_executable_bit != 0
&& let Some(path) = unpacked_path
{
let metadata = tokio::fs::metadata(&path)
.await
.map_err(ExtractError::IoError)?;
let permissions = metadata.permissions();
if permissions.mode() & EXECUTABLE_MODE_BITS != EXECUTABLE_MODE_BITS {
tokio::fs::set_permissions(
&path,
std::fs::Permissions::from_mode(
permissions.mode() | EXECUTABLE_MODE_BITS,
),
)
.await
.map_err(ExtractError::IoError)?;
}
}
}
}
}
Ok(())
}
pub(super) async fn extract_tar_zst_entry<R: tokio::io::AsyncRead + Unpin>(
mut reader: R,
destination: &Path,
) -> Result<(), ExtractError> {
let buf_reader = tokio::io::BufReader::with_capacity(DEFAULT_BUF_SIZE, &mut reader);
let decoder = ZstdDecoder::new(buf_reader);
let archive = tokio_tar::ArchiveBuilder::new(decoder)
.set_preserve_mtime(true)
.set_preserve_permissions(false)
.set_unpack_xattrs(false)
.set_allow_external_symlinks(true)
.build();
unpack_tar_archive(archive, destination).await?;
Ok(())
}