Skip to content

Commit 04a6737

Browse files
committed
fix(readlink): use physical resolution for canonicalize flags to match GNU behavior
Changed ResolveMode from Logical to Physical for -f, -e, and -m flags in readlink to ensure symlinks are followed before resolving '..' (parent directory), matching GNU readlink's physical resolution order for compatibility. Added a test case to verify the symlink resolution occurs before parent directory evaluation.
1 parent 415d01c commit 04a6737

2 files changed

Lines changed: 19 additions & 1 deletion

File tree

src/uu/readlink/src/readlink.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
3737
let silent = matches.get_flag(OPT_SILENT) || matches.get_flag(OPT_QUIET);
3838
let verbose = matches.get_flag(OPT_VERBOSE);
3939

40+
// GNU readlink -f/-e/-m follows symlinks first and then applies `..` (physical resolution).
41+
// ResolveMode::Logical collapses `..` before following links, which yields the opposite order,
42+
// so we choose Physical here for GNU compatibility.
4043
let res_mode = if matches.get_flag(OPT_CANONICALIZE)
4144
|| matches.get_flag(OPT_CANONICALIZE_EXISTING)
4245
|| matches.get_flag(OPT_CANONICALIZE_MISSING)
4346
{
44-
ResolveMode::Logical
47+
ResolveMode::Physical
4548
} else {
4649
ResolveMode::None
4750
};

tests/by-util/test_readlink.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@ fn test_canonicalize_missing() {
6868
assert_eq!(actual, expect);
6969
}
7070

71+
#[test]
72+
#[cfg(unix)]
73+
fn test_canonicalize_symlink_before_parentdir() {
74+
// GNU readlink follows the symlink first and only then evaluates `..`.
75+
// Logical resolution would collapse `link/..` up front and return the current directory instead.
76+
let (at, mut ucmd) = at_and_ucmd!();
77+
at.mkdir("real");
78+
at.mkdir("real/sub");
79+
at.relative_symlink_dir("real/sub", "link");
80+
81+
let actual = ucmd.args(&["-f", "link/.."]).succeeds().stdout_move_str();
82+
let expect = format!("{}/real\n", at.root_dir_resolved());
83+
assert_eq!(actual, expect);
84+
}
85+
7186
#[test]
7287
fn test_long_redirection_to_current_dir() {
7388
let (at, mut ucmd) = at_and_ucmd!();

0 commit comments

Comments
 (0)