Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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"))] {
// VxWorks and Redox 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