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:
Valentin
2026-06-02 02:53:03 +07:00
committed by GitHub
parent 949437fe85
commit 4986a8460c
3 changed files with 90 additions and 13 deletions
+12 -4
View File
@@ -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(_)))
+6 -2
View File
@@ -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
+72 -7
View File
@@ -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));
}