From de6f983ccc79db9a8738bf959c203f19ea598ba7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 24 May 2022 22:58:16 +0200 Subject: [PATCH] ln: Implement -L -P to make tests/ln/hard-to-sym.sh work --- src/uu/ln/src/ln.rs | 53 +++++++++++++++++++++++++++++++--------- tests/by-util/test_ln.rs | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index fdbb32311a3..71dab238afc 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -36,6 +36,7 @@ pub struct Settings { suffix: String, symbolic: bool, relative: bool, + logical: bool, target_dir: Option, no_target_dir: bool, no_dereference: bool, @@ -121,9 +122,12 @@ const USAGE: &str = "\ mod options { pub const FORCE: &str = "force"; + //pub const DIRECTORY: &str = "directory"; pub const INTERACTIVE: &str = "interactive"; pub const NO_DEREFERENCE: &str = "no-dereference"; pub const SYMBOLIC: &str = "symbolic"; + pub const LOGICAL: &str = "logical"; + pub const PHYSICAL: &str = "physical"; pub const TARGET_DIRECTORY: &str = "target-directory"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const RELATIVE: &str = "relative"; @@ -152,6 +156,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(PathBuf::from) .collect(); + let symbolic = matches.is_present(options::SYMBOLIC); + let overwrite_mode = if matches.is_present(options::FORCE) { OverwriteMode::Force } else if matches.is_present(options::INTERACTIVE) { @@ -162,12 +168,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let backup_mode = backup_control::determine_backup_mode(&matches)?; let backup_suffix = backup_control::determine_backup_suffix(&matches); + let logical = if matches.is_present(options::LOGICAL) { + true + } else { + matches.is_present(options::LOGICAL) && matches.is_present(options::PHYSICAL) + }; let settings = Settings { overwrite: overwrite_mode, backup: backup_mode, suffix: backup_suffix, - symbolic: matches.is_present(options::SYMBOLIC), + symbolic, + logical, relative: matches.is_present(options::RELATIVE), target_dir: matches .value_of(options::TARGET_DIRECTORY) @@ -188,9 +200,12 @@ pub fn uu_app<'a>() -> Command<'a> { .infer_long_args(true) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) - // TODO: opts.arg( - // Arg::new(("d", "directory", "allow users with appropriate privileges to attempt \ - // to make hard links to directories"); + /*.arg( + Arg::new(options::DIRECTORY) + .short('d') + .long(options::DIRECTORY) + .help("allow users with appropriate privileges to attempt to make hard links to directories") + )*/ .arg( Arg::new(options::FORCE) .short('f') @@ -212,15 +227,23 @@ pub fn uu_app<'a>() -> Command<'a> { symbolic link to a directory", ), ) - // TODO: opts.arg( - // Arg::new(("L", "logical", "dereference TARGETs that are symbolic links"); - // - // TODO: opts.arg( - // Arg::new(("P", "physical", "make hard links directly to symbolic links"); + .arg( + Arg::new(options::LOGICAL) + .short('L') + .long(options::LOGICAL) + .help("dereference TARGETs that are symbolic links"), + ) + .arg( + Arg::new(options::PHYSICAL) + .short('P') + .long(options::PHYSICAL) + .help("make hard links directly to symbolic links") + .overrides_with(options::LOGICAL), + ) .arg( Arg::new(options::SYMBOLIC) .short('s') - .long("symbolic") + .long(options::SYMBOLIC) .help("make symbolic links instead of hard links") // override added for https://github.com/uutils/coreutils/issues/2359 .overrides_with(options::SYMBOLIC), @@ -446,7 +469,15 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> { if settings.symbolic { symlink(&source, dst)?; } else { - fs::hard_link(&source, dst)?; + let p = if settings.logical && is_symlink(&source) { + // if we want to have an hard link, + // source is a symlink and -L is passed + // we want to resolve the symlink to create the hardlink + std::fs::canonicalize(&source)? + } else { + source.to_path_buf() + }; + fs::hard_link(&p, dst)?; } if settings.verbose { diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 980225260d1..042aa1e5d32 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -650,3 +650,55 @@ fn test_backup_force() { // we should have the same content as b as we had time to do a backup assert_eq!(at.read("b~"), "b2\n"); } + +#[test] +fn test_hard_logical() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "file1"; + let link = "symlink1"; + let target = "hard-to-a"; + let target2 = "hard-to-a2"; + at.touch(file_a); + at.symlink_file(file_a, link); + + ucmd.args(&["-P", "-L", link, target]); + assert!(!at.is_symlink(target)); + + ucmd.args(&["-P", "-L", "-s", link, target2]); + assert!(!at.is_symlink(target2)); +} + +#[test] +fn test_hard_logical_non_exit_fail() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "/no-such-dir"; + let link = "hard-to-dangle"; + + scene.ucmd().args(&["-s", file_a]); + assert!(!at.is_symlink("no-such-dir")); + + scene + .ucmd() + .args(&["-L", "no-such-dir", link]) + .fails() + .stderr_contains("failed to link 'no-such-dir'"); +} + +#[test] +fn test_hard_logical_dir_fail() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir = "d"; + at.mkdir(dir); + let target = "link-to-dir"; + + scene.ucmd().args(&["-s", dir, target]); + + scene + .ucmd() + .args(&["-L", target, "hard-to-dir-link"]) + .fails() + .stderr_contains("failed to link 'link-to-dir'"); +}