diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index ea77a3506..d81d63a73 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -15,6 +15,7 @@ use libc::{ use std::borrow::Cow; use std::env; use std::fs; +use std::io::Error as IOError; use std::io::Result as IOResult; use std::io::{Error, ErrorKind}; #[cfg(any(unix, target_os = "redox"))] @@ -109,26 +110,42 @@ pub fn normalize_path(path: &Path) -> PathBuf { ret } -fn resolve>(original: P) -> IOResult { +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 first_resolution = None; loop { if followed == MAX_LINKS_FOLLOWED { - return Err(Error::new( - ErrorKind::InvalidInput, - "maximum links followed", + return Err(( + // 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"), )); } - if !fs::symlink_metadata(&result)?.file_type().is_symlink() { - break; + match fs::symlink_metadata(&result) { + Ok(meta) => { + if !meta.file_type().is_symlink() { + break; + } + } + Err(e) => return Err((result, e)), } followed += 1; - let path = fs::read_link(&result)?; - result.pop(); - result.push(path); + match fs::read_link(&result) { + Ok(path) => { + result.pop(); + result.push(path); + } + Err(e) => return Err((result, e)), + } + + if first_resolution.is_none() { + first_resolution = Some(result.clone()); + } } Ok(result) } @@ -214,11 +231,10 @@ pub fn canonicalize>( } match resolve(&result) { - Err(_) if miss_mode == MissingHandling::Missing => continue, - Err(e) => return Err(e), + Err((path, _)) if miss_mode == MissingHandling::Missing => result = path, + Err((_, e)) => return Err(e), Ok(path) => { - result.pop(); - result.push(path); + result = path; } } } @@ -230,14 +246,12 @@ pub fn canonicalize>( } match resolve(&result) { - Err(e) if miss_mode == MissingHandling::Existing => { + Err((_, e)) if miss_mode == MissingHandling::Existing => { return Err(e); } - Ok(path) => { - result.pop(); - result.push(path); + Ok(path) | Err((path, _)) => { + result = path; } - Err(_) => (), } if res_mode == ResolveMode::Physical { result = normalize_path(&result); diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 8cb1551f0..7b6da5d36 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -139,3 +139,23 @@ fn test_realpath_logical_mode() { .succeeds() .stdout_contains("dir1\n"); } + +#[test] +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")); +} + +#[test] +fn test_realpath_loop() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("2", "1"); + at.symlink_file("3", "2"); + at.symlink_file("1", "3"); + ucmd.arg("1") + .succeeds() + .stdout_only(at.plus_as_string("2\n")); +}