mirror of
https://github.com/uutils/tar.git
synced 2026-06-10 16:14:35 -07:00
Absolute Paths Support for Archives Creation (#291)
Added support for -P and --absolute-names flags to preserve absolute paths when creating archives, matching GNU tar behavior. * CLI: Added -P / --absolute-names options to uu_app. * Operations: Configured tar::Builder to preserve absolute paths based on the new flag. * Normalization: Updated normalize_path to skip stripping root and prefix components when absolute paths are allowed. * Tests: Added integration tests for path preservation and trimming during archive creation and extraction.
This commit is contained in:
@@ -18,6 +18,7 @@ use uucore::error::UResult;
|
||||
///
|
||||
/// * `archive_path` - Path where the tar archive should be created
|
||||
/// * `files` - Slice of file paths to add to the archive
|
||||
/// * `allow_absolute` - Allow absolute paths while creating archive
|
||||
/// * `verbose` - Whether to print verbose output during creation
|
||||
///
|
||||
/// # Errors
|
||||
@@ -26,7 +27,12 @@ use uucore::error::UResult;
|
||||
/// - The archive file cannot be created
|
||||
/// - Any input file cannot be read
|
||||
/// - Files cannot be added due to I/O or permission errors
|
||||
pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UResult<()> {
|
||||
pub fn create_archive(
|
||||
archive_path: &Path,
|
||||
files: &[&Path],
|
||||
allow_absolute: bool,
|
||||
verbose: bool,
|
||||
) -> UResult<()> {
|
||||
// Create the output file
|
||||
let file = File::create(archive_path).map_err(|e| TarError::CannotCreateArchive {
|
||||
path: archive_path.to_path_buf(),
|
||||
@@ -35,6 +41,8 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR
|
||||
|
||||
// Create Builder instance
|
||||
let mut builder = Builder::new(file);
|
||||
builder.preserve_absolute(allow_absolute);
|
||||
|
||||
let mut out = BufWriter::new(io::stdout().lock());
|
||||
|
||||
// Add each file or directory to the archive
|
||||
@@ -64,7 +72,7 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR
|
||||
}
|
||||
|
||||
// Normalize path if needed (so far, handles only absolute paths)
|
||||
let normalized_name = if let Some(normalized) = normalize_path(path) {
|
||||
let normalized_name = if let Some(normalized) = normalize_path(path, allow_absolute) {
|
||||
let original_components: Vec<Component> = path.components().collect();
|
||||
let normalized_components: Vec<Component> = normalized.components().collect();
|
||||
if original_components.len() > normalized_components.len() {
|
||||
@@ -129,8 +137,8 @@ fn get_tree(path: &Path) -> Result<Vec<PathBuf>, std::io::Error> {
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
fn normalize_path(path: &Path) -> Option<PathBuf> {
|
||||
if path.is_absolute() {
|
||||
fn normalize_path(path: &Path, allow_absolute: bool) -> Option<PathBuf> {
|
||||
if path.is_absolute() && !allow_absolute {
|
||||
Some(
|
||||
path.components()
|
||||
.filter(|c| !matches!(c, RootDir | ParentDir | Prefix(_)))
|
||||
|
||||
@@ -131,6 +131,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
};
|
||||
|
||||
let verbose = matches.get_flag("verbose");
|
||||
let allow_absolute = matches.get_flag("absolute-names");
|
||||
|
||||
// Handle extract operation
|
||||
if matches.get_flag("extract") {
|
||||
@@ -159,7 +160,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
));
|
||||
}
|
||||
|
||||
return operations::create::create_archive(archive_path, &files, verbose);
|
||||
return operations::create::create_archive(archive_path, &files, allow_absolute, verbose);
|
||||
}
|
||||
|
||||
// Handle list operation
|
||||
@@ -199,6 +200,10 @@ pub fn uu_app() -> Command {
|
||||
// Archive file
|
||||
arg!(-f --file <ARCHIVE> "Use archive file or device ARCHIVE")
|
||||
.value_parser(clap::value_parser!(PathBuf)),
|
||||
arg!(
|
||||
-P --"absolute-names"
|
||||
"Don't strip leading '/'s from file names"
|
||||
),
|
||||
// Compression options
|
||||
// arg!(-z --gzip "Filter through gzip"),
|
||||
// arg!(-j --bzip2 "Filter through bzip2"),
|
||||
@@ -207,7 +212,6 @@ pub fn uu_app() -> Command {
|
||||
arg!(-v --verbose "Verbosely list files processed"),
|
||||
// arg!(-h --dereference "Follow symlinks"),
|
||||
// arg!(-p --"preserve-permissions" "Extract information about file permissions"),
|
||||
// arg!(-P --"absolute-names" "Don't strip leading '/' from file names"),
|
||||
// Help
|
||||
arg!(--help "Print help information").action(ArgAction::Help),
|
||||
// Files to process
|
||||
|
||||
@@ -171,11 +171,55 @@ fn test_create_absolute_path() {
|
||||
file_abs_path.push("file1.txt");
|
||||
|
||||
at.write(&file_abs_path.display().to_string(), "content1");
|
||||
ucmd.args(&["-cf", "archive.tar", &file_abs_path.display().to_string()])
|
||||
.succeeds()
|
||||
.stdout_contains("Removing leading");
|
||||
|
||||
assert!(at.file_exists("archive.tar"));
|
||||
// Trim leading '/'
|
||||
ucmd.args(&[
|
||||
"-cf",
|
||||
"archive-trimed.tar",
|
||||
&file_abs_path.display().to_string(),
|
||||
])
|
||||
.succeeds()
|
||||
.stdout_contains("Removing leading");
|
||||
|
||||
assert!(at.file_exists("archive-trimed.tar"));
|
||||
|
||||
let expected_trimmed_path = file_abs_path
|
||||
.components()
|
||||
.filter(|c| !matches!(c, path::Component::RootDir | path::Component::Prefix(_)))
|
||||
.map(|c| c.as_os_str().display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-tf", "archive-trimed.tar"])
|
||||
.current_dir(at.as_string())
|
||||
.succeeds()
|
||||
.stdout_contains(&expected_trimmed_path);
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
// Preserve leading '/'
|
||||
ucmd.args(&[
|
||||
"-cPf",
|
||||
"archive-preserved.tar",
|
||||
&file_abs_path.display().to_string(),
|
||||
])
|
||||
.succeeds()
|
||||
.no_output();
|
||||
|
||||
assert!(at.file_exists("archive-preserved.tar"));
|
||||
|
||||
let expected_prefix = file_abs_path
|
||||
.components()
|
||||
.find(|c| matches!(c, path::Component::RootDir | path::Component::Prefix(_)))
|
||||
.map(|c| c.as_os_str().display().to_string())
|
||||
.unwrap();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-tf", "archive-preserved.tar"])
|
||||
.current_dir(at.as_string())
|
||||
.succeeds()
|
||||
.stdout_contains(expected_prefix)
|
||||
.stdout_contains(expected_trimmed_path);
|
||||
}
|
||||
|
||||
// Extract operation tests
|
||||
@@ -554,11 +598,15 @@ fn test_extract_created_from_absolute_path() {
|
||||
file_abs_path.push("file1.txt");
|
||||
|
||||
at.write(&file_abs_path.display().to_string(), "content1");
|
||||
ucmd.args(&["-cf", "archive.tar", &file_abs_path.display().to_string()])
|
||||
.succeeds();
|
||||
ucmd.args(&[
|
||||
"-cf",
|
||||
"archive-trimed.tar",
|
||||
&file_abs_path.display().to_string(),
|
||||
])
|
||||
.succeeds();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-xf", "archive.tar"])
|
||||
.args(&["-xf", "archive-trimed.tar"])
|
||||
.current_dir(at.as_string())
|
||||
.succeeds();
|
||||
|
||||
@@ -569,6 +617,23 @@ fn test_extract_created_from_absolute_path() {
|
||||
.collect::<Vec<_>>()
|
||||
.join(std::path::MAIN_SEPARATOR_STR);
|
||||
|
||||
assert!(at.file_exists(&expected_path));
|
||||
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
"-cPf",
|
||||
"archive-preserved.tar",
|
||||
&file_abs_path.display().to_string(),
|
||||
])
|
||||
.current_dir(at.as_string())
|
||||
.succeeds();
|
||||
|
||||
at.remove(&expected_path);
|
||||
new_ucmd!()
|
||||
.args(&["-xf", "archive-preserved.tar"])
|
||||
.current_dir(at.as_string())
|
||||
.succeeds();
|
||||
|
||||
assert!(at.file_exists(expected_path));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user