diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 0452af5b3..f5295f17f 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -223,15 +223,17 @@ pub fn normalize_path(path: &Path) -> PathBuf { ret } -fn resolve>(original: P) -> Result { +fn resolve>(original: P) -> Result { const MAX_LINKS_FOLLOWED: u32 = 255; let mut followed = 0; let mut result = original.as_ref().to_path_buf(); - + let mut symlink_is_absolute = false; let mut first_resolution = None; + loop { if followed == MAX_LINKS_FOLLOWED { return Err(( + symlink_is_absolute, // When we hit MAX_LINKS_FOLLOWED we should return the first resolution (that's what GNU does - for whatever reason) first_resolution.unwrap(), Error::new(ErrorKind::InvalidInput, "maximum links followed"), @@ -244,16 +246,17 @@ fn resolve>(original: P) -> Result { break; } } - Err(e) => return Err((result, e)), + Err(e) => return Err((symlink_is_absolute, result, e)), } followed += 1; match fs::read_link(&result) { Ok(path) => { result.pop(); + symlink_is_absolute = path.is_absolute(); result.push(path); } - Err(e) => return Err((result, e)), + Err(e) => return Err((symlink_is_absolute, result, e)), } if first_resolution.is_none() { @@ -343,8 +346,13 @@ pub fn canonicalize>( } match resolve(&result) { - Err((path, _)) if miss_mode == MissingHandling::Missing => result = path, - Err((_, e)) => return Err(e), + Err((_, path, e)) => { + if miss_mode == MissingHandling::Missing { + result = path; + } else { + return Err(e); + } + } Ok(path) => { result = path; } @@ -358,10 +366,20 @@ pub fn canonicalize>( } match resolve(&result) { - Err((_, e)) if miss_mode == MissingHandling::Existing => { - return Err(e); + Err((is_absolute, path, err)) => { + // If the resolved symlink is an absolute path and non-existent, + // `realpath` throws no such file error. + if miss_mode == MissingHandling::Existing + || (err.kind() == ErrorKind::NotFound + && is_absolute + && miss_mode == MissingHandling::Normal) + { + return Err(err); + } else { + result = path; + } } - Ok(path) | Err((path, _)) => { + Ok(path) => { result = path; } } diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 72bf5b6ea..e0875cc77 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -149,8 +149,8 @@ fn test_realpath_dangling() { let (at, mut ucmd) = at_and_ucmd!(); at.symlink_file("nonexistent-file", "link"); ucmd.arg("link") - .succeeds() - .stdout_only(at.plus_as_string("nonexistent-file\n")); + .fails() + .stderr_contains("realpath: link: No such file or directory"); } #[test] @@ -202,3 +202,34 @@ fn test_realpath_missing() { .succeeds() .stdout_only(expect); } + +#[test] +fn test_realpath_when_symlink_is_absolute_and_enoent() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir2"); + at.touch("dir2/bar"); + + at.mkdir("dir1"); + at.symlink_file("dir2/bar", "dir1/foo1"); + at.symlink_file("/dir2/bar", "dir1/foo2"); + at.relative_symlink_file("dir2/baz", at.plus("dir1/foo3").to_str().unwrap()); + + #[cfg(unix)] + ucmd.arg("dir1/foo1") + .arg("dir1/foo2") + .arg("dir1/foo3") + .run() + .stdout_contains("/dir2/bar\n") + .stdout_contains("/dir2/baz\n") + .stderr_is("realpath: dir1/foo2: No such file or directory"); + + #[cfg(windows)] + ucmd.arg("dir1/foo1") + .arg("dir1/foo2") + .arg("dir1/foo3") + .run() + .stdout_contains("\\dir2\\bar\n") + .stdout_contains("\\dir2\\baz\n") + .stderr_is("realpath: dir1/foo2: No such file or directory"); +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 422d36328..5e1424ca4 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -701,6 +701,11 @@ impl AtPath { symlink_file(&self.plus(original), &self.plus(link)).unwrap(); } + pub fn relative_symlink_file(&self, original: &str, link: &str) { + log_info("symlink", &format!("{},{}", original, link)); + symlink_file(original, link).unwrap(); + } + pub fn symlink_dir(&self, original: &str, link: &str) { log_info( "symlink",