Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define fs::hard_link to not follow symlinks. #78026

Merged
merged 5 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1701,10 +1701,14 @@ pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
/// The `dst` path will be a link pointing to the `src` path. Note that systems
/// often require these two paths to both be located on the same filesystem.
///
/// If `src` names a symbolic link, it is platform-specific whether the symbolic
/// link is followed. On platforms where it's possible to not follow it, it is
/// not followed, and the created hard link points to the symbolic link itself.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `link` function on Unix
/// and the `CreateHardLink` function on Windows.
/// This function currently corresponds to the `linkat` function with no flags
/// on Unix and the `CreateHardLink` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: io#platform-specific-behavior
Expand Down
51 changes: 51 additions & 0 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
15 changes: 14 additions & 1 deletion library/std/src/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,20 @@ pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
let src = cstr(src)?;
let dst = cstr(dst)?;
cvt(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })?;
cfg_if::cfg_if! {
if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android"))] {
// VxWorks, Redox, and old versions of Android lack `linkat`, so use
// `link` instead. POSIX leaves it implementation-defined whether
// `link` follows symlinks, so rely on the `symlink_hard_link` test
// in library/std/src/fs/tests.rs to check the behavior.
cvt(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })?;
} else {
// Use `linkat` with `AT_FDCWD` instead of `link` as `linkat` gives
// us a flag to specify how symlinks should be handled. Pass 0 as
// the flags argument, meaning don't follow symlinks.
cvt(unsafe { libc::linkat(libc::AT_FDCWD, src.as_ptr(), libc::AT_FDCWD, dst.as_ptr(), 0) })?;
}
}
Ok(())
}

Expand Down