diff options
Diffstat (limited to 'fs/cifs/smb2ops.c')
| -rw-r--r-- | fs/cifs/smb2ops.c | 99 | 
1 files changed, 86 insertions, 13 deletions
| diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 11dde4b24f8..757da3e54d3 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -532,7 +532,10 @@ smb2_clone_range(const unsigned int xid,  	int rc;  	unsigned int ret_data_len;  	struct copychunk_ioctl *pcchunk; -	char *retbuf = NULL; +	struct copychunk_ioctl_rsp *retbuf = NULL; +	struct cifs_tcon *tcon; +	int chunks_copied = 0; +	bool chunk_sizes_updated = false;  	pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL); @@ -547,27 +550,96 @@ smb2_clone_range(const unsigned int xid,  	/* Note: request_res_key sets res_key null only if rc !=0 */  	if (rc) -		return rc; +		goto cchunk_out;  	/* For now array only one chunk long, will make more flexible later */  	pcchunk->ChunkCount = __constant_cpu_to_le32(1);  	pcchunk->Reserved = 0; -	pcchunk->SourceOffset = cpu_to_le64(src_off); -	pcchunk->TargetOffset = cpu_to_le64(dest_off); -	pcchunk->Length = cpu_to_le32(len);  	pcchunk->Reserved2 = 0; -	/* Request that server copy to target from src file identified by key */ -	rc = SMB2_ioctl(xid, tlink_tcon(trgtfile->tlink), -			trgtfile->fid.persistent_fid, -			trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE, -			true /* is_fsctl */, (char *)pcchunk, -			sizeof(struct copychunk_ioctl),	&retbuf, &ret_data_len); +	tcon = tlink_tcon(trgtfile->tlink); -	/* BB need to special case rc = EINVAL to alter chunk size */ +	while (len > 0) { +		pcchunk->SourceOffset = cpu_to_le64(src_off); +		pcchunk->TargetOffset = cpu_to_le64(dest_off); +		pcchunk->Length = +			cpu_to_le32(min_t(u32, len, tcon->max_bytes_chunk)); -	cifs_dbg(FYI, "rc %d data length out %d\n", rc, ret_data_len); +		/* Request server copy to target from src identified by key */ +		rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, +			trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE, +			true /* is_fsctl */, (char *)pcchunk, +			sizeof(struct copychunk_ioctl),	(char **)&retbuf, +			&ret_data_len); +		if (rc == 0) { +			if (ret_data_len != +					sizeof(struct copychunk_ioctl_rsp)) { +				cifs_dbg(VFS, "invalid cchunk response size\n"); +				rc = -EIO; +				goto cchunk_out; +			} +			if (retbuf->TotalBytesWritten == 0) { +				cifs_dbg(FYI, "no bytes copied\n"); +				rc = -EIO; +				goto cchunk_out; +			} +			/* +			 * Check if server claimed to write more than we asked +			 */ +			if (le32_to_cpu(retbuf->TotalBytesWritten) > +			    le32_to_cpu(pcchunk->Length)) { +				cifs_dbg(VFS, "invalid copy chunk response\n"); +				rc = -EIO; +				goto cchunk_out; +			} +			if (le32_to_cpu(retbuf->ChunksWritten) != 1) { +				cifs_dbg(VFS, "invalid num chunks written\n"); +				rc = -EIO; +				goto cchunk_out; +			} +			chunks_copied++; + +			src_off += le32_to_cpu(retbuf->TotalBytesWritten); +			dest_off += le32_to_cpu(retbuf->TotalBytesWritten); +			len -= le32_to_cpu(retbuf->TotalBytesWritten); + +			cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %d\n", +				le32_to_cpu(retbuf->ChunksWritten), +				le32_to_cpu(retbuf->ChunkBytesWritten), +				le32_to_cpu(retbuf->TotalBytesWritten)); +		} else if (rc == -EINVAL) { +			if (ret_data_len != sizeof(struct copychunk_ioctl_rsp)) +				goto cchunk_out; + +			cifs_dbg(FYI, "MaxChunks %d BytesChunk %d MaxCopy %d\n", +				le32_to_cpu(retbuf->ChunksWritten), +				le32_to_cpu(retbuf->ChunkBytesWritten), +				le32_to_cpu(retbuf->TotalBytesWritten)); + +			/* +			 * Check if this is the first request using these sizes, +			 * (ie check if copy succeed once with original sizes +			 * and check if the server gave us different sizes after +			 * we already updated max sizes on previous request). +			 * if not then why is the server returning an error now +			 */ +			if ((chunks_copied != 0) || chunk_sizes_updated) +				goto cchunk_out; + +			/* Check that server is not asking us to grow size */ +			if (le32_to_cpu(retbuf->ChunkBytesWritten) < +					tcon->max_bytes_chunk) +				tcon->max_bytes_chunk = +					le32_to_cpu(retbuf->ChunkBytesWritten); +			else +				goto cchunk_out; /* server gave us bogus size */ + +			/* No need to change MaxChunks since already set to 1 */ +			chunk_sizes_updated = true; +		} +	} +cchunk_out:  	kfree(pcchunk);  	return rc;  } @@ -1247,6 +1319,7 @@ struct smb_version_operations smb30_operations = {  	.create_lease_buf = smb3_create_lease_buf,  	.parse_lease_buf = smb3_parse_lease_buf,  	.clone_range = smb2_clone_range, +	.validate_negotiate = smb3_validate_negotiate,  };  struct smb_version_values smb20_values = { | 
