mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
realpath: Error when resolved symlink is absolute and ENOENT (#3037)
* realpath: Match behavior where resolving symlinks with absolute path is an error if ENOENT This PR changes `realpath` to match the behavior in GNU where, ```shell hbina@akarin ~/Documents> mkdir dir1 hbina@akarin ~/Documents> mkdir dir2 hbina@akarin ~/Documents> touch dir2/bar hbina@akarin ~/Documents> ln -s ../dir2/bar dir1/foo1 hbina@akarin ~/Documents> ln -s /dir2/bar dir1/foo2 hbina@akarin ~/Documents> ln -s ../dir2/baz dir1/foo3 hbina@akarin ~/Documents> realpath ./dir1/foo1 ./dir1/foo2 ./dir1/foo3 /home/hbina/Documents/dir2/bar realpath: ./dir1/foo2: No such file or directory /home/hbina/Documents/dir2/baz ``` Currently, our `realpath` will happily print the second one out, ```shell hbina@akarin ~/Documents> ~/git/uutils/target/debug/coreutils realpath ./dir1/foo1 ./dir1/foo2 ./dir1/foo3 /home/hbina/Documents/dir2/bar /dir2/bar /home/hbina/Documents/dir2/baz ``` Closes https://github.com/uutils/coreutils/issues/3036 Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>
This commit is contained in:
parent
eace4bc907
commit
30a174e6e4
3 changed files with 65 additions and 11 deletions
|
@ -223,15 +223,17 @@ pub fn normalize_path(path: &Path) -> PathBuf {
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve<P: AsRef<Path>>(original: P) -> Result<PathBuf, (PathBuf, IOError)> {
|
fn resolve<P: AsRef<Path>>(original: P) -> Result<PathBuf, (bool, PathBuf, IOError)> {
|
||||||
const MAX_LINKS_FOLLOWED: u32 = 255;
|
const MAX_LINKS_FOLLOWED: u32 = 255;
|
||||||
let mut followed = 0;
|
let mut followed = 0;
|
||||||
let mut result = original.as_ref().to_path_buf();
|
let mut result = original.as_ref().to_path_buf();
|
||||||
|
let mut symlink_is_absolute = false;
|
||||||
let mut first_resolution = None;
|
let mut first_resolution = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if followed == MAX_LINKS_FOLLOWED {
|
if followed == MAX_LINKS_FOLLOWED {
|
||||||
return Err((
|
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)
|
// When we hit MAX_LINKS_FOLLOWED we should return the first resolution (that's what GNU does - for whatever reason)
|
||||||
first_resolution.unwrap(),
|
first_resolution.unwrap(),
|
||||||
Error::new(ErrorKind::InvalidInput, "maximum links followed"),
|
Error::new(ErrorKind::InvalidInput, "maximum links followed"),
|
||||||
|
@ -244,16 +246,17 @@ fn resolve<P: AsRef<Path>>(original: P) -> Result<PathBuf, (PathBuf, IOError)> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => return Err((result, e)),
|
Err(e) => return Err((symlink_is_absolute, result, e)),
|
||||||
}
|
}
|
||||||
|
|
||||||
followed += 1;
|
followed += 1;
|
||||||
match fs::read_link(&result) {
|
match fs::read_link(&result) {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
result.pop();
|
result.pop();
|
||||||
|
symlink_is_absolute = path.is_absolute();
|
||||||
result.push(path);
|
result.push(path);
|
||||||
}
|
}
|
||||||
Err(e) => return Err((result, e)),
|
Err(e) => return Err((symlink_is_absolute, result, e)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if first_resolution.is_none() {
|
if first_resolution.is_none() {
|
||||||
|
@ -343,8 +346,13 @@ pub fn canonicalize<P: AsRef<Path>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
match resolve(&result) {
|
match resolve(&result) {
|
||||||
Err((path, _)) if miss_mode == MissingHandling::Missing => result = path,
|
Err((_, path, e)) => {
|
||||||
Err((_, e)) => return Err(e),
|
if miss_mode == MissingHandling::Missing {
|
||||||
|
result = path;
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
result = path;
|
result = path;
|
||||||
}
|
}
|
||||||
|
@ -358,10 +366,20 @@ pub fn canonicalize<P: AsRef<Path>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
match resolve(&result) {
|
match resolve(&result) {
|
||||||
Err((_, e)) if miss_mode == MissingHandling::Existing => {
|
Err((is_absolute, path, err)) => {
|
||||||
return Err(e);
|
// 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;
|
result = path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,8 +149,8 @@ fn test_realpath_dangling() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
at.symlink_file("nonexistent-file", "link");
|
at.symlink_file("nonexistent-file", "link");
|
||||||
ucmd.arg("link")
|
ucmd.arg("link")
|
||||||
.succeeds()
|
.fails()
|
||||||
.stdout_only(at.plus_as_string("nonexistent-file\n"));
|
.stderr_contains("realpath: link: No such file or directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -202,3 +202,34 @@ fn test_realpath_missing() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only(expect);
|
.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");
|
||||||
|
}
|
||||||
|
|
|
@ -701,6 +701,11 @@ impl AtPath {
|
||||||
symlink_file(&self.plus(original), &self.plus(link)).unwrap();
|
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) {
|
pub fn symlink_dir(&self, original: &str, link: &str) {
|
||||||
log_info(
|
log_info(
|
||||||
"symlink",
|
"symlink",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue