diff --git a/core/Cargo.lock b/core/Cargo.lock index bf782413c69e..403ee6cedb75 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -6916,6 +6916,7 @@ dependencies = [ "log", "opendal-core", "serde", + "tempfile", "tokio", "xattr", ] diff --git a/core/services/fs/Cargo.toml b/core/services/fs/Cargo.toml index 532348c76d8e..5dc257ab0de6 100644 --- a/core/services/fs/Cargo.toml +++ b/core/services/fs/Cargo.toml @@ -41,3 +41,7 @@ tokio = { workspace = true, features = ["fs", "rt-multi-thread"] } [target.'cfg(unix)'.dependencies] xattr = "1" + +[dev-dependencies] +tempfile = "3" +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/core/services/fs/src/core.rs b/core/services/fs/src/core.rs index b385c8485829..092e5712cdcd 100644 --- a/core/services/fs/src/core.rs +++ b/core/services/fs/src/core.rs @@ -199,7 +199,20 @@ impl FsCore { .ensure_write_abs_path(&self.root, to.trim_end_matches('/')) .await?; - tokio::fs::copy(from, to).await.map_err(new_std_io_error)?; + tokio::fs::copy(&from, &to) + .await + .map_err(new_std_io_error)?; + + // `write_with_user_metadata` is only supported in unix platform. + #[cfg(unix)] + { + if let Ok(user_meta) = Self::get_user_metadata(&from) { + if !user_meta.is_empty() { + Self::set_user_metadata(&to, &user_meta)?; + } + } + } + Ok(()) } @@ -277,3 +290,40 @@ impl FsCore { /// Using "user." as the standard namespace for user-defined attributes. #[cfg(unix)] const XATTR_USER_PREFIX: &str = "user."; + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(unix)] + #[tokio::test] + async fn test_fs_copy_preserves_user_metadata() { + let temp_dir = tempfile::TempDir::new().unwrap(); + let root = temp_dir.path().to_path_buf(); + + let info = Arc::new(AccessorInfo::default()); + let core = FsCore { + info, + root: root.clone(), + atomic_write_dir: None, + buf_pool: oio::PooledBuf::new(1), + }; + + let src = "src_meta.txt"; + let dst = "dst_meta.txt"; + + let src_path = root.join(src); + let dst_path = root.join(dst); + + tokio::fs::write(&src_path, b"payload").await.unwrap(); + + let mut meta = HashMap::new(); + meta.insert("key".to_string(), "preserved123".to_string()); + FsCore::set_user_metadata(&src_path, &meta).unwrap(); + + core.fs_copy(src, dst).await.unwrap(); + + let got = FsCore::get_user_metadata(&dst_path).unwrap(); + assert_eq!(got.get("key").map(String::as_str), Some("preserved123")); + } +}