Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions benches/bench_tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,16 @@ fn list_archive_50_files(bencher: divan::Bencher) {

bencher.bench_local(|| {
let input = File::open(&archive_path).unwrap();
operations::list::list_archive(input, &archive_path, false, CompressionMode::None).unwrap();
operations::list::list_archive(
input,
&archive_path,
false,
CompressionMode::None,
&[],
false,
0,
)
.unwrap();
});
}

Expand All @@ -160,7 +169,16 @@ fn list_archive_verbose_50_files(bencher: divan::Bencher) {

bencher.bench_local(|| {
let input = File::open(&archive_path).unwrap();
operations::list::list_archive(input, &archive_path, true, CompressionMode::None).unwrap();
operations::list::list_archive(
input,
&archive_path,
true,
CompressionMode::None,
&[],
false,
0,
)
.unwrap();
});
}

Expand All @@ -187,6 +205,9 @@ fn extract_archive_20_files(bencher: divan::Bencher) {
&archive_path,
false,
CompressionMode::None,
&[],
false,
0,
)
.unwrap();
});
Expand Down
4 changes: 4 additions & 0 deletions src/uu/tar/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ pub enum TarError {
/// Refusing to write archive contents to terminal
#[error("tar: Refusing to write archive contents to terminal (missing -f option?)")]
RefuseWriteArchiveToTerminal,

/// Cannot change working directory
#[error("tar: Cannot change directory to '{path}': {source}")]
CannotChangeDirectory { path: PathBuf, source: io::Error },
}

impl TarError {
Expand Down
75 changes: 57 additions & 18 deletions src/uu/tar/src/operations/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,96 @@ use crate::errors::TarError;
use crate::CompressionMode;
use std::io::Read;
use std::io::{self, BufWriter, Write};
use std::path::Path;
use std::path::{Component, Path, PathBuf};
use tar::Archive;
use uucore::error::UResult;

/// Extract files from a tar archive
use super::{entry_matches, strip_leading_components};

/// Extract files from a tar archive.
///
/// # Arguments
///
/// * `archive_path` - Path to the tar archive to extract
/// * `verbose` - Whether to print verbose output during extraction
/// * `input` - Readable source of the archive data
/// * `archive_path` - Path used for error messages and verbose header
/// * `verbose` - Print each extracted path to stdout
/// * `compression` - Compression mode to use when reading
/// * `file_patterns` - If non-empty, only extract entries matching these names (or globs)
/// * `wildcards` - Treat `file_patterns` as glob patterns (`*`, `?`)
/// * `strip_components` - Strip this many leading path components before writing
///
/// # Errors
///
/// Returns an error if:
/// - The archive file cannot be opened
/// - The archive format is invalid
/// - Files cannot be extracted due to I/O or permission errors
/// Returns an error if the archive cannot be read or entries cannot be extracted.
pub fn extract_archive(
input: impl Read,
archive_path: &Path,
verbose: bool,
compression: CompressionMode,
file_patterns: &[PathBuf],
wildcards: bool,
strip_components: u32,
) -> UResult<()> {
let reader = open_archive_reader(input, compression)?;
let mut archive = Archive::new(reader);
let mut out = BufWriter::new(io::stdout().lock());

// Extract to current directory
if verbose {
writeln!(out, "Extracting archive: {}", archive_path.display()).map_err(TarError::Io)?;
}

// Iterate through entries for verbose output and error handling
for entry_result in archive.entries().map_err(TarError::CannotReadEntries)? {
let mut entry = entry_result.map_err(TarError::CannotReadEntry)?;

// Get the path before unpacking (clone it so we can use it after borrowing entry mutably)
let path = entry
.path()
.map_err(TarError::CannotReadEntryPath)?
.to_path_buf();

if verbose {
writeln!(out, "{}", path.display()).map_err(TarError::Io)?;
if !entry_matches(&path, file_patterns, wildcards) {
continue;
}

// Unpack the entry
entry.unpack_in(".").map_err(|e| TarError::CannotExtract {
path: path.clone(),
source: e,
})?;
if strip_components > 0 {
let effective_path = match strip_leading_components(&path, strip_components) {
Some(p) => p,
None => continue,
};

// Reject paths that escape the destination after stripping.
if effective_path.is_absolute()
|| effective_path
.components()
.any(|c| c == Component::ParentDir)
{
continue;
}

if verbose {
writeln!(out, "{}", effective_path.display()).map_err(TarError::Io)?;
}

if let Some(parent) = effective_path.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).map_err(TarError::Io)?;
}
}
entry
.unpack(&effective_path)
.map_err(|e| TarError::CannotExtract {
path: effective_path,
source: e,
})?;
} else {
if verbose {
writeln!(out, "{}", path.display()).map_err(TarError::Io)?;
}

entry.unpack_in(".").map_err(|e| TarError::CannotExtract {
path: path.clone(),
source: e,
})?;
}
}

out.flush().map_err(TarError::Io)?;
Expand Down
Loading
Loading