[PATCH 7/7] ksmbd: fix COPYCHUNK for ADS streams

"Gaël Blivet-Bailly" <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
From: Gael Blivet <[email protected]>

ksmbd_vfs_copy_file_ranges() unconditionally returned -EBADF whenever
either the source or destination of a server-side COPYCHUNK was a
stream handle. This breaks copying files that carry alternate data
streams (e.g. duplicating a file with a resource fork, or an
AFP_AfpInfo stream) via COPYCHUNK, since vfs_copy_file_range() doesn't
work on xattr-backed streams to begin with -- the copy needs to read
the source xattr's value and write it to the destination directly.
Implement that path instead of failing outright, and echo back
TotalBytesWritten as the sum of the chunk lengths the client sent
(macOS reuses the main file's chunk list for stream copies, so the
requested total reflects the file size rather than the stream size,
and returning the stream's actual size would look like a mismatch to
the client).

Signed-off-by: Gael Blivet <[email protected]>
---
 fs/smb/server/vfs.c | 46 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 44 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index c491574d5..be93a8ec9 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1821,8 +1821,50 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
 		return -EACCES;
 	}
 
-	if (ksmbd_stream_fd(src_fp) || ksmbd_stream_fd(dst_fp))
-		return -EBADF;
+	if (ksmbd_stream_fd(src_fp) || ksmbd_stream_fd(dst_fp)) {
+		/*
+		 * ADS stream copychunk: copy xattr from src to dst directly.
+		 * vfs_copy_file_range doesn't work on xattr-backed streams.
+		 */
+		char *buf = NULL;
+		ssize_t buf_len;
+		int sret;
+
+		if (!ksmbd_stream_fd(src_fp) || !ksmbd_stream_fd(dst_fp))
+			return -EINVAL;
+
+		buf_len = ksmbd_vfs_getxattr(file_mnt_idmap(src_fp->filp),
+					     file_dentry(src_fp->filp),
+					     src_fp->stream.name, &buf);
+		if (buf_len < 0)
+			return buf_len;
+
+		if (buf_len > 0) {
+			/*
+			 * vfs_setxattr(NULL, 0) removes the xattr on Linux, so
+			 * skip it for empty streams: the Open already created
+			 * the empty xattr via smb2_set_stream_name_xattr.
+			 */
+			sret = ksmbd_vfs_setxattr(file_mnt_idmap(dst_fp->filp),
+						  &dst_fp->filp->f_path,
+						  dst_fp->stream.name,
+						  buf, buf_len, 0, false);
+			kfree(buf);
+			if (sret < 0)
+				return sret;
+		}
+
+		/*
+		 * macOS reuses the main file's chunk list for stream copies,
+		 * so req_total equals the file size, not the stream size.
+		 * Echo back TotalBytesWritten = sum(chunks) so macOS doesn't
+		 * treat the size mismatch as an error.
+		 */
+		*chunk_count_written = chunk_count;
+		for (i = 0; i < chunk_count; i++)
+			*total_size_written += le32_to_cpu(chunks[i].Length);
+		return 0;
+	}
 
 	smb_break_all_levII_oplock(work, dst_fp, 1);
 
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.