From 0ea35f3fbcbb3596b379e2f6dd7eac2f676da298 Mon Sep 17 00:00:00 2001 From: Sivachandran Date: Tue, 20 Apr 2021 01:33:13 +0530 Subject: [PATCH] Implement install create leading components(-D) option (#2092) * Implement install's create leading components(-D) option * Format changes * Add install test to check fail on long dir name --- src/uu/install/src/install.rs | 39 ++++++++++++++++++++++++++------- tests/by-util/test_install.rs | 41 ++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index a75ce45be..4ce665b80 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -41,6 +41,7 @@ pub struct Behavior { compare: bool, strip: bool, strip_program: String, + create_leading: bool, } #[derive(Clone, Eq, PartialEq)] @@ -70,7 +71,7 @@ static OPT_BACKUP: &str = "backup"; static OPT_BACKUP_2: &str = "backup2"; static OPT_DIRECTORY: &str = "directory"; static OPT_IGNORED: &str = "ignored"; -static OPT_CREATED: &str = "created"; +static OPT_CREATE_LEADING: &str = "create-leading"; static OPT_GROUP: &str = "group"; static OPT_MODE: &str = "mode"; static OPT_OWNER: &str = "owner"; @@ -133,9 +134,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( // TODO implement flag - Arg::with_name(OPT_CREATED) + Arg::with_name(OPT_CREATE_LEADING) .short("D") - .help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST") + .help("create all leading components of DEST except the last, then copy SOURCE to DEST") ) .arg( Arg::with_name(OPT_GROUP) @@ -266,8 +267,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("--backup") } else if matches.is_present(OPT_BACKUP_2) { Err("-b") - } else if matches.is_present(OPT_CREATED) { - Err("-D") } else if matches.is_present(OPT_SUFFIX) { Err("--suffix, -S") } else if matches.is_present(OPT_TARGET_DIRECTORY) { @@ -343,6 +342,7 @@ fn behavior(matches: &ArgMatches) -> Result { .value_of(OPT_STRIP_PROGRAM) .unwrap_or(DEFAULT_STRIP_PROGRAM), ), + create_leading: matches.is_present(OPT_CREATE_LEADING), }) } @@ -410,12 +410,35 @@ fn standard(paths: Vec, b: Behavior) -> i32 { .iter() .map(PathBuf::from) .collect::>(); + let target = Path::new(paths.last().unwrap()); - if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 { - copy_file_to_file(&sources[0], &target.to_path_buf(), &b) - } else { + if sources.len() > 1 || (target.exists() && target.is_dir()) { copy_files_into_dir(sources, &target.to_path_buf(), &b) + } else { + if let Some(parent) = target.parent() { + if !parent.exists() && b.create_leading { + if let Err(e) = fs::create_dir_all(parent) { + show_error!("failed to create {}: {}", parent.display(), e); + return 1; + } + + if mode::chmod(&parent, b.mode()).is_err() { + show_error!("failed to chmod {}", parent.display()); + return 1; + } + } + } + + if target.is_file() || is_new_file_path(target) { + copy_file_to_file(&sources[0], &target.to_path_buf(), &b) + } else { + show_error!( + "invalid target {}: No such file or directory", + target.display() + ); + 1 + } } } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index dfaaabce6..fa23de745 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -359,7 +359,7 @@ fn test_install_target_new_file_failing_nonexistent_parent() { ucmd.arg(file1) .arg(format!("{}/{}", dir, file2)) .fails() - .stderr_contains(&"not a directory"); + .stderr_contains(&"No such file or directory"); } #[test] @@ -649,3 +649,42 @@ fn test_install_and_strip_with_non_existent_program() { .stderr; assert!(stderr.contains("No such file or directory")); } + +#[test] +fn test_install_creating_leading_dirs() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let source = "create_leading_test_file"; + let target = "dir1/dir2/dir3/test_file"; + + at.touch(source); + + scene + .ucmd() + .arg("-D") + .arg(source) + .arg(at.plus(target)) + .succeeds() + .no_stderr(); +} + +#[test] +#[cfg(not(windows))] +fn test_install_creating_leading_dir_fails_on_long_name() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let source = "create_leading_test_file"; + let target = format!("{}/test_file", "d".repeat(libc::PATH_MAX as usize + 1)); + + at.touch(source); + + scene + .ucmd() + .arg("-D") + .arg(source) + .arg(at.plus(target.as_str())) + .fails() + .stderr_contains("failed to create"); +}