feature(ln): Implement -n

This commit is contained in:
Sylvestre Ledru 2021-01-03 00:56:20 +01:00
parent 7f1d47b77a
commit 4a23a1a218
2 changed files with 154 additions and 22 deletions

View file

@ -27,11 +27,13 @@ use uucore::fs::{canonicalize, CanonicalizeMode};
pub struct Settings {
overwrite: OverwriteMode,
backup: BackupMode,
force: bool,
suffix: String,
symbolic: bool,
relative: bool,
target_dir: Option<String>,
no_target_dir: bool,
no_dereference: bool,
verbose: bool,
}
@ -81,6 +83,7 @@ static OPT_B: &str = "b";
static OPT_BACKUP: &str = "backup";
static OPT_FORCE: &str = "force";
static OPT_INTERACTIVE: &str = "interactive";
static OPT_NO_DEREFERENCE: &str = "no-dereference";
static OPT_SYMBOLIC: &str = "symbolic";
static OPT_SUFFIX: &str = "suffix";
static OPT_TARGET_DIRECTORY: &str = "target-directory";
@ -136,11 +139,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(OPT_INTERACTIVE)
.help("prompt whether to remove existing destination files"),
)
.arg(
Arg::with_name(OPT_NO_DEREFERENCE)
.short("n")
.long(OPT_NO_DEREFERENCE)
.help(
"treat LINK_executable!() as a normal file if it is a \
symbolic link to a directory",
),
)
// TODO: opts.arg(
// Arg::with_name(("L", "logical", "dereference TARGETs that are symbolic links");
// TODO: opts.arg(
// Arg::with_name(("n", "no-dereference", "treat LINK_executable!() as a normal file if it is a \
// symbolic link to a directory");
//
// TODO: opts.arg(
// Arg::with_name(("P", "physical", "make hard links directly to symbolic links");
.arg(
@ -192,7 +202,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
)
.get_matches_from(args);
/* the list of files */
/* the list of files */
let paths: Vec<PathBuf> = matches
.values_of(ARG_FILES)
@ -234,11 +244,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let settings = Settings {
overwrite: overwrite_mode,
backup: backup_mode,
force: matches.is_present(OPT_FORCE),
suffix: backup_suffix.to_string(),
symbolic: matches.is_present(OPT_SYMBOLIC),
relative: matches.is_present(OPT_RELATIVE),
target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from),
no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY),
no_dereference: matches.is_present(OPT_NO_DEREFERENCE),
verbose: matches.is_present(OPT_VERBOSE),
};
@ -299,24 +311,47 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting
let mut all_successful = true;
for srcpath in files.iter() {
let targetpath = match srcpath.as_os_str().to_str() {
Some(name) => {
match Path::new(name).file_name() {
Some(basename) => target_dir.join(basename),
// This can be None only for "." or "..". Trying
// to create a link with such name will fail with
// EEXIST, which agrees with the behavior of GNU
// coreutils.
None => target_dir.join(name),
let targetpath = if settings.no_dereference && settings.force {
// In that case, we don't want to do link resolution
// We need to clean the target
if is_symlink(target_dir) {
if target_dir.is_file() {
match fs::remove_file(target_dir) {
Err(e) => show_error!("Could not update {}: {}", target_dir.display(), e),
_ => (),
};
}
if target_dir.is_dir() {
// Not sure why but on Windows, the symlink can be
// considered as a dir
// See test_ln::test_symlink_no_deref_dir
match fs::remove_dir(target_dir) {
Err(e) => show_error!("Could not update {}: {}", target_dir.display(), e),
_ => (),
};
}
}
None => {
show_error!(
"cannot stat '{}': No such file or directory",
srcpath.display()
);
all_successful = false;
continue;
target_dir.clone()
} else {
match srcpath.as_os_str().to_str() {
Some(name) => {
match Path::new(name).file_name() {
Some(basename) => target_dir.join(basename),
// This can be None only for "." or "..". Trying
// to create a link with such name will fail with
// EEXIST, which agrees with the behavior of GNU
// coreutils.
None => target_dir.join(name),
}
}
None => {
show_error!(
"cannot stat '{}': No such file or directory",
srcpath.display()
);
all_successful = false;
continue;
}
}
};
@ -389,6 +424,12 @@ fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> {
}
}
if settings.no_dereference && settings.force {
if dst.exists() {
fs::remove_file(dst)?;
}
}
if settings.symbolic {
symlink(&source, dst)?;
} else {

View file

@ -321,8 +321,7 @@ fn test_symlink_errors() {
// $ ln -T -t a b
// ln: cannot combine --target-directory (-t) and --no-target-directory (-T)
ucmd.args(&["-T", "-t", dir, file_a, file_b])
.fails();
ucmd.args(&["-T", "-t", dir, file_a, file_b]).fails();
}
#[test]
@ -484,3 +483,95 @@ fn test_symlink_relative_dir() {
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), dir);
}
#[test]
fn test_symlink_no_deref_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let dir1 = "foo";
let dir2 = "bar";
let link = "baz";
at.mkdir(dir1);
at.mkdir(dir2);
scene
.ucmd()
.args(&["-s", dir2, link])
.succeeds()
.no_stderr();
assert!(at.dir_exists(dir1));
assert!(at.dir_exists(dir2));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), dir2);
// try the normal behavior
scene
.ucmd()
.args(&["-sf", dir1, link])
.succeeds()
.no_stderr();
assert!(at.dir_exists(dir1));
assert!(at.dir_exists(dir2));
assert!(at.is_symlink("baz/foo"));
assert_eq!(at.resolve_link("baz/foo"), dir1);
// Doesn't work without the force
scene.ucmd().args(&["-sn", dir1, link]).fails();
// Try with the no-deref
let result = scene.ucmd().args(&["-sfn", dir1, link]).run();
println!("stdout {}", result.stdout);
println!("stderr {}", result.stderr);
assert!(result.success);
assert!(at.dir_exists(dir1));
assert!(at.dir_exists(dir2));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), dir1);
}
#[test]
fn test_symlink_no_deref_file() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file1 = "foo";
let file2 = "bar";
let link = "baz";
at.touch(file1);
at.touch(file2);
scene
.ucmd()
.args(&["-s", file2, link])
.succeeds()
.no_stderr();
assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), file2);
// try the normal behavior
scene
.ucmd()
.args(&["-sf", file1, link])
.succeeds()
.no_stderr();
assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.is_symlink("baz"));
assert_eq!(at.resolve_link("baz"), file1);
// Doesn't work without the force
scene.ucmd().args(&["-sn", file1, link]).fails();
// Try with the no-deref
let result = scene.ucmd().args(&["-sfn", file1, link]).run();
println!("stdout {}", result.stdout);
println!("stderr {}", result.stderr);
assert!(result.success);
assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), file1);
}