[PATCH 5/5] ksmbd: implement full-file copy for AAPL ChunkCount=0 COPYCHUNK

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

fsctl_copychunk() treats FSCTL_SRV_COPYCHUNK with ChunkCount=0 as the
standard SMB2 "query my copy limits, don't copy anything" request and
returns success without ever looking up the file handles. That's
correct for compliant SMB2 clients, but macOS Finder's Cmd+D duplicate
sends ChunkCount=0 expecting the server to copy the whole file -- so
duplicated files (and streams) are left at their just-created 0 bytes
while the client reports success.

Scope the full-copy fallback to AAPL-negotiated connections
(conn->is_aapl) only, so standard non-Apple SMB2 clients keep the
spec-correct query-limits behavior unchanged. Handles both the main
data fork (loop over vfs_copy_file_range, since file size doesn't fit
in a single wire chunk's 32-bit Length field) and the ADS
stream-to-stream path (report the actual xattr bytes copied instead
of always reporting 0 when there's no real chunk list to sum).

Signed-off-by: Gael Blivet <[email protected]>
---
 fs/smb/server/smb2pdu.c | 38 +++++++++++++++++++++++++-------------
 fs/smb/server/vfs.c     | 41 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 13 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 63beb2bcd..8546cbda0 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -8843,23 +8843,35 @@ static int fsctl_copychunk(struct ksmbd_work *work,
 		cpu_to_le32(ksmbd_server_side_copy_max_total_size());
 
 	chunk_count = le32_to_cpu(ci_req->ChunkCount);
-	if (chunk_count == 0)
+	/*
+	 * ChunkCount=0 is the standard SMB2 "query my copy limits" request
+	 * (no data copied) -- but macOS Finder's Cmd+D duplicate sends
+	 * FSCTL_SRV_COPYCHUNK with ChunkCount=0 meaning "copy the whole
+	 * file", relying on the AAPL-negotiated server to do a full copy
+	 * instead. Keep the standard no-op behavior for everyone else.
+	 */
+	if (chunk_count == 0 && !work->conn->is_aapl)
 		goto out;
 	total_size_written = 0;
+	i = 0;
 
-	/* verify the SRV_COPYCHUNK_COPY packet */
-	if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
-	    input_count < struct_size(ci_req, Chunks, chunk_count)) {
-		rsp->hdr.Status = STATUS_INVALID_PARAMETER;
-		return -EINVAL;
-	}
+	if (chunk_count) {
+		/* verify the SRV_COPYCHUNK_COPY packet */
+		if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
+		    input_count < struct_size(ci_req, Chunks, chunk_count)) {
+			rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+			return -EINVAL;
+		}
 
-	chunks = &ci_req->Chunks[0];
-	for (i = 0; i < chunk_count; i++) {
-		if (le32_to_cpu(chunks[i].Length) == 0 ||
-		    le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
-			break;
-		total_size_written += le32_to_cpu(chunks[i].Length);
+		chunks = &ci_req->Chunks[0];
+		for (i = 0; i < chunk_count; i++) {
+			if (le32_to_cpu(chunks[i].Length) == 0 ||
+			    le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
+				break;
+			total_size_written += le32_to_cpu(chunks[i].Length);
+		}
+	} else {
+		chunks = &ci_req->Chunks[0];
 	}
 
 	if (i < chunk_count ||
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index be93a8ec9..222e8b63d 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1854,6 +1854,13 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
 				return sret;
 		}
 
+		if (chunk_count == 0) {
+			/* AAPL full-stream-copy request: report actual bytes copied. */
+			*chunk_count_written = 1;
+			*total_size_written = buf_len;
+			return 0;
+		}
+
 		/*
 		 * macOS reuses the main file's chunk list for stream copies,
 		 * so req_total equals the file size, not the stream size.
@@ -1868,6 +1875,40 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
 
 	smb_break_all_levII_oplock(work, dst_fp, 1);
 
+	/*
+	 * macOS Finder's Cmd+D duplicate sends FSCTL_SRV_COPYCHUNK with
+	 * ChunkCount=0 meaning "copy the whole file", not the standard
+	 * SMB2 "query my copy limits, no data" semantics -- fsctl_copychunk()
+	 * only reaches here with chunk_count == 0 for AAPL-negotiated
+	 * connections, so this doesn't affect compliant non-Apple clients.
+	 * Without this, the destination stays at its just-created 0 bytes.
+	 */
+	if (chunk_count == 0) {
+		loff_t size = i_size_read(file_inode(src_fp->filp));
+		loff_t off = 0;
+
+		while (off < size) {
+			ssize_t copied = vfs_copy_file_range(src_fp->filp, off,
+							     dst_fp->filp, off,
+							     size - off, 0);
+			if (copied == -EOPNOTSUPP || copied == -EXDEV)
+				copied = vfs_copy_file_range(src_fp->filp, off,
+							      dst_fp->filp, off,
+							      size - off,
+							      COPY_FILE_SPLICE);
+			if (copied < 0)
+				return copied;
+			if (copied == 0)
+				break;
+			off += copied;
+		}
+
+		*chunk_count_written = 1;
+		*chunk_size_written = (unsigned int)min_t(loff_t, size, UINT_MAX);
+		*total_size_written = off;
+		return 0;
+	}
+
 	if (!work->tcon->posix_extensions) {
 		for (i = 0; i < chunk_count; i++) {
 			src_off = le64_to_cpu(chunks[i].SourceOffset);
-- 
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.