ksmbd: fall back to copy for duplicate extents

FSCTL_DUPLICATE_EXTENTS_TO_FILE currently returns STATUS_NOT_SUPPORTED
when vfs_clone_file_range() cannot clone the requested range. That can
happen on filesystems without reflink support even though the server can
still satisfy the request by copying the bytes.

Validate the requested source range before attempting the operation. If
the destination range extends past EOF, leave the destination size
unchanged and complete the request without copying, matching observed
client expectations for this ioctl.

Reject sparse source to non-sparse destination requests as unsupported.
Keep sparse destination and sparse-to-sparse cases on the normal clone
or copy path.

Reject overlapping same-file ranges as unsupported before attempting
the clone or copy operation.

Return the expected handle status for invalid handles. A closed target
handle fails with STATUS_FILE_CLOSED, while a bad source handle embedded
in the request buffer fails with STATUS_INVALID_HANDLE.

Fall back to vfs_copy_file_range() whenever the clone operation does not
copy the full requested length, and keep reporting an error only if the
fallback also fails or copies a partial range.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-12 00:16:24 +09:00
parent a72692c5bc
commit 96db370817

View File

@ -9933,15 +9933,18 @@ int smb2_ioctl(struct ksmbd_work *work)
dup_ext->PersistentFileHandle);
if (!fp_in) {
pr_err("not found file handle in duplicate extent to file\n");
ret = -ENOENT;
goto out;
ret = -EBADF;
rsp->hdr.Status = STATUS_INVALID_HANDLE;
goto out2;
}
fp_out = ksmbd_lookup_fd_fast(work, id);
if (!fp_out) {
pr_err("not found fp\n");
ret = -ENOENT;
goto dup_ext_out;
ret = -EBADF;
rsp->hdr.Status = STATUS_FILE_CLOSED;
ksmbd_fd_put(work, fp_in);
goto out2;
}
if (!test_tree_conn_flag(work->tcon,
@ -9962,21 +9965,32 @@ int smb2_ioctl(struct ksmbd_work *work)
src_off = le64_to_cpu(dup_ext->SourceFileOffset);
dst_off = le64_to_cpu(dup_ext->TargetFileOffset);
length = le64_to_cpu(dup_ext->ByteCount);
/*
* XXX: It is not clear if FSCTL_DUPLICATE_EXTENTS_TO_FILE
* should fall back to vfs_copy_file_range(). This could be
* beneficial when re-exporting nfs/smb mount, but note that
* this can result in partial copy that returns an error status.
* If/when FSCTL_DUPLICATE_EXTENTS_TO_FILE_EX is implemented,
* fall back to vfs_copy_file_range(), should be avoided when
* the flag DUPLICATE_EXTENTS_DATA_EX_SOURCE_ATOMIC is set.
*/
cloned = vfs_clone_file_range(fp_in->filp, src_off,
fp_out->filp, dst_off, length, 0);
if (cloned == -EXDEV || cloned == -EOPNOTSUPP) {
if (src_off < 0 || dst_off < 0 || length < 0 ||
src_off + length < src_off || dst_off + length < dst_off) {
ret = -EINVAL;
goto dup_ext_out;
}
if (src_off + length > i_size_read(file_inode(fp_in->filp))) {
ret = -EOPNOTSUPP;
goto dup_ext_out;
} else if (cloned != length) {
}
if (dst_off + length > i_size_read(file_inode(fp_out->filp)))
goto dup_ext_out;
if ((fp_in->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE) &&
!(fp_out->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)) {
ret = -EOPNOTSUPP;
goto dup_ext_out;
}
if (file_inode(fp_in->filp) == file_inode(fp_out->filp) &&
dst_off + length > src_off &&
dst_off < src_off + length) {
ret = -EOPNOTSUPP;
goto dup_ext_out;
}
cloned = vfs_clone_file_range(fp_in->filp, src_off,
fp_out->filp, dst_off, length, 0);
if (cloned != length) {
cloned = vfs_copy_file_range(fp_in->filp, src_off,
fp_out->filp, dst_off,
length, 0);