Skip to content

Commit 72e271a

Browse files
committed
ln: avoid unlinking destination before hard-link source validation
1 parent ef8e45c commit 72e271a

File tree

2 files changed

+33
-10
lines changed

2 files changed

+33
-10
lines changed

src/uu/ln/src/ln.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// spell-checker:ignore (ToDO) srcpath targetpath EEXIST
77

88
use clap::{Arg, ArgAction, Command};
9-
use std::io::{Write, stdout};
9+
use std::io::{ErrorKind, Write, stdout};
1010
use uucore::display::Quotable;
1111
use uucore::error::{FromIo, UError, UResult};
1212
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
@@ -251,7 +251,7 @@ pub fn uu_app() -> Command {
251251

252252
fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
253253
// Handle cases where we create links in a directory first.
254-
if let Some(ref target_path) = settings.target_dir {
254+
if let Some(target_path) = &settings.target_dir {
255255
// 4th form: a directory is specified by -t.
256256
return link_files_in_dir(files, target_path, settings);
257257
}
@@ -404,7 +404,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
404404
return Err(LnError::SameFile(src.to_owned(), dst.to_owned()).into());
405405
}
406406
}
407-
if let Some(ref p) = backup_path {
407+
if let Some(p) = &backup_path {
408408
fs::rename(dst, p)
409409
.map_err_context(|| translate!("ln-cannot-backup", "file" => dst.quote()))?;
410410
}
@@ -432,7 +432,8 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
432432
return Err(LnError::SameFile(src.to_owned(), dst.to_owned()).into());
433433
}
434434
}
435-
if fs::remove_file(dst).is_ok() {}
435+
436+
if settings.symbolic && fs::remove_file(dst).is_ok() {}
436437
// In case of error, don't do anything
437438
}
438439
}
@@ -449,25 +450,34 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
449450
} else {
450451
source.to_path_buf()
451452
};
452-
if let Err(e) = fs::hard_link(&p, dst) {
453+
if let Err(err) = fs::hard_link(&p, dst) {
453454
if p.is_dir() {
454455
return Err(LnError::FailedToCreateHardLinkDir(source.to_path_buf()).into());
455456
}
456-
return Err(e).map_err_context(|| {
457-
translate!("ln-failed-to-create-hard-link", "source" => source.quote(), "dest" => dst.quote())
458-
});
457+
if settings.overwrite == OverwriteMode::Force
458+
&& err.kind() == ErrorKind::AlreadyExists
459+
{
460+
let _ = fs::remove_file(dst);
461+
// In case of error, don't do anything
462+
fs::hard_link(&p, dst).map_err_context(|| {
463+
translate!("ln-failed-to-create-hard-link", "source" => source.quote(), "dest" => dst.quote())
464+
})?;
465+
} else {
466+
return Err(err).map_err_context(|| {
467+
translate!("ln-failed-to-create-hard-link", "source" => source.quote(), "dest" => dst.quote())
468+
});
469+
}
459470
}
460471
Ok(())
461472
}
462473
})();
463474
if res.is_err() {
464-
if let Some(ref p) = backup_path {
475+
if let Some(p) = &backup_path {
465476
fs::rename(p, dst)
466477
.map_err_context(|| translate!("ln-cannot-backup", "file" => dst.quote()))?;
467478
}
468479
res?;
469480
}
470-
471481
if settings.verbose {
472482
let mut out = stdout();
473483
write!(out, "{} -> {}", dst.quote(), source.quote())?;

tests/by-util/test_ln.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,19 @@ fn test_force_same_file_detected_after_canonicalization() {
882882
assert_eq!(at.read("file"), "hello");
883883
}
884884

885+
#[test]
886+
fn test_force_missing_hard_link_source_preserves_destination() {
887+
let (at, mut ucmd) = at_and_ucmd!();
888+
889+
at.write("dst", "keep\n");
890+
891+
ucmd.args(&["-f", "missing_source", "dst"])
892+
.fails_with_code(1)
893+
.stderr_contains("failed to create hard link 'missing_source'");
894+
895+
assert!(at.file_exists("dst"));
896+
assert_eq!(at.read("dst"), "keep\n");
897+
}
885898
#[test]
886899
#[cfg(not(target_os = "android"))]
887900
fn test_force_ln_existing_hard_link_entry() {

0 commit comments

Comments
 (0)