diff --git a/src/uu/tar/src/operations/create.rs b/src/uu/tar/src/operations/create.rs index 4b296f8..dd05b6b 100644 --- a/src/uu/tar/src/operations/create.rs +++ b/src/uu/tar/src/operations/create.rs @@ -6,6 +6,7 @@ use crate::errors::TarError; use std::collections::VecDeque; use std::fs::{self, File}; +use std::path::Component::{self, ParentDir, Prefix, RootDir}; use std::path::{self, Path, PathBuf}; use tar::Builder; use uucore::error::UResult; @@ -64,9 +65,26 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR println!("{to_print}"); } + // Normalize path if needed (so far, handles only absolute paths) + let normalized_name = if let Some(normalized) = normalize_path(path) { + let original_components: Vec = path.components().collect(); + let normalized_components: Vec = normalized.components().collect(); + if original_components.len() > normalized_components.len() { + let removed: PathBuf = original_components + [..original_components.len() - normalized_components.len()] + .iter() + .collect(); + println!("Removing leading `{}' from member names", removed.display()); + } + + normalized + } else { + path.to_path_buf() + }; + // If it's a directory, recursively add all contents if path.is_dir() { - builder.append_dir_all(path, path).map_err(|e| { + builder.append_dir_all(normalized_name, path).map_err(|e| { TarError::TarOperationError(format!( "Failed to add directory '{}': {}", path.display(), @@ -75,13 +93,15 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR })?; } else { // For files, add them directly - builder.append_path(path).map_err(|e| { - TarError::TarOperationError(format!( - "Failed to add file '{}': {}", - path.display(), - e - )) - })?; + builder + .append_path_with_name(path, normalized_name) + .map_err(|e| { + TarError::TarOperationError(format!( + "Failed to add file '{}': {}", + path.display(), + e + )) + })?; } } @@ -110,3 +130,15 @@ fn get_tree(path: &Path) -> Result, std::io::Error> { Ok(paths) } + +fn normalize_path(path: &Path) -> Option { + if path.is_absolute() { + Some( + path.components() + .filter(|c| !matches!(c, RootDir | ParentDir | Prefix(_))) + .collect::(), + ) + } else { + None + } +} diff --git a/tests/by-util/test_tar.rs b/tests/by-util/test_tar.rs index b876c54..2f16646 100644 --- a/tests/by-util/test_tar.rs +++ b/tests/by-util/test_tar.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::path; +use std::path::{self, PathBuf}; use uutests::{at_and_ucmd, new_ucmd}; @@ -33,7 +33,7 @@ fn test_version() { } #[test] -fn test_verbose() { +fn test_create_dir_verbose() { let (at, mut ucmd) = at_and_ucmd!(); let separator = path::MAIN_SEPARATOR; @@ -81,6 +81,21 @@ fn test_create_multiple_files() { assert!(at.file_exists("archive.tar")); } +#[test] +fn test_create_absolute_path() { + let (at, mut ucmd) = at_and_ucmd!(); + + let mut file_abs_path = PathBuf::from(at.root_dir_resolved()); + 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")); +} + // Extract operation tests #[test] @@ -139,3 +154,29 @@ fn test_extract_nonexistent_archive() { .fails() .code_is(2); } + +#[test] +fn test_extract_created_from_absolute_path() { + let (at, mut ucmd) = at_and_ucmd!(); + + let mut file_abs_path = PathBuf::from(at.root_dir_resolved()); + 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(); + + new_ucmd!() + .args(&["-xf", "archive.tar"]) + .current_dir(at.as_string()) + .succeeds(); + + let expected_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::>() + .join(std::path::MAIN_SEPARATOR_STR); + + assert!(at.file_exists(expected_path)); +}