Skip to content

Commit 41134be

Browse files
authored
Rollup merge of #78026 - sunfishcode:symlink-hard-link, r=dtolnay
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. [implementation-defined]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html
2 parents d69ee57 + 6249cda commit 41134be

File tree

3 files changed

+71
-3
lines changed

3 files changed

+71
-3
lines changed

library/std/src/fs.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1701,10 +1701,14 @@ pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
17011701
/// The `dst` path will be a link pointing to the `src` path. Note that systems
17021702
/// often require these two paths to both be located on the same filesystem.
17031703
///
1704+
/// If `src` names a symbolic link, it is platform-specific whether the symbolic
1705+
/// link is followed. On platforms where it's possible to not follow it, it is
1706+
/// not followed, and the created hard link points to the symbolic link itself.
1707+
///
17041708
/// # Platform-specific behavior
17051709
///
1706-
/// This function currently corresponds to the `link` function on Unix
1707-
/// and the `CreateHardLink` function on Windows.
1710+
/// This function currently corresponds to the `linkat` function with no flags
1711+
/// on Unix and the `CreateHardLink` function on Windows.
17081712
/// Note that, this [may change in the future][changes].
17091713
///
17101714
/// [changes]: io#platform-specific-behavior

library/std/src/fs/tests.rs

+51
Original file line numberDiff line numberDiff line change
@@ -1336,3 +1336,54 @@ fn metadata_access_times() {
13361336
}
13371337
}
13381338
}
1339+
1340+
/// Test creating hard links to symlinks.
1341+
#[test]
1342+
fn symlink_hard_link() {
1343+
let tmpdir = tmpdir();
1344+
1345+
// Create "file", a file.
1346+
check!(fs::File::create(tmpdir.join("file")));
1347+
1348+
// Create "symlink", a symlink to "file".
1349+
check!(symlink_file("file", tmpdir.join("symlink")));
1350+
1351+
// Create "hard_link", a hard link to "symlink".
1352+
check!(fs::hard_link(tmpdir.join("symlink"), tmpdir.join("hard_link")));
1353+
1354+
// "hard_link" should appear as a symlink.
1355+
assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink());
1356+
1357+
// We sould be able to open "file" via any of the above names.
1358+
let _ = check!(fs::File::open(tmpdir.join("file")));
1359+
assert!(fs::File::open(tmpdir.join("file.renamed")).is_err());
1360+
let _ = check!(fs::File::open(tmpdir.join("symlink")));
1361+
let _ = check!(fs::File::open(tmpdir.join("hard_link")));
1362+
1363+
// Rename "file" to "file.renamed".
1364+
check!(fs::rename(tmpdir.join("file"), tmpdir.join("file.renamed")));
1365+
1366+
// Now, the symlink and the hard link should be dangling.
1367+
assert!(fs::File::open(tmpdir.join("file")).is_err());
1368+
let _ = check!(fs::File::open(tmpdir.join("file.renamed")));
1369+
assert!(fs::File::open(tmpdir.join("symlink")).is_err());
1370+
assert!(fs::File::open(tmpdir.join("hard_link")).is_err());
1371+
1372+
// The symlink and the hard link should both still point to "file".
1373+
assert!(fs::read_link(tmpdir.join("file")).is_err());
1374+
assert!(fs::read_link(tmpdir.join("file.renamed")).is_err());
1375+
assert_eq!(check!(fs::read_link(tmpdir.join("symlink"))), Path::new("file"));
1376+
assert_eq!(check!(fs::read_link(tmpdir.join("hard_link"))), Path::new("file"));
1377+
1378+
// Remove "file.renamed".
1379+
check!(fs::remove_file(tmpdir.join("file.renamed")));
1380+
1381+
// Now, we can't open the file by any name.
1382+
assert!(fs::File::open(tmpdir.join("file")).is_err());
1383+
assert!(fs::File::open(tmpdir.join("file.renamed")).is_err());
1384+
assert!(fs::File::open(tmpdir.join("symlink")).is_err());
1385+
assert!(fs::File::open(tmpdir.join("hard_link")).is_err());
1386+
1387+
// "hard_link" should still appear as a symlink.
1388+
assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink());
1389+
}

library/std/src/sys/unix/fs.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,20 @@ pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
10811081
pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
10821082
let src = cstr(src)?;
10831083
let dst = cstr(dst)?;
1084-
cvt(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })?;
1084+
cfg_if::cfg_if! {
1085+
if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android"))] {
1086+
// VxWorks, Redox, and old versions of Android lack `linkat`, so use
1087+
// `link` instead. POSIX leaves it implementation-defined whether
1088+
// `link` follows symlinks, so rely on the `symlink_hard_link` test
1089+
// in library/std/src/fs/tests.rs to check the behavior.
1090+
cvt(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })?;
1091+
} else {
1092+
// Use `linkat` with `AT_FDCWD` instead of `link` as `linkat` gives
1093+
// us a flag to specify how symlinks should be handled. Pass 0 as
1094+
// the flags argument, meaning don't follow symlinks.
1095+
cvt(unsafe { libc::linkat(libc::AT_FDCWD, src.as_ptr(), libc::AT_FDCWD, dst.as_ptr(), 0) })?;
1096+
}
1097+
}
10851098
Ok(())
10861099
}
10871100

0 commit comments

Comments
 (0)