From 5d8db4abf74f2c61363cdd401a2fb639918387fa Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 13 Sep 2026 01:59:25 +0200 Subject: [PATCH] fix: symlinks --- packages/polyio/src/file.rs | 49 +++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/polyio/src/file.rs b/packages/polyio/src/file.rs index f80453cc..ec5834b8 100644 --- a/packages/polyio/src/file.rs +++ b/packages/polyio/src/file.rs @@ -620,6 +620,10 @@ pub async fn symlink_metadata(path: impl AsRef) -> PolyIOResult }) } +fn resolved_target(original: &std::path::Path) -> PathBuf { + crate::canonicalize(original).unwrap_or_else(|_| original.to_path_buf()) +} + /// Symlink on Unix hard link on Windows so both paths must be on one volume #[tracing::instrument( level = "debug", @@ -633,7 +637,8 @@ pub async fn symlink_file( original: impl AsRef, link: impl AsRef, ) -> PolyIOResult<()> { - let original = original.as_ref(); + let original = resolved_target(original.as_ref()); + let original = original.as_path(); let link = link.as_ref(); #[cfg(windows)] @@ -723,7 +728,8 @@ pub async fn symlink_dir( original: impl AsRef, link: impl AsRef, ) -> PolyIOResult<()> { - let original = original.as_ref(); + let original = resolved_target(original.as_ref()); + let original = original.as_path(); let link = link.as_ref(); #[cfg(windows)] @@ -846,6 +852,45 @@ mod tests { assert_eq!(write_buffer_size(Some(0)), DEFAULT_WRITE_BUFFER); } + #[cfg(unix)] + #[tokio::test] + async fn a_link_target_is_stored_resolved() { + let root = scratch("resolved-target"); + let real = root.join("var").join("home"); + std::fs::create_dir_all(&real).unwrap(); + std::os::unix::fs::symlink(&real, root.join("home")).unwrap(); + + let pack = real.join("pack.zip"); + std::fs::write(&pack, b"pack").unwrap(); + + let through_link = root.join("home").join("pack.zip"); + let link = root.join("resourcepacks.zip"); + + symlink_file(&through_link, &link).await.unwrap(); + + assert_eq!( + read_link(&link).await.unwrap(), + crate::canonicalize(&pack).unwrap(), + "the stored target must be the resolved path, not the spelling we were given" + ); + + std::fs::remove_dir_all(&root).ok(); + } + + #[cfg(unix)] + #[tokio::test] + async fn a_missing_target_is_stored_as_given() { + let root = scratch("unresolved-target"); + let missing = root.join("not-here.zip"); + let link = root.join("link.zip"); + + symlink_file(&missing, &link).await.unwrap(); + + assert_eq!(read_link(&link).await.unwrap(), missing); + + std::fs::remove_dir_all(&root).ok(); + } + fn scratch(tag: &str) -> PathBuf { static N: AtomicU64 = AtomicU64::new(0); let dir = std::env::temp_dir().join(format!(