summaryrefslogtreecommitdiff
path: root/fs/smb/client/smb2ops.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smb/client/smb2ops.c')
-rw-r--r--fs/smb/client/smb2ops.c364
1 files changed, 218 insertions, 146 deletions
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index e586f3f4b5c9..7c392cf5940b 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -504,8 +504,8 @@ smb3_negotiate_wsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
wsize = min_t(unsigned int, wsize, server->max_write);
#ifdef CONFIG_CIFS_SMB_DIRECT
if (server->rdma) {
- struct smbdirect_socket_parameters *sp =
- &server->smbd_conn->socket.parameters;
+ const struct smbdirect_socket_parameters *sp =
+ smbd_get_parameters(server->smbd_conn);
if (server->sign)
/*
@@ -555,8 +555,8 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
rsize = min_t(unsigned int, rsize, server->max_read);
#ifdef CONFIG_CIFS_SMB_DIRECT
if (server->rdma) {
- struct smbdirect_socket_parameters *sp =
- &server->smbd_conn->socket.parameters;
+ const struct smbdirect_socket_parameters *sp =
+ smbd_get_parameters(server->smbd_conn);
if (server->sign)
/*
@@ -954,11 +954,8 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
rc = open_cached_dir(xid, tcon, full_path, cifs_sb, true, &cfid);
if (!rc) {
- if (cfid->has_lease) {
- close_cached_dir(cfid);
- return 0;
- }
close_cached_dir(cfid);
+ return 0;
}
utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb);
@@ -1806,140 +1803,226 @@ free_vars:
return rc;
}
+/**
+ * calc_chunk_count - calculates the number chunks to be filled in the Chunks[]
+ * array of struct copychunk_ioctl
+ *
+ * @tcon: destination file tcon
+ * @bytes_left: how many bytes are left to copy
+ *
+ * Return: maximum number of chunks with which Chunks[] can be filled.
+ */
+static inline u32
+calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left)
+{
+ u32 max_chunks = READ_ONCE(tcon->max_chunks);
+ u32 max_bytes_copy = READ_ONCE(tcon->max_bytes_copy);
+ u32 max_bytes_chunk = READ_ONCE(tcon->max_bytes_chunk);
+ u64 need;
+ u32 allowed;
+
+ if (!max_bytes_chunk || !max_bytes_copy || !max_chunks)
+ return 0;
+
+ /* chunks needed for the remaining bytes */
+ need = DIV_ROUND_UP_ULL(bytes_left, max_bytes_chunk);
+ /* chunks allowed per cc request */
+ allowed = DIV_ROUND_UP(max_bytes_copy, max_bytes_chunk);
+
+ return (u32)umin(need, umin(max_chunks, allowed));
+}
+
+/**
+ * smb2_copychunk_range - server-side copy of data range
+ *
+ * @xid: transaction id
+ * @src_file: source file
+ * @dst_file: destination file
+ * @src_off: source file byte offset
+ * @len: number of bytes to copy
+ * @dst_off: destination file byte offset
+ *
+ * Obtains a resume key for @src_file and issues FSCTL_SRV_COPYCHUNK_WRITE
+ * IOCTLs, splitting the request into chunks limited by tcon->max_*.
+ *
+ * Return: @len on success; negative errno on failure.
+ */
static ssize_t
smb2_copychunk_range(const unsigned int xid,
- struct cifsFileInfo *srcfile,
- struct cifsFileInfo *trgtfile, u64 src_off,
- u64 len, u64 dest_off)
+ struct cifsFileInfo *src_file,
+ struct cifsFileInfo *dst_file,
+ u64 src_off,
+ u64 len,
+ u64 dst_off)
{
- int rc;
- unsigned int ret_data_len;
- struct copychunk_ioctl *pcchunk;
- struct copychunk_ioctl_rsp *retbuf = NULL;
+ int rc = 0;
+ unsigned int ret_data_len = 0;
+ struct copychunk_ioctl *cc_req = NULL;
+ struct copychunk_ioctl_rsp *cc_rsp = NULL;
struct cifs_tcon *tcon;
- int chunks_copied = 0;
- bool chunk_sizes_updated = false;
- ssize_t bytes_written, total_bytes_written = 0;
+ struct copychunk *chunk;
+ u32 chunks, chunk_count, chunk_bytes;
+ u32 copy_bytes, copy_bytes_left;
+ u32 chunks_written, bytes_written;
+ u64 total_bytes_left = len;
+ u64 src_off_prev, dst_off_prev;
+ u32 retries = 0;
+
+ tcon = tlink_tcon(dst_file->tlink);
+
+ trace_smb3_copychunk_enter(xid, src_file->fid.volatile_fid,
+ dst_file->fid.volatile_fid, tcon->tid,
+ tcon->ses->Suid, src_off, dst_off, len);
+
+retry:
+ chunk_count = calc_chunk_count(tcon, total_bytes_left);
+ if (!chunk_count) {
+ rc = -EOPNOTSUPP;
+ goto out;
+ }
- pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL);
- if (pcchunk == NULL)
- return -ENOMEM;
+ cc_req = kzalloc(struct_size(cc_req, Chunks, chunk_count), GFP_KERNEL);
+ if (!cc_req) {
+ rc = -ENOMEM;
+ goto out;
+ }
- cifs_dbg(FYI, "%s: about to call request res key\n", __func__);
/* Request a key from the server to identify the source of the copy */
- rc = SMB2_request_res_key(xid, tlink_tcon(srcfile->tlink),
- srcfile->fid.persistent_fid,
- srcfile->fid.volatile_fid, pcchunk);
+ rc = SMB2_request_res_key(xid,
+ tlink_tcon(src_file->tlink),
+ src_file->fid.persistent_fid,
+ src_file->fid.volatile_fid,
+ cc_req);
- /* Note: request_res_key sets res_key null only if rc !=0 */
+ /* Note: request_res_key sets res_key null only if rc != 0 */
if (rc)
- goto cchunk_out;
+ goto out;
+
+ while (total_bytes_left > 0) {
- /* For now array only one chunk long, will make more flexible later */
- pcchunk->ChunkCount = cpu_to_le32(1);
- pcchunk->Reserved = 0;
- pcchunk->Reserved2 = 0;
+ /* Store previous offsets to allow rewind */
+ src_off_prev = src_off;
+ dst_off_prev = dst_off;
- tcon = tlink_tcon(trgtfile->tlink);
+ chunks = 0;
+ copy_bytes = 0;
+ copy_bytes_left = umin(total_bytes_left, tcon->max_bytes_copy);
+ while (copy_bytes_left > 0 && chunks < chunk_count) {
+ chunk = &cc_req->Chunks[chunks++];
- trace_smb3_copychunk_enter(xid, srcfile->fid.volatile_fid,
- trgtfile->fid.volatile_fid, tcon->tid,
- tcon->ses->Suid, src_off, dest_off, len);
+ chunk->SourceOffset = cpu_to_le64(src_off);
+ chunk->TargetOffset = cpu_to_le64(dst_off);
- while (len > 0) {
- pcchunk->SourceOffset = cpu_to_le64(src_off);
- pcchunk->TargetOffset = cpu_to_le64(dest_off);
- pcchunk->Length =
- cpu_to_le32(min_t(u64, len, tcon->max_bytes_chunk));
+ chunk_bytes = umin(copy_bytes_left, tcon->max_bytes_chunk);
+
+ chunk->Length = cpu_to_le32(chunk_bytes);
+ /* Buffer is zeroed, no need to set chunk->Reserved = 0 */
+
+ src_off += chunk_bytes;
+ dst_off += chunk_bytes;
+
+ copy_bytes_left -= chunk_bytes;
+ copy_bytes += chunk_bytes;
+ }
+
+ cc_req->ChunkCount = cpu_to_le32(chunks);
+ /* Buffer is zeroed, no need to set cc_req->Reserved = 0 */
/* Request server copy to target from src identified by key */
- kfree(retbuf);
- retbuf = NULL;
- rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
- trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
- (char *)pcchunk, sizeof(struct copychunk_ioctl),
- CIFSMaxBufSize, (char **)&retbuf, &ret_data_len);
+ kfree(cc_rsp);
+ cc_rsp = NULL;
+ rc = SMB2_ioctl(xid, tcon, dst_file->fid.persistent_fid,
+ dst_file->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
+ (char *)cc_req, struct_size(cc_req, Chunks, chunks),
+ CIFSMaxBufSize, (char **)&cc_rsp, &ret_data_len);
+
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ if (unlikely(ret_data_len != sizeof(*cc_rsp))) {
+ cifs_tcon_dbg(VFS, "Copychunk invalid response: size %u/%zu\n",
+ ret_data_len, sizeof(*cc_rsp));
+ rc = -EIO;
+ goto out;
+ }
+
+ bytes_written = le32_to_cpu(cc_rsp->TotalBytesWritten);
+ chunks_written = le32_to_cpu(cc_rsp->ChunksWritten);
+ chunk_bytes = le32_to_cpu(cc_rsp->ChunkBytesWritten);
+
if (rc == 0) {
- if (ret_data_len !=
- sizeof(struct copychunk_ioctl_rsp)) {
- cifs_tcon_dbg(VFS, "Invalid cchunk response size\n");
- rc = -EIO;
- goto cchunk_out;
- }
- if (retbuf->TotalBytesWritten == 0) {
- cifs_dbg(FYI, "no bytes copied\n");
+ /* Check if server claimed to write more than we asked */
+ if (unlikely(!bytes_written || bytes_written > copy_bytes ||
+ !chunks_written || chunks_written > chunks)) {
+ cifs_tcon_dbg(VFS, "Copychunk invalid response: bytes written %u/%u, chunks written %u/%u\n",
+ bytes_written, copy_bytes, chunks_written, chunks);
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_tcon_dbg(VFS, "Invalid copy chunk response\n");
- rc = -EIO;
- goto cchunk_out;
+ goto out;
}
- if (le32_to_cpu(retbuf->ChunksWritten) != 1) {
- cifs_tcon_dbg(VFS, "Invalid num chunks written\n");
- rc = -EIO;
- goto cchunk_out;
+
+ /* Partial write: rewind */
+ if (bytes_written < copy_bytes) {
+ u32 delta = copy_bytes - bytes_written;
+
+ src_off -= delta;
+ dst_off -= delta;
}
- chunks_copied++;
-
- bytes_written = le32_to_cpu(retbuf->TotalBytesWritten);
- src_off += bytes_written;
- dest_off += bytes_written;
- len -= bytes_written;
- total_bytes_written += bytes_written;
-
- cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %zu\n",
- le32_to_cpu(retbuf->ChunksWritten),
- le32_to_cpu(retbuf->ChunkBytesWritten),
- bytes_written);
- trace_smb3_copychunk_done(xid, srcfile->fid.volatile_fid,
- trgtfile->fid.volatile_fid, tcon->tid,
- tcon->ses->Suid, src_off, dest_off, len);
- } 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 */
+ total_bytes_left -= bytes_written;
+ continue;
+ }
- /* No need to change MaxChunks since already set to 1 */
- chunk_sizes_updated = true;
- } else
- goto cchunk_out;
+ /*
+ * Check if server is not asking us to reduce size.
+ *
+ * Note: As per MS-SMB2 2.2.32.1, the values returned
+ * in cc_rsp are not strictly lower than what existed
+ * before.
+ */
+ if (bytes_written < tcon->max_bytes_copy) {
+ cifs_tcon_dbg(FYI, "Copychunk MaxBytesCopy updated: %u -> %u\n",
+ tcon->max_bytes_copy, bytes_written);
+ tcon->max_bytes_copy = bytes_written;
+ }
+
+ if (chunks_written < tcon->max_chunks) {
+ cifs_tcon_dbg(FYI, "Copychunk MaxChunks updated: %u -> %u\n",
+ tcon->max_chunks, chunks_written);
+ tcon->max_chunks = chunks_written;
+ }
+
+ if (chunk_bytes < tcon->max_bytes_chunk) {
+ cifs_tcon_dbg(FYI, "Copychunk MaxBytesChunk updated: %u -> %u\n",
+ tcon->max_bytes_chunk, chunk_bytes);
+ tcon->max_bytes_chunk = chunk_bytes;
+ }
+
+ /* reset to last offsets */
+ if (retries++ < 2) {
+ src_off = src_off_prev;
+ dst_off = dst_off_prev;
+ kfree(cc_req);
+ cc_req = NULL;
+ goto retry;
+ }
+
+ break;
}
-cchunk_out:
- kfree(pcchunk);
- kfree(retbuf);
- if (rc)
+out:
+ kfree(cc_req);
+ kfree(cc_rsp);
+ if (rc) {
+ trace_smb3_copychunk_err(xid, src_file->fid.volatile_fid,
+ dst_file->fid.volatile_fid, tcon->tid,
+ tcon->ses->Suid, src_off, dst_off, len, rc);
return rc;
- else
- return total_bytes_written;
+ } else {
+ trace_smb3_copychunk_done(xid, src_file->fid.volatile_fid,
+ dst_file->fid.volatile_fid, tcon->tid,
+ tcon->ses->Suid, src_off, dst_off, len);
+ return len;
+ }
}
static int
@@ -3284,7 +3367,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid,
ses->Suid, offset, len);
- inode_lock(inode);
filemap_invalidate_lock(inode->i_mapping);
i_size = i_size_read(inode);
@@ -3302,6 +3384,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
* first, otherwise the data may be inconsistent with the server.
*/
truncate_pagecache_range(inode, offset, offset + len - 1);
+ netfs_wait_for_outstanding_io(inode);
/* if file not oplocked can't be sure whether asking to extend size */
rc = -EOPNOTSUPP;
@@ -3330,7 +3413,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
zero_range_exit:
filemap_invalidate_unlock(inode->i_mapping);
- inode_unlock(inode);
free_xid(xid);
if (rc)
trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid,
@@ -3354,7 +3436,6 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
xid = get_xid();
- inode_lock(inode);
/* Need to make file sparse, if not already, before freeing range. */
/* Consider adding equivalent for compressed since it could also work */
if (!smb2_set_sparse(xid, tcon, cfile, inode, set_sparse)) {
@@ -3368,6 +3449,7 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
* caches first, otherwise the data may be inconsistent with the server.
*/
truncate_pagecache_range(inode, offset, offset + len - 1);
+ netfs_wait_for_outstanding_io(inode);
cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len);
@@ -3402,7 +3484,6 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
unlock:
filemap_invalidate_unlock(inode->i_mapping);
out:
- inode_unlock(inode);
free_xid(xid);
return rc;
}
@@ -3666,8 +3747,6 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
xid = get_xid();
- inode_lock(inode);
-
old_eof = i_size_read(inode);
if ((off >= old_eof) ||
off + len >= old_eof) {
@@ -3682,6 +3761,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
truncate_pagecache_range(inode, off, old_eof);
ictx->zero_point = old_eof;
+ netfs_wait_for_outstanding_io(inode);
rc = smb2_copychunk_range(xid, cfile, cfile, off + len,
old_eof - off - len, off);
@@ -3702,8 +3782,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
fscache_resize_cookie(cifs_inode_cookie(inode), new_eof);
out_2:
filemap_invalidate_unlock(inode->i_mapping);
- out:
- inode_unlock(inode);
+out:
free_xid(xid);
return rc;
}
@@ -3720,8 +3799,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
xid = get_xid();
- inode_lock(inode);
-
old_eof = i_size_read(inode);
if (off >= old_eof) {
rc = -EINVAL;
@@ -3736,6 +3813,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
if (rc < 0)
goto out_2;
truncate_pagecache_range(inode, off, old_eof);
+ netfs_wait_for_outstanding_io(inode);
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, new_eof);
@@ -3758,8 +3836,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
rc = 0;
out_2:
filemap_invalidate_unlock(inode->i_mapping);
- out:
- inode_unlock(inode);
+out:
free_xid(xid);
return rc;
}
@@ -4219,7 +4296,7 @@ fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, unsigned int orig_len,
static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst *rqst,
int num_rqst, const u8 *sig, u8 **iv,
struct aead_request **req, struct sg_table *sgt,
- unsigned int *num_sgs, size_t *sensitive_size)
+ unsigned int *num_sgs)
{
unsigned int req_size = sizeof(**req) + crypto_aead_reqsize(tfm);
unsigned int iv_size = crypto_aead_ivsize(tfm);
@@ -4236,9 +4313,8 @@ static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst
len += req_size;
len = ALIGN(len, __alignof__(struct scatterlist));
len += array_size(*num_sgs, sizeof(struct scatterlist));
- *sensitive_size = len;
- p = kvzalloc(len, GFP_NOFS);
+ p = kzalloc(len, GFP_NOFS);
if (!p)
return ERR_PTR(-ENOMEM);
@@ -4252,16 +4328,14 @@ static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst
static void *smb2_get_aead_req(struct crypto_aead *tfm, struct smb_rqst *rqst,
int num_rqst, const u8 *sig, u8 **iv,
- struct aead_request **req, struct scatterlist **sgl,
- size_t *sensitive_size)
+ struct aead_request **req, struct scatterlist **sgl)
{
struct sg_table sgtable = {};
unsigned int skip, num_sgs, i, j;
ssize_t rc;
void *p;
- p = smb2_aead_req_alloc(tfm, rqst, num_rqst, sig, iv, req, &sgtable,
- &num_sgs, sensitive_size);
+ p = smb2_aead_req_alloc(tfm, rqst, num_rqst, sig, iv, req, &sgtable, &num_sgs);
if (IS_ERR(p))
return ERR_CAST(p);
@@ -4350,7 +4424,6 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
DECLARE_CRYPTO_WAIT(wait);
unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
void *creq;
- size_t sensitive_size;
rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), enc, key);
if (rc) {
@@ -4376,8 +4449,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
return rc;
}
- creq = smb2_get_aead_req(tfm, rqst, num_rqst, sign, &iv, &req, &sg,
- &sensitive_size);
+ creq = smb2_get_aead_req(tfm, rqst, num_rqst, sign, &iv, &req, &sg);
if (IS_ERR(creq))
return PTR_ERR(creq);
@@ -4407,7 +4479,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
if (!rc && enc)
memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);
- kvfree_sensitive(creq, sensitive_size);
+ kfree_sensitive(creq);
return rc;
}
@@ -4658,7 +4730,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
unsigned int pad_len;
struct cifs_io_subrequest *rdata = mid->callback_data;
struct smb2_hdr *shdr = (struct smb2_hdr *)buf;
- int length;
+ size_t copied;
bool use_rdma_mr = false;
if (shdr->Command != SMB2_READ) {
@@ -4771,10 +4843,10 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
} else if (buf_len >= data_offset + data_len) {
/* read response payload is in buf */
WARN_ONCE(buffer, "read data can be either in buf or in buffer");
- length = copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_iter);
- if (length < 0)
- return length;
- rdata->got_bytes = data_len;
+ copied = copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_iter);
+ if (copied == 0)
+ return -EIO;
+ rdata->got_bytes = copied;
} else {
/* read response payload cannot be in both buf and pages */
WARN_ONCE(1, "buf can not contain only a part of read data");