diff options
author | Enzo Matsumiya <ematsumiya@suse.de> | 2024-05-09 15:21:39 -0300 |
---|---|---|
committer | Enzo Matsumiya <ematsumiya@suse.de> | 2024-05-12 17:51:52 -0600 |
commit | 40afed03b810c78e4ca2d08bdfc645088b782709 (patch) | |
tree | 6c308157d0f76b7bacaf0ef36764d52bb9a3a05c /fs/smb | |
parent | 40414c6a34081b372e45c7ce5060a6d34779f6ba (diff) | |
download | linux-40afed03b810c78e4ca2d08bdfc645088b782709.tar.gz linux-40afed03b810c78e4ca2d08bdfc645088b782709.tar.bz2 linux-40afed03b810c78e4ca2d08bdfc645088b782709.zip |
smb: client: implement decompression of READ responses
Implement LZ77 decompression for SMB2 READ responses.
Since a lot of the code in smb2ops.c for receiving encrypted
responses are very similar, add some helpers and/or modify
some of those to support decompression as well. No behaviour
change is expected.
Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
Diffstat (limited to 'fs/smb')
-rw-r--r-- | fs/smb/client/cifsfs.c | 12 | ||||
-rw-r--r-- | fs/smb/client/cifsglob.h | 1 | ||||
-rw-r--r-- | fs/smb/client/compress.c | 49 | ||||
-rw-r--r-- | fs/smb/client/compress.h | 21 | ||||
-rw-r--r-- | fs/smb/client/compress/lz77.c | 108 | ||||
-rw-r--r-- | fs/smb/client/compress/lz77.h | 2 | ||||
-rw-r--r-- | fs/smb/client/smb2ops.c | 293 | ||||
-rw-r--r-- | fs/smb/client/smb2pdu.c | 6 |
8 files changed, 422 insertions, 70 deletions
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index bc160a4eb1cb..6de2ea59e644 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -161,6 +161,7 @@ struct workqueue_struct *fileinfo_put_wq; struct workqueue_struct *cifsoplockd_wq; struct workqueue_struct *deferredclose_wq; struct workqueue_struct *compress_wq; +struct workqueue_struct *decompress_wq; __u32 cifs_lock_secret; /* @@ -1902,9 +1903,15 @@ init_cifs(void) goto out_destroy_deferredclose_wq; } + decompress_wq = alloc_workqueue("smb3decompressd", WQ_CPU_INTENSIVE|WQ_FREEZABLE|WQ_MEM_RECLAIM, 0); + if (!decompress_wq) { + rc = -ENOMEM; + goto out_destroy_compress_wq; + } + rc = cifs_init_inodecache(); if (rc) - goto out_destroy_compress_wq; + goto out_destroy_decompress_wq; rc = init_mids(); if (rc) @@ -1966,6 +1973,8 @@ out_destroy_mids: destroy_mids(); out_destroy_inodecache: cifs_destroy_inodecache(); +out_destroy_decompress_wq: + destroy_workqueue(decompress_wq); out_destroy_compress_wq: destroy_workqueue(compress_wq); out_destroy_deferredclose_wq: @@ -2003,6 +2012,7 @@ exit_cifs(void) cifs_destroy_request_bufs(); destroy_mids(); cifs_destroy_inodecache(); + destroy_workqueue(decompress_wq); destroy_workqueue(compress_wq); destroy_workqueue(deferredclose_wq); destroy_workqueue(cifsoplockd_wq); diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 3b7a162080ba..c366fae1f669 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -2104,6 +2104,7 @@ extern struct workqueue_struct *fileinfo_put_wq; extern struct workqueue_struct *cifsoplockd_wq; extern struct workqueue_struct *deferredclose_wq; extern struct workqueue_struct *compress_wq; +extern struct workqueue_struct *decompress_wq; extern __u32 cifs_lock_secret; extern mempool_t *cifs_mid_poolp; diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c index 4efbccbd40bf..de55ddd122a5 100644 --- a/fs/smb/client/compress.c +++ b/fs/smb/client/compress.c @@ -20,6 +20,7 @@ #include "../common/smb2pdu.h" #include "cifsproto.h" #include "smb2proto.h" +#include "cifs_debug.h" #include "compress/lz77.h" #include "compress.h" @@ -48,3 +49,51 @@ int smb_compress(void *buf, const void *data, size_t *len) return ret; } + +int smb_decompress(const void *src, size_t src_len, void *dst, size_t *dst_len) +{ + const struct smb2_compression_hdr *hdr; + size_t buf_len, data_len; + int ret; + + hdr = src; + if (hdr->CompressionAlgorithm != SMB3_COMPRESS_LZ77) + return -EIO; + + buf_len = le32_to_cpu(hdr->Offset); + data_len = le32_to_cpu(hdr->OriginalCompressedSegmentSize); + + /* + * Copy uncompressed data from the beginning of the payload. + * The remainder is all compressed data. + */ + src += SMB_COMPRESS_HDR_LEN; + memcpy(dst, src, buf_len); + src += buf_len; + src_len -= SMB_COMPRESS_HDR_LEN + buf_len; + *dst_len -= buf_len; + + ret = lz77_decompress(src, src_len, dst + buf_len, dst_len); + if (ret) + return ret; + + if (*dst_len != data_len) { + cifs_dbg(VFS, "decompressed size mismatch: got %zu, expected %zu\n", + *dst_len, data_len); + return -ECONNRESET; + } + + if (((struct smb2_hdr *)dst)->ProtocolId != SMB2_PROTO_NUMBER) { + cifs_dbg(VFS, "decompressed buffer is not an SMB2 message: ProtocolId 0x%x\n", + *(__le32 *)dst); + return -ECONNRESET; + } + + /* + * @dst_len contains only the decompressed data size, add back + * the previously copied uncompressed size + */ + *dst_len += buf_len; + + return 0; +} diff --git a/fs/smb/client/compress.h b/fs/smb/client/compress.h index 38a4ac1c92dd..ffc712fae45a 100644 --- a/fs/smb/client/compress.h +++ b/fs/smb/client/compress.h @@ -25,6 +25,9 @@ /* sizeof(smb2_compression_payload_hdr) - sizeof(OriginalPayloadSize) */ #define SMB_COMPRESS_PAYLOAD_HDR_LEN 8 #define SMB_COMPRESS_MIN_LEN PAGE_SIZE +#define SMB_DECOMPRESS_MAX_LEN(_srv) \ + (256 + SMB_COMPRESS_HDR_LEN + \ + max_t(size_t, (_srv)->maxBuf, max_t(size_t, (_srv)->max_read, (_srv)->max_write))) struct smb_compress_ctx { struct TCP_Server_Info *server; @@ -38,6 +41,7 @@ struct smb_compress_ctx { #ifdef CONFIG_CIFS_COMPRESSION int smb_compress(void *buf, const void *data, size_t *len); +int smb_decompress(const void *src, size_t src_len, void *dst, size_t *dst_len); /** * smb_compress_alg_valid() - Validate a compression algorithm. @@ -63,6 +67,13 @@ static __always_inline int smb_compress_alg_valid(__le16 alg, bool valid_none) return false; } +static __always_inline bool has_compress_hdr(void *buf) +{ + struct smb2_compression_hdr *hdr = buf; + + return (hdr->ProtocolId == SMB2_COMPRESSION_TRANSFORM_ID); +} + /** * should_compress() - Determines if a request (write) or the response to a * request (read) should be compressed. @@ -98,9 +109,19 @@ static __always_inline bool should_compress(const struct cifs_tcon *tcon, const return (shdr->Command == SMB2_READ); } + +static __always_inline size_t decompressed_size(const void *buf) +{ + const struct smb2_compression_hdr *hdr = buf; + + return le32_to_cpu(hdr->Offset) + + le32_to_cpu(hdr->OriginalCompressedSegmentSize); +} #else /* CONFIG_CIFS_COMPRESSION */ #define smb_compress(arg1, arg2, arg3) (-EOPNOTSUPP) +#define smb_decompress(arg1, arg2, arg3, arg4) (-EOPNOTSUPP) #define smb_compress_alg_valid(arg1, arg2) (-EOPNOTSUPP) #define should_compress(arg1, arg2) (false) +#define decompress_size(arg1) (0) #endif /* !CONFIG_CIFS_COMPRESSION */ #endif /* _SMB_COMPRESS_H */ diff --git a/fs/smb/client/compress/lz77.c b/fs/smb/client/compress/lz77.c index 2b8d548f9492..6af0c3e454cd 100644 --- a/fs/smb/client/compress/lz77.c +++ b/fs/smb/client/compress/lz77.c @@ -209,3 +209,111 @@ err_free: return ret; } + +int lz77_decompress(const u8 *src, size_t src_len, u8 *dst, size_t *dst_len) +{ + const u8 *srcp = src, *end = src + src_len, *nib = NULL; + u8 *dstp = dst, *dst_end = dst + *dst_len; + size_t nflags = 0, n, i; + long flags; + + *dst_len = 0; + + while (likely(srcp + 2 <= end)) { + u32 dist, len; + u16 sym; + + if (nflags == 0) { + flags = *(u32 *)srcp; + srcp += 4; + nflags = 32; + } + + /* literals */ + n = flags ? __builtin_clz(flags) : 32; + n = lz77_min(n, nflags); + nflags -= n; + flags <<= n; + + if (dstp + n > dst_end) + return -EFAULT; + + if (lz77_copy(dstp, srcp, n)) + return -EFAULT; + + dstp += n; + srcp += n; + + if (nflags == 0) + continue; + + /* + * This means we've parsed the whole input @src buffer and filled + * @dst within its memory bounds. + * + * However, it's up to callers to determine if the decompressed + * buffer and size are according to what they expected to get. + */ + if (unlikely(srcp + 2 > end)) + break; + + nflags--; + flags <<= 1; + + /* match */ + sym = *(u16 *)srcp; + srcp += 2; + + dist = (sym / 8) + 1; + len = sym % 8; + + /* + * XXX: this section is purposefully not doing any bounds + * checking because "performance". + * + * Let's hope/assume that the checks done above are enough. + */ + if (len == 7) { + if (!nib) { + nib = srcp; + len = *srcp++ % 16; + } else { + len = *nib >> 4; + nib = NULL; + } + + if (len == 15) { + len = *srcp++; + if (len == 255) { + len = *(u16 *)srcp; + srcp += 2; + if (len == 0) { + len = *(u32 *)srcp; + srcp += 4; + } + + if (len < 15 + 7) + return -EIO; + + len -= (15 + 7); + } + len += 15; + } + len += 7; + } + len += 3; + + for (i = 0; i < len; i++) { + u8 *ref = dstp - dist; + + if (dist > dstp - dst) + return -EFAULT; + + *dstp++ = *ref; + } + } + + *dst_len = dstp - dst; + + return 0; +} diff --git a/fs/smb/client/compress/lz77.h b/fs/smb/client/compress/lz77.h index 57dacadd6a58..e26733a57954 100644 --- a/fs/smb/client/compress/lz77.h +++ b/fs/smb/client/compress/lz77.h @@ -280,7 +280,9 @@ static __always_inline unsigned long lz77_min(unsigned long a, unsigned long b) } int lz77_compress(const u8 *src, size_t src_len, u8 *dst, size_t *dst_len); +int lz77_decompress(const u8 *src, size_t src_len, u8 *dst, size_t *dst_len); #else /* CONFIG_CIFS_COMPRESSION */ #define lz77_compress(arg1, arg2, arg3, arg4) (-EOPNOTSUPP) +#define lz77_decompress(arg1, arg2, arg3, arg4) (-EOPNOTSUPP) #endif /* !CONFIG_CIFS_COMPRESSION */ #endif /* _SMB_COMPRESS_LZ77_H */ diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index adced44632c9..34ee8ba24c1d 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4073,16 +4073,6 @@ smb2_dir_needs_close(struct cifsFileInfo *cfile) return !cfile->invalidHandle; } -static __always_inline bool has_compress_hdr(void *buf) -{ - struct smb2_compression_hdr *hdr = buf; - - if (hdr->ProtocolId == SMB2_COMPRESSION_TRANSFORM_ID) - return true; - - return false; -} - static __always_inline struct smb2_hdr *get_shdr(void *buf) { struct smb2_hdr *shdr = buf; @@ -4425,12 +4415,17 @@ err_free: return rc; } +static __always_inline bool has_transform_hdr(const void *buf) +{ + const struct smb2_transform_hdr *hdr = buf; + + return (hdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM); +} + static int smb3_is_transform_hdr(void *buf) { - struct smb2_transform_hdr *trhdr = buf; - - return trhdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM; + return (has_compress_hdr(buf) || has_transform_hdr(buf)); } static int @@ -4640,6 +4635,40 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid, return 0; } +static void handle_offloaded_read(struct TCP_Server_Info *server, struct mid_q_entry *mid, + void *buf, size_t buf_len, void *data, size_t data_len) +{ + int ret; + + ret = handle_read_data(server, mid, buf, buf_len, data, data_len, true); + if (ret >= 0) { + ret = 0; +#ifdef CONFIG_CIFS_STATS2 + mid->when_received = jiffies; +#endif + if (server->ops->is_network_name_deleted) + server->ops->is_network_name_deleted(buf, server); + + mid->callback(mid); + } else { + spin_lock(&server->srv_lock); + if (server->tcpStatus == CifsNeedReconnect) { + spin_lock(&server->mid_lock); + mid->mid_state = MID_RETRY_NEEDED; + spin_unlock(&server->mid_lock); + spin_unlock(&server->srv_lock); + mid->callback(mid); + } else { + spin_lock(&server->mid_lock); + mid->mid_state = MID_REQUEST_SUBMITTED; + mid->mid_flags &= ~(MID_DELETED); + list_add_tail(&mid->qhead, &server->pending_mid_q); + spin_unlock(&server->mid_lock); + spin_unlock(&server->srv_lock); + } + } +} + struct smb2_decrypt_work { struct work_struct decrypt; struct TCP_Server_Info *server; @@ -4648,7 +4677,6 @@ struct smb2_decrypt_work { unsigned int len; }; - static void smb2_decrypt_offload(struct work_struct *work) { struct smb2_decrypt_work *dw = container_of(work, @@ -4667,44 +4695,14 @@ static void smb2_decrypt_offload(struct work_struct *work) dw->server->lstrp = jiffies; mid = smb2_find_dequeue_mid(dw->server, dw->buf); - if (mid == NULL) + if (mid == NULL) { cifs_dbg(FYI, "mid not found\n"); - else { - mid->decrypted = true; - rc = handle_read_data(dw->server, mid, dw->buf, - dw->server->vals->read_rsp_size, - &dw->buffer, dw->len, - true); - if (rc >= 0) { -#ifdef CONFIG_CIFS_STATS2 - mid->when_received = jiffies; -#endif - if (dw->server->ops->is_network_name_deleted) - dw->server->ops->is_network_name_deleted(dw->buf, - dw->server); - - mid->callback(mid); - } else { - spin_lock(&dw->server->srv_lock); - if (dw->server->tcpStatus == CifsNeedReconnect) { - spin_lock(&dw->server->mid_lock); - mid->mid_state = MID_RETRY_NEEDED; - spin_unlock(&dw->server->mid_lock); - spin_unlock(&dw->server->srv_lock); - mid->callback(mid); - } else { - spin_lock(&dw->server->mid_lock); - mid->mid_state = MID_REQUEST_SUBMITTED; - mid->mid_flags &= ~(MID_DELETED); - list_add_tail(&mid->qhead, - &dw->server->pending_mid_q); - spin_unlock(&dw->server->mid_lock); - spin_unlock(&dw->server->srv_lock); - } - } - release_mid(mid); + goto free_pages; } + handle_offloaded_read(dw->server, mid, dw->buf, dw->server->vals->read_rsp_size, + &dw->buffer, dw->len); + release_mid(mid); free_pages: cifs_clear_xarray_buffer(&dw->buffer); cifs_small_buf_release(dw->buf); @@ -4807,8 +4805,7 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid, } else { cifs_dbg(FYI, "mid found\n"); (*mid)->decrypted = true; - rc = handle_read_data(server, *mid, buf, - server->vals->read_rsp_size, + rc = handle_read_data(server, *mid, buf, server->vals->read_rsp_size, &dw->buffer, dw->len, false); if (rc >= 0) { if (server->ops->is_network_name_deleted) { @@ -4923,35 +4920,191 @@ one_more: return ret; } +struct decompress_offload_ctx { + struct TCP_Server_Info *server; + struct work_struct work; + void *buf; + size_t len; +}; + +static void decompress_thread(struct work_struct *work) +{ + struct decompress_offload_ctx *ctx; + struct mid_q_entry *mid; + size_t dst_len; + void *dst; + int ret; + + ctx = container_of(work, struct decompress_offload_ctx, work); + dst_len = decompressed_size(ctx->buf); + dst = kvzalloc(dst_len, GFP_KERNEL); + if (!dst) { + ret = -ENOMEM; + goto err_free; + } + + ret = smb_decompress(ctx->buf, ctx->len, dst, &dst_len); + if (ret) + goto err_free; + + ctx->server->lstrp = jiffies; + mid = smb2_find_dequeue_mid(ctx->server, dst); + if (!mid) { + ret = -EIO; + goto err_free; + } + + handle_offloaded_read(ctx->server, mid, dst, dst_len, NULL, 0); + release_mid(mid); +err_free: + kvfree(ctx->buf); + kvfree(dst); + + if (ret) { + spin_lock(&ctx->server->srv_lock); + ctx->server->tcpStatus = CifsNeedReconnect; + spin_unlock(&ctx->server->srv_lock); + } + + kfree(ctx); +} + +static int receive_compressed(struct TCP_Server_Info *server, char **bufs, struct mid_q_entry **mid, + int *num_mids) +{ + size_t src_len, dst_len; + void *src, *dst; + int ret; + + *num_mids = 0; + mid[0] = NULL; + + src_len = server->pdu_size; + src = kvzalloc(src_len, GFP_KERNEL); + if (!src) + return -ENOMEM; + + ret = server->total_read; + memcpy(src, server->smallbuf, ret); + ret = cifs_read_from_socket(server, src + ret, src_len - ret); + if (ret < 0) + return ret; + + server->total_read += ret; + + dst_len = decompressed_size(src); + + /* offload large decompressions to %decompress_rq */ + if (src_len > CIFSMaxBufSize + MAX_HEADER_SIZE(server) || + dst_len > CIFSMaxBufSize + MAX_HEADER_SIZE(server)) { + struct decompress_offload_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + kvfree(src); + return -ENOMEM; + } + + ctx->server = server; + ctx->buf = src; + ctx->len = src_len; + INIT_WORK(&ctx->work, decompress_thread); + + queue_work(decompress_wq, &ctx->work); + + return -EINPROGRESS; + } + + dst = cifs_buf_get(); + ret = smb_decompress(src, src_len, dst, &dst_len); + if (ret) { + cifs_buf_release(dst); + goto err_free; + } + + mid[0] = smb2_find_mid(server, dst); + if (!mid[0]) { + ret = -EIO; + goto err_free; + } + + *num_mids = 1; + server->bigbuf = bufs[0] = dst; + server->pdu_size = server->total_read = mid[0]->resp_buf_size = dst_len; + + if (mid[0]->handle) + ret = mid[0]->handle(server, mid[0]); + else + ret = cifs_handle_standard(server, mid[0]); +err_free: + kvfree(src); + if (ret) { + *num_mids = 0; + cifs_buf_release(server->bigbuf); + server->bigbuf = (char *)cifs_buf_get(); + server->large_buf = false; + cifs_reconnect(server, true); + } + + return ret; +} + static int smb3_receive_transform(struct TCP_Server_Info *server, struct mid_q_entry **mids, char **bufs, int *num_mids) { char *buf = server->smallbuf; unsigned int pdu_length = server->pdu_size; - struct smb2_transform_hdr *tr_hdr = (struct smb2_transform_hdr *)buf; - unsigned int orig_len = le32_to_cpu(tr_hdr->OriginalMessageSize); - - if (pdu_length < sizeof(struct smb2_transform_hdr) + - sizeof(struct smb2_hdr)) { - cifs_server_dbg(VFS, "Transform message is too small (%u)\n", - pdu_length); - cifs_reconnect(server, true); - return -ECONNABORTED; + unsigned int orig_len, pdu_hdr_len = 0; + bool compressed = false, encrypted = false; + + if (has_transform_hdr(buf)) { + struct smb2_transform_hdr *hdr; + + hdr = (struct smb2_transform_hdr *)buf; + orig_len = le32_to_cpu(hdr->OriginalMessageSize); + pdu_hdr_len = sizeof(*hdr); + if (pdu_length < orig_len + sizeof(struct smb2_transform_hdr)) { + cifs_server_dbg(VFS, "Transform message is broken\n"); + goto err_reconnect; + } + encrypted = true; + } else if (has_compress_hdr(buf)) { + struct smb2_compression_hdr *hdr; + + hdr = (struct smb2_compression_hdr *)buf; + pdu_hdr_len = SMB_COMPRESS_HDR_LEN; + orig_len = le32_to_cpu(hdr->OriginalCompressedSegmentSize); + if (orig_len > SMB_DECOMPRESS_MAX_LEN(server)) { + cifs_server_dbg(VFS, "Uncompressed message is too big (%u, max %lu)\n", + orig_len, SMB_DECOMPRESS_MAX_LEN(server)); + goto err_reconnect; + } + compressed = true; + } else { + cifs_server_dbg(VFS, "Invalid ProtocolId 0x%x\n", *(uint32_t *)buf); + goto err_reconnect; } - if (pdu_length < orig_len + sizeof(struct smb2_transform_hdr)) { - cifs_server_dbg(VFS, "Transform message is broken\n"); - cifs_reconnect(server, true); - return -ECONNABORTED; + if (pdu_length < pdu_hdr_len + sizeof(struct smb2_hdr)) { + cifs_server_dbg(VFS, "Transform message is too small (%u)\n", pdu_length); + goto err_reconnect; } - /* TODO: add support for compounds containing READ. */ - if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server)) { - return receive_encrypted_read(server, &mids[0], num_mids); - } + if (compressed) + /* TODO: encrypted + compressed */ + return receive_compressed(server, bufs, mids, num_mids); + + if (encrypted) { + /* TODO: add support for compounds containing READ. */ + if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server)) + return receive_encrypted_read(server, &mids[0], num_mids); - return receive_encrypted_standard(server, mids, bufs, num_mids); + return receive_encrypted_standard(server, mids, bufs, num_mids); + } +err_reconnect: + cifs_reconnect(server, true); + return -ECONNABORTED; } int @@ -4973,6 +5126,8 @@ static int smb2_next_header(struct TCP_Server_Info *server, char *buf, *noff = le32_to_cpu(t_hdr->OriginalMessageSize); if (unlikely(check_add_overflow(*noff, sizeof(*t_hdr), noff))) return -EINVAL; + } else if (hdr->ProtocolId == SMB2_COMPRESSION_TRANSFORM_ID) { + *noff = 0; } else { *noff = le32_to_cpu(hdr->NextCommand); } diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 8f351a9a73d4..23801b028530 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4615,6 +4615,12 @@ smb2_async_readv(struct cifs_readdata *rdata) flags |= CIFS_HAS_CREDITS; } + if (should_compress(io_parms.tcon, buf)) { + struct smb2_read_req *req = (struct smb2_read_req *)buf; + + req->Flags |= SMB2_READFLAG_REQUEST_COMPRESSED; + } + kref_get(&rdata->refcount); rc = cifs_call_async(server, &rqst, cifs_readv_receive, smb2_readv_callback, |