Define fs::hard_link to not follow symlinks.

POSIX leaves it implementation-defined whether `link` follows symlinks.
In practice, for example, on Linux it does not and on FreeBSD it does.
So, switch to `linkat`, so that we can pick a behavior rather than
depending on OS defaults.

Pick the option to not follow symlinks. This is somewhat arbitrary, but
seems the less surprising choice because hard linking is a very
low-level feature which requires the source and destination to be on
the same mounted filesystem, and following a symbolic link could end
up in a different mounted filesystem.
This commit is contained in:
Dan Gohman
2020-10-16 09:09:20 -07:00
parent a78a62fc99
commit 91a9f83dd1
3 changed files with 60 additions and 3 deletions

View File

@@ -1337,3 +1337,54 @@ fn metadata_access_times() {
}
}
}
/// Test creating hard links to symlinks.
#[test]
fn symlink_hard_link() {
let tmpdir = tmpdir();
// Create "file", a file.
check!(fs::File::create(tmpdir.join("file")));
// Create "symlink", a symlink to "file".
check!(symlink_file("file", tmpdir.join("symlink")));
// Create "hard_link", a hard link to "symlink".
check!(fs::hard_link(tmpdir.join("symlink"), tmpdir.join("hard_link")));
// "hard_link" should appear as a symlink.
assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink());
// We sould be able to open "file" via any of the above names.
let _ = check!(fs::File::open(tmpdir.join("file")));
assert!(fs::File::open(tmpdir.join("file.renamed")).is_err());
let _ = check!(fs::File::open(tmpdir.join("symlink")));
let _ = check!(fs::File::open(tmpdir.join("hard_link")));
// Rename "file" to "file.renamed".
check!(fs::rename(tmpdir.join("file"), tmpdir.join("file.renamed")));
// Now, the symlink and the hard link should be dangling.
assert!(fs::File::open(tmpdir.join("file")).is_err());
let _ = check!(fs::File::open(tmpdir.join("file.renamed")));
assert!(fs::File::open(tmpdir.join("symlink")).is_err());
assert!(fs::File::open(tmpdir.join("hard_link")).is_err());
// The symlink and the hard link should both still point to "file".
assert!(fs::read_link(tmpdir.join("file")).is_err());
assert!(fs::read_link(tmpdir.join("file.renamed")).is_err());
assert_eq!(check!(fs::read_link(tmpdir.join("symlink"))), Path::new("file"));
assert_eq!(check!(fs::read_link(tmpdir.join("hard_link"))), Path::new("file"));
// Remove "file.renamed".
check!(fs::remove_file(tmpdir.join("file.renamed")));
// Now, we can't open the file by any name.
assert!(fs::File::open(tmpdir.join("file")).is_err());
assert!(fs::File::open(tmpdir.join("file.renamed")).is_err());
assert!(fs::File::open(tmpdir.join("symlink")).is_err());
assert!(fs::File::open(tmpdir.join("hard_link")).is_err());
// "hard_link" should still appear as a symlink.
assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink());
}