This is an automated email from the ASF dual-hosted git repository.

dentiny pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 868837c9e feat(fs): copy with metadata (#7535)
868837c9e is described below

commit 868837c9ec2e5e29e7bf97cf8e00a24bf0424766
Author: dentiny <[email protected]>
AuthorDate: Wed Jun 24 09:59:29 2026 -0700

    feat(fs): copy with metadata (#7535)
---
 core/Cargo.lock              |  1 +
 core/services/fs/Cargo.toml  |  4 ++++
 core/services/fs/src/core.rs | 47 +++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/core/Cargo.lock b/core/Cargo.lock
index 457874deb..17a511f37 100644
--- a/core/Cargo.lock
+++ b/core/Cargo.lock
@@ -7016,6 +7016,7 @@ dependencies = [
  "log",
  "opendal-core",
  "serde",
+ "tempfile",
  "tokio",
  "xattr",
 ]
diff --git a/core/services/fs/Cargo.toml b/core/services/fs/Cargo.toml
index 6e66f250d..06ea4d623 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 e6b45f579..e6c54edeb 100644
--- a/core/services/fs/src/core.rs
+++ b/core/services/fs/src/core.rs
@@ -224,7 +224,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)?;
+
+        // only *nix supports `write_with_user_metadata`
+        #[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(())
     }
 
@@ -321,3 +334,35 @@ mod error {
 }
 
 pub(super) use error::*;
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[cfg(unix)]
+    #[tokio::test]
+    async fn test_fs_backend_copy_preserves_user_metadata() {
+        use opendal_core::Operator;
+
+        let temp_dir = tempfile::TempDir::new().unwrap();
+        let root = temp_dir.path();
+
+        let src = "src_meta.txt";
+        let dst = "dst_meta.txt";
+
+        let src_path = root.join(src);
+        let dst_path = root.join(dst);
+
+        std::fs::File::create(&src_path).unwrap();
+
+        let mut meta = HashMap::new();
+        meta.insert("key".to_string(), "preserved123".to_string());
+        FsCore::set_user_metadata(&src_path, &meta).unwrap();
+
+        let op = 
Operator::new(crate::Fs::default().root(root.to_str().unwrap())).unwrap();
+        op.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"));
+    }
+}

Reply via email to