From b63a9de02d64ecd5ff0749e90253f5b30ba5b9c0 Mon Sep 17 00:00:00 2001 From: Steve French Date: Wed, 8 May 2019 22:41:37 -0500 Subject: [PATCH 01/10] smb3: display session id in debug data Displaying the session id in /proc/fs/cifs/DebugData is needed in order to correlate Linux client information with network and server traces for many common support scenarios. Turned out to be very important for debugging. Signed-off-by: Steve French CC: Stable Reviewed-by: Pavel Shilovsky --- fs/cifs/cifs_debug.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c index 6a69f11aacf7..45e74da40f3a 100644 --- a/fs/cifs/cifs_debug.c +++ b/fs/cifs/cifs_debug.c @@ -380,6 +380,8 @@ skip_rdma: atomic_read(&server->in_send), atomic_read(&server->num_waiters)); #endif + /* dump session id helpful for use with network trace */ + seq_printf(m, " SessionId: 0x%llx", ses->Suid); if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) seq_puts(m, " encrypted"); if (ses->sign) From a205d5005eba9e2bcfff735c5d1e416df39df912 Mon Sep 17 00:00:00 2001 From: Christoph Probst Date: Wed, 8 May 2019 21:36:25 +0200 Subject: [PATCH 02/10] cifs: cleanup smb2ops.c and normalize strings Fix checkpatch warnings/errors in smb2ops.c except "LONG_LINE". Add missing linebreaks, indentings, __func__. Remove void-returns, unneeded braces. Address warnings spotted by checkpatch. Add SPDX License Header. Add missing "\n" and capitalize first letter in some cifs_dbg() strings. Signed-off-by: Christoph Probst Signed-off-by: Steve French Acked-by: Pavel Shilovsky --- fs/cifs/smb2ops.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index a930c8965e5c..466554cdff4b 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * SMB2 version specific operations * @@ -282,7 +283,7 @@ smb2_find_mid(struct TCP_Server_Info *server, char *buf) __u64 wire_mid = le64_to_cpu(shdr->MessageId); if (shdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) { - cifs_dbg(VFS, "encrypted frame parsing not supported yet"); + cifs_dbg(VFS, "Encrypted frame parsing not supported yet\n"); return NULL; } @@ -324,6 +325,7 @@ static int smb2_negotiate(const unsigned int xid, struct cifs_ses *ses) { int rc; + ses->server->CurrentMid = 0; rc = SMB2_negotiate(xid, ses); /* BB we probably don't need to retry with modern servers */ @@ -789,8 +791,6 @@ smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon) SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); else close_shroot(&tcon->crfid); - - return; } static void @@ -818,7 +818,6 @@ smb2_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon) SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid, FS_DEVICE_INFORMATION); SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); - return; } static int @@ -906,9 +905,8 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, value = &src->ea_data[src->ea_name_length + 1]; value_len = (size_t)le16_to_cpu(src->ea_value_length); - if (name_len == 0) { + if (name_len == 0) break; - } if (src_size < 8 + name_len + 1 + value_len) { cifs_dbg(FYI, "EA entry goes beyond length of list\n"); @@ -1161,6 +1159,7 @@ static void smb2_clear_stats(struct cifs_tcon *tcon) { int i; + for (i = 0; i < NUMBER_OF_SMB2_COMMANDS; i++) { atomic_set(&tcon->stats.smb2_stats.smb2_com_sent[i], 0); atomic_set(&tcon->stats.smb2_stats.smb2_com_failed[i], 0); @@ -1529,7 +1528,7 @@ smb2_copychunk_range(const unsigned int xid, if (pcchunk == NULL) return -ENOMEM; - cifs_dbg(FYI, "in smb2_copychunk_range - about to call request res key\n"); + 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, @@ -1649,6 +1648,7 @@ static unsigned int smb2_read_data_offset(char *buf) { struct smb2_read_rsp *rsp = (struct smb2_read_rsp *)buf; + return rsp->DataOffset; } @@ -1777,7 +1777,7 @@ smb2_duplicate_extents(const unsigned int xid, dup_ext_buf.SourceFileOffset = cpu_to_le64(src_off); dup_ext_buf.TargetFileOffset = cpu_to_le64(dest_off); dup_ext_buf.ByteCount = cpu_to_le64(len); - cifs_dbg(FYI, "duplicate extents: src off %lld dst off %lld len %lld", + cifs_dbg(FYI, "Duplicate extents: src off %lld dst off %lld len %lld\n", src_off, dest_off, len); rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); @@ -1794,7 +1794,7 @@ smb2_duplicate_extents(const unsigned int xid, &ret_data_len); if (ret_data_len > 0) - cifs_dbg(FYI, "non-zero response length in duplicate extents"); + cifs_dbg(FYI, "Non-zero response length in duplicate extents\n"); duplicate_extents_out: return rc; @@ -1983,9 +1983,9 @@ smb2_close_dir(const unsigned int xid, struct cifs_tcon *tcon, } /* -* If we negotiate SMB2 protocol and get STATUS_PENDING - update -* the number of credits and return true. Otherwise - return false. -*/ + * If we negotiate SMB2 protocol and get STATUS_PENDING - update + * the number of credits and return true. Otherwise - return false. + */ static bool smb2_is_status_pending(char *buf, struct TCP_Server_Info *server) { @@ -2306,7 +2306,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, struct get_dfs_referral_rsp *dfs_rsp = NULL; u32 dfs_req_size = 0, dfs_rsp_size = 0; - cifs_dbg(FYI, "smb2_get_dfs_refer path <%s>\n", search_name); + cifs_dbg(FYI, "%s: path: %s\n", __func__, search_name); /* * Try to use the IPC tcon, otherwise just use any @@ -2360,7 +2360,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, if (rc) { if ((rc != -ENOENT) && (rc != -EOPNOTSUPP)) - cifs_dbg(VFS, "ioctl error in smb2_get_dfs_refer rc=%d\n", rc); + cifs_dbg(VFS, "ioctl error in %s rc=%d\n", __func__, rc); goto out; } @@ -2369,7 +2369,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, nls_codepage, remap, search_name, true /* is_unicode */); if (rc) { - cifs_dbg(VFS, "parse error in smb2_get_dfs_refer rc=%d\n", rc); + cifs_dbg(VFS, "parse error in %s rc=%d\n", __func__, rc); goto out; } @@ -2745,7 +2745,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, inode = d_inode(cfile->dentry); cifsi = CIFS_I(inode); - trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid, + trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid, ses->Suid, offset, len); @@ -2816,7 +2816,7 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon, return rc; } - cifs_dbg(FYI, "offset %lld len %lld", offset, len); + cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); fsctl_buf.FileOffset = cpu_to_le64(offset); fsctl_buf.BeyondFinalZero = cpu_to_le64(offset + len); @@ -3384,7 +3384,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, req = aead_request_alloc(tfm, GFP_KERNEL); if (!req) { - cifs_dbg(VFS, "%s: Failed to alloc aead request", __func__); + cifs_dbg(VFS, "%s: Failed to alloc aead request\n", __func__); return -ENOMEM; } @@ -3395,7 +3395,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, sg = init_sg(num_rqst, rqst, sign); if (!sg) { - cifs_dbg(VFS, "%s: Failed to init sg", __func__); + cifs_dbg(VFS, "%s: Failed to init sg\n", __func__); rc = -ENOMEM; goto free_req; } @@ -3403,7 +3403,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, iv_len = crypto_aead_ivsize(tfm); iv = kzalloc(iv_len, GFP_KERNEL); if (!iv) { - cifs_dbg(VFS, "%s: Failed to alloc IV", __func__); + cifs_dbg(VFS, "%s: Failed to alloc iv\n", __func__); rc = -ENOMEM; goto free_sg; } @@ -3511,7 +3511,7 @@ smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst, fill_transform_hdr(tr_hdr, orig_len, old_rq); rc = crypt_message(server, num_rqst, new_rq, 1); - cifs_dbg(FYI, "encrypt message returned %d", rc); + cifs_dbg(FYI, "Encrypt message returned %d\n", rc); if (rc) goto err_free; @@ -3552,7 +3552,7 @@ decrypt_raw_data(struct TCP_Server_Info *server, char *buf, rqst.rq_tailsz = (page_data_size % PAGE_SIZE) ? : PAGE_SIZE; rc = crypt_message(server, 1, &rqst, 0); - cifs_dbg(FYI, "decrypt message returned %d\n", rc); + cifs_dbg(FYI, "Decrypt message returned %d\n", rc); if (rc) return rc; From d1c35afb0892fc8b334ee0ce0902155d2cfb118c Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 9 May 2019 00:09:37 -0500 Subject: [PATCH 03/10] smb3: trivial cleanup to smb2ops.c Minor cleanup - e.g. missing \n at end of debug statement. Reported-by: Christoph Probst Signed-off-by: Steve French Acked-by: Pavel Shilovsky --- fs/cifs/smb2ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 466554cdff4b..542b50c0b292 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -2759,7 +2759,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, return rc; } - cifs_dbg(FYI, "offset %lld len %lld", offset, len); + cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); fsctl_buf.FileOffset = cpu_to_le64(offset); fsctl_buf.BeyondFinalZero = cpu_to_le64(offset + len); From 14e25977f98887f1f99c3ce7537a7674aab3cbdd Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Mon, 13 May 2019 11:24:17 +1000 Subject: [PATCH 04/10] cifs: use the right include for signal_pending() This header is actually where signal_pending is defined although either would work. Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index 9a16ff4b9f5e..60661b3f983a 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include "cifspdu.h" #include "cifsglob.h" #include "cifsproto.h" From 1d2a4f57cebdab1d496b976a1e6ea998dc9b03c8 Mon Sep 17 00:00:00 2001 From: Long Li Date: Mon, 13 May 2019 21:01:28 -0700 Subject: [PATCH 05/10] cifs:smbd When reconnecting to server, call smbd_destroy() after all MIDs have been called commit 214bab448476 ("cifs: Call MID callback before destroying transport") assumes that the MID callback should not take srv_mutex, this may not always be true. SMB Direct requires the MID callback completed before calling transport so all pending memory registration can be freed. So restore the original calling sequence so TCP transport will use the same code, but moving smbd_destroy() after all MID has been called. fixes: 214bab448476 ("cifs: Call MID callback before destroying transport") Signed-off-by: Long Li Signed-off-by: Steve French Reviewed-by: Pavel Shilovsky --- fs/cifs/connect.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 084756cfdaee..0b3ac8b76d18 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -528,6 +528,21 @@ cifs_reconnect(struct TCP_Server_Info *server) /* do not want to be sending data on a socket we are freeing */ cifs_dbg(FYI, "%s: tearing down socket\n", __func__); mutex_lock(&server->srv_mutex); + if (server->ssocket) { + cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", + server->ssocket->state, server->ssocket->flags); + kernel_sock_shutdown(server->ssocket, SHUT_WR); + cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n", + server->ssocket->state, server->ssocket->flags); + sock_release(server->ssocket); + server->ssocket = NULL; + } + server->sequence_number = 0; + server->session_estab = false; + kfree(server->session_key.response); + server->session_key.response = NULL; + server->session_key.len = 0; + server->lstrp = jiffies; /* mark submitted MIDs for retry and issue callback */ INIT_LIST_HEAD(&retry_list); @@ -540,6 +555,7 @@ cifs_reconnect(struct TCP_Server_Info *server) list_move(&mid_entry->qhead, &retry_list); } spin_unlock(&GlobalMid_Lock); + mutex_unlock(&server->srv_mutex); cifs_dbg(FYI, "%s: issuing mid callbacks\n", __func__); list_for_each_safe(tmp, tmp2, &retry_list) { @@ -548,24 +564,11 @@ cifs_reconnect(struct TCP_Server_Info *server) mid_entry->callback(mid_entry); } - if (server->ssocket) { - cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", - server->ssocket->state, server->ssocket->flags); - kernel_sock_shutdown(server->ssocket, SHUT_WR); - cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n", - server->ssocket->state, server->ssocket->flags); - sock_release(server->ssocket); - server->ssocket = NULL; - } else if (cifs_rdma_enabled(server)) + if (cifs_rdma_enabled(server)) { + mutex_lock(&server->srv_mutex); smbd_destroy(server); - server->sequence_number = 0; - server->session_estab = false; - kfree(server->session_key.response); - server->session_key.response = NULL; - server->session_key.len = 0; - server->lstrp = jiffies; - - mutex_unlock(&server->srv_mutex); + mutex_unlock(&server->srv_mutex); + } do { try_to_freeze(); From 7f46d23e1b14f0827eb19c60eedcc6525ca2e742 Mon Sep 17 00:00:00 2001 From: Long Li Date: Mon, 13 May 2019 21:01:29 -0700 Subject: [PATCH 06/10] cifs:smbd Use the correct DMA direction when sending data When sending data, use the DMA_TO_DEVICE to map buffers. Also log the number of requests in a compounding request from upper layer. Signed-off-by: Long Li Signed-off-by: Steve French Acked-by: Pavel Shilovsky Acked-by: Ronnie Sahlberg --- fs/cifs/smbdirect.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/cifs/smbdirect.c b/fs/cifs/smbdirect.c index 251ef1223206..caac37b1de8c 100644 --- a/fs/cifs/smbdirect.c +++ b/fs/cifs/smbdirect.c @@ -903,7 +903,7 @@ static int smbd_create_header(struct smbd_connection *info, request->sge[0].addr = ib_dma_map_single(info->id->device, (void *)packet, header_length, - DMA_BIDIRECTIONAL); + DMA_TO_DEVICE); if (ib_dma_mapping_error(info->id->device, request->sge[0].addr)) { mempool_free(request, info->request_mempool); rc = -EIO; @@ -1005,7 +1005,7 @@ static int smbd_post_send_sgl(struct smbd_connection *info, for_each_sg(sgl, sg, num_sgs, i) { request->sge[i+1].addr = ib_dma_map_page(info->id->device, sg_page(sg), - sg->offset, sg->length, DMA_BIDIRECTIONAL); + sg->offset, sg->length, DMA_TO_DEVICE); if (ib_dma_mapping_error( info->id->device, request->sge[i+1].addr)) { rc = -EIO; @@ -2110,8 +2110,10 @@ int smbd_send(struct TCP_Server_Info *server, goto done; } - rqst_idx = 0; + log_write(INFO, "num_rqst=%d total length=%u\n", + num_rqst, remaining_data_length); + rqst_idx = 0; next_rqst: rqst = &rqst_array[rqst_idx]; iov = rqst->rq_iov; From 3b249115719ba2cb56d3f92ee7492e033059d3f3 Mon Sep 17 00:00:00 2001 From: Long Li Date: Wed, 15 May 2019 14:09:04 -0700 Subject: [PATCH 07/10] cifs: Don't match port on SMBDirect transport SMBDirect manages its own ports in the transport layer, there is no need to check the port to find a connection. Signed-off-by: Long Li Signed-off-by: Steve French Reviewed-by: Ronnie sahlberg --- fs/cifs/connect.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 0b3ac8b76d18..8c4121da624e 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2446,6 +2446,10 @@ match_port(struct TCP_Server_Info *server, struct sockaddr *addr) { __be16 port, *sport; + /* SMBDirect manages its own ports, don't match it here */ + if (server->rdma) + return true; + switch (addr->sa_family) { case AF_INET: sport = &((struct sockaddr_in *) &server->dstaddr)->sin_port; From 2c87d6a94d162e68ca393cb87719dae8737f55c0 Mon Sep 17 00:00:00 2001 From: Long Li Date: Wed, 15 May 2019 14:09:05 -0700 Subject: [PATCH 08/10] cifs: Allocate memory for all iovs in smb2_ioctl An IOCTL uses up to 2 iovs. The 1st iov is the command itself, the 2nd iov is optional data for that command. The 1st iov is always allocated on the heap but the 2nd iov may point to a variable on the stack. This will trigger an error when passing the 2nd iov for RDMA I/O. Fix this by allocating a buffer for the 2nd iov. Signed-off-by: Long Li Signed-off-by: Steve French Reviewed-by: Pavel Shilovsky Reviewed-by: Ronnie sahlberg --- fs/cifs/smb2pdu.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 29f011d8d8e2..710ceb875161 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -2538,11 +2538,25 @@ SMB2_ioctl_init(struct cifs_tcon *tcon, struct smb_rqst *rqst, struct kvec *iov = rqst->rq_iov; unsigned int total_len; int rc; + char *in_data_buf; rc = smb2_plain_req_init(SMB2_IOCTL, tcon, (void **) &req, &total_len); if (rc) return rc; + if (indatalen) { + /* + * indatalen is usually small at a couple of bytes max, so + * just allocate through generic pool + */ + in_data_buf = kmalloc(indatalen, GFP_NOFS); + if (!in_data_buf) { + cifs_small_buf_release(req); + return -ENOMEM; + } + memcpy(in_data_buf, in_data, indatalen); + } + req->CtlCode = cpu_to_le32(opcode); req->PersistentFileId = persistent_fid; req->VolatileFileId = volatile_fid; @@ -2563,7 +2577,7 @@ SMB2_ioctl_init(struct cifs_tcon *tcon, struct smb_rqst *rqst, cpu_to_le32(offsetof(struct smb2_ioctl_req, Buffer)); rqst->rq_nvec = 2; iov[0].iov_len = total_len - 1; - iov[1].iov_base = in_data; + iov[1].iov_base = in_data_buf; iov[1].iov_len = indatalen; } else { rqst->rq_nvec = 1; @@ -2605,8 +2619,11 @@ SMB2_ioctl_init(struct cifs_tcon *tcon, struct smb_rqst *rqst, void SMB2_ioctl_free(struct smb_rqst *rqst) { - if (rqst && rqst->rq_iov) + if (rqst && rqst->rq_iov) { cifs_small_buf_release(rqst->rq_iov[0].iov_base); /* request */ + if (rqst->rq_iov[1].iov_len) + kfree(rqst->rq_iov[1].iov_base); + } } From 9ab70ca653307771589e1414102c552d8dbdbbef Mon Sep 17 00:00:00 2001 From: Kovtunenko Oleksandr Date: Tue, 14 May 2019 05:52:34 +0000 Subject: [PATCH 09/10] Fixed https://bugzilla.kernel.org/show_bug.cgi?id=202935 allow write on the same file Copychunk allows source and target to be on the same file. For details on restrictions see MS-SMB2 3.3.5.15.6 Signed-off-by: Kovtunenko Oleksandr Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index b1a5fcfa3ce1..d0cb042732cb 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -1070,11 +1070,6 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, cifs_dbg(FYI, "copychunk range\n"); - if (src_inode == target_inode) { - rc = -EINVAL; - goto out; - } - if (!src_file->private_data || !dst_file->private_data) { rc = -EBADF; cifs_dbg(VFS, "missing cifsFileInfo on copy range src file\n"); From dece44e381ab4a9fd1021db45ba4472e8c85becb Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 15 May 2019 07:17:02 +1000 Subject: [PATCH 10/10] cifs: add support for SEEK_DATA and SEEK_HOLE Add llseek op for SEEK_DATA and SEEK_HOLE. Improves xfstests/285,286,436,445,448 and 490 Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French --- fs/cifs/cifsfs.c | 9 +++++ fs/cifs/cifsglob.h | 2 ++ fs/cifs/smb2ops.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index d0cb042732cb..f5fcd6360056 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -878,6 +878,9 @@ out: static loff_t cifs_llseek(struct file *file, loff_t offset, int whence) { + struct cifsFileInfo *cfile = file->private_data; + struct cifs_tcon *tcon; + /* * whence == SEEK_END || SEEK_DATA || SEEK_HOLE => we must revalidate * the cached file length @@ -909,6 +912,12 @@ static loff_t cifs_llseek(struct file *file, loff_t offset, int whence) if (rc < 0) return (loff_t)rc; } + if (cfile && cfile->tlink) { + tcon = tlink_tcon(cfile->tlink); + if (tcon->ses->server->ops->llseek) + return tcon->ses->server->ops->llseek(file, tcon, + offset, whence); + } return generic_file_llseek(file, offset, whence); } diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 33c251b408aa..334ff5f9c3f3 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -497,6 +497,8 @@ struct smb_version_operations { /* version specific fiemap implementation */ int (*fiemap)(struct cifs_tcon *tcon, struct cifsFileInfo *, struct fiemap_extent_info *, u64, u64); + /* version specific llseek implementation */ + loff_t (*llseek)(struct file *, struct cifs_tcon *, loff_t, int); }; struct smb_version_values { diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 542b50c0b292..e921e6511728 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -2922,6 +2922,90 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, return rc; } +static loff_t smb3_llseek(struct file *file, struct cifs_tcon *tcon, loff_t offset, int whence) +{ + struct cifsFileInfo *wrcfile, *cfile = file->private_data; + struct cifsInodeInfo *cifsi; + struct inode *inode; + int rc = 0; + struct file_allocated_range_buffer in_data, *out_data = NULL; + u32 out_data_len; + unsigned int xid; + + if (whence != SEEK_HOLE && whence != SEEK_DATA) + return generic_file_llseek(file, offset, whence); + + inode = d_inode(cfile->dentry); + cifsi = CIFS_I(inode); + + if (offset < 0 || offset >= i_size_read(inode)) + return -ENXIO; + + xid = get_xid(); + /* + * We need to be sure that all dirty pages are written as they + * might fill holes on the server. + * Note that we also MUST flush any written pages since at least + * some servers (Windows2016) will not reflect recent writes in + * QUERY_ALLOCATED_RANGES until SMB2_flush is called. + */ + wrcfile = find_writable_file(cifsi, false); + if (wrcfile) { + filemap_write_and_wait(inode->i_mapping); + smb2_flush_file(xid, tcon, &wrcfile->fid); + cifsFileInfo_put(wrcfile); + } + + if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)) { + if (whence == SEEK_HOLE) + offset = i_size_read(inode); + goto lseek_exit; + } + + in_data.file_offset = cpu_to_le64(offset); + in_data.length = cpu_to_le64(i_size_read(inode)); + + rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FSCTL_QUERY_ALLOCATED_RANGES, true, + (char *)&in_data, sizeof(in_data), + sizeof(struct file_allocated_range_buffer), + (char **)&out_data, &out_data_len); + if (rc == -E2BIG) + rc = 0; + if (rc) + goto lseek_exit; + + if (whence == SEEK_HOLE && out_data_len == 0) + goto lseek_exit; + + if (whence == SEEK_DATA && out_data_len == 0) { + rc = -ENXIO; + goto lseek_exit; + } + + if (out_data_len < sizeof(struct file_allocated_range_buffer)) { + rc = -EINVAL; + goto lseek_exit; + } + if (whence == SEEK_DATA) { + offset = le64_to_cpu(out_data->file_offset); + goto lseek_exit; + } + if (offset < le64_to_cpu(out_data->file_offset)) + goto lseek_exit; + + offset = le64_to_cpu(out_data->file_offset) + le64_to_cpu(out_data->length); + + lseek_exit: + free_xid(xid); + kfree(out_data); + if (!rc) + return vfs_setpos(file, offset, inode->i_sb->s_maxbytes); + else + return rc; +} + static int smb3_fiemap(struct cifs_tcon *tcon, struct cifsFileInfo *cfile, struct fiemap_extent_info *fei, u64 start, u64 len) @@ -4166,6 +4250,7 @@ struct smb_version_operations smb20_operations = { .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, .fiemap = smb3_fiemap, + .llseek = smb3_llseek, }; struct smb_version_operations smb21_operations = { @@ -4266,6 +4351,7 @@ struct smb_version_operations smb21_operations = { .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, .fiemap = smb3_fiemap, + .llseek = smb3_llseek, }; struct smb_version_operations smb30_operations = { @@ -4375,6 +4461,7 @@ struct smb_version_operations smb30_operations = { .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, .fiemap = smb3_fiemap, + .llseek = smb3_llseek, }; struct smb_version_operations smb311_operations = { @@ -4485,6 +4572,7 @@ struct smb_version_operations smb311_operations = { .ioctl_query_info = smb2_ioctl_query_info, .make_node = smb2_make_node, .fiemap = smb3_fiemap, + .llseek = smb3_llseek, }; struct smb_version_values smb20_values = {