From baf57b56d3604880ccb3956ec6c62ea894f5de99 Mon Sep 17 00:00:00 2001 From: Paul Aurich Date: Thu, 9 Jul 2020 22:01:16 -0700 Subject: [PATCH 01/16] cifs: Fix leak when handling lease break for cached root fid Handling a lease break for the cached root didn't free the smb2_lease_break_work allocation, resulting in a leak: unreferenced object 0xffff98383a5af480 (size 128): comm "cifsd", pid 684, jiffies 4294936606 (age 534.868s) hex dump (first 32 bytes): c0 ff ff ff 1f 00 00 00 88 f4 5a 3a 38 98 ff ff ..........Z:8... 88 f4 5a 3a 38 98 ff ff 80 88 d6 8a ff ff ff ff ..Z:8........... backtrace: [<0000000068957336>] smb2_is_valid_oplock_break+0x1fa/0x8c0 [<0000000073b70b9e>] cifs_demultiplex_thread+0x73d/0xcc0 [<00000000905fa372>] kthread+0x11c/0x150 [<0000000079378e4e>] ret_from_fork+0x22/0x30 Avoid this leak by only allocating when necessary. Fixes: a93864d93977 ("cifs: add lease tracking to the cached root fid") Signed-off-by: Paul Aurich CC: Stable # v4.18+ Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/smb2misc.c | 75 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index 157992864ce7..d88e2683626e 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -508,15 +508,31 @@ cifs_ses_oplock_break(struct work_struct *work) kfree(lw); } -static bool -smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, - struct smb2_lease_break_work *lw) +static void +smb2_queue_pending_open_break(struct tcon_link *tlink, __u8 *lease_key, + __le32 new_lease_state) +{ + struct smb2_lease_break_work *lw; + + lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL); + if (!lw) { + cifs_put_tlink(tlink); + return; + } + + INIT_WORK(&lw->lease_break, cifs_ses_oplock_break); + lw->tlink = tlink; + lw->lease_state = new_lease_state; + memcpy(lw->lease_key, lease_key, SMB2_LEASE_KEY_SIZE); + queue_work(cifsiod_wq, &lw->lease_break); +} + +static bool +smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp) { - bool found; __u8 lease_state; struct list_head *tmp; struct cifsFileInfo *cfile; - struct cifs_pending_open *open; struct cifsInodeInfo *cinode; int ack_req = le32_to_cpu(rsp->Flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); @@ -546,22 +562,29 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, cfile->oplock_level = lease_state; cifs_queue_oplock_break(cfile); - kfree(lw); return true; } - found = false; + return false; +} + +static struct cifs_pending_open * +smb2_tcon_find_pending_open_lease(struct cifs_tcon *tcon, + struct smb2_lease_break *rsp) +{ + __u8 lease_state = le32_to_cpu(rsp->NewLeaseState); + int ack_req = le32_to_cpu(rsp->Flags & + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); + struct cifs_pending_open *open; + struct cifs_pending_open *found = NULL; + list_for_each_entry(open, &tcon->pending_opens, olist) { if (memcmp(open->lease_key, rsp->LeaseKey, SMB2_LEASE_KEY_SIZE)) continue; if (!found && ack_req) { - found = true; - memcpy(lw->lease_key, open->lease_key, - SMB2_LEASE_KEY_SIZE); - lw->tlink = cifs_get_tlink(open->tlink); - queue_work(cifsiod_wq, &lw->lease_break); + found = open; } cifs_dbg(FYI, "found in the pending open list\n"); @@ -582,14 +605,7 @@ smb2_is_valid_lease_break(char *buffer) struct TCP_Server_Info *server; struct cifs_ses *ses; struct cifs_tcon *tcon; - struct smb2_lease_break_work *lw; - - lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL); - if (!lw) - return false; - - INIT_WORK(&lw->lease_break, cifs_ses_oplock_break); - lw->lease_state = rsp->NewLeaseState; + struct cifs_pending_open *open; cifs_dbg(FYI, "Checking for lease break\n"); @@ -607,11 +623,27 @@ smb2_is_valid_lease_break(char *buffer) spin_lock(&tcon->open_file_lock); cifs_stats_inc( &tcon->stats.cifs_stats.num_oplock_brks); - if (smb2_tcon_has_lease(tcon, rsp, lw)) { + if (smb2_tcon_has_lease(tcon, rsp)) { spin_unlock(&tcon->open_file_lock); spin_unlock(&cifs_tcp_ses_lock); return true; } + open = smb2_tcon_find_pending_open_lease(tcon, + rsp); + if (open) { + __u8 lease_key[SMB2_LEASE_KEY_SIZE]; + struct tcon_link *tlink; + + tlink = cifs_get_tlink(open->tlink); + memcpy(lease_key, open->lease_key, + SMB2_LEASE_KEY_SIZE); + spin_unlock(&tcon->open_file_lock); + spin_unlock(&cifs_tcp_ses_lock); + smb2_queue_pending_open_break(tlink, + lease_key, + rsp->NewLeaseState); + return true; + } spin_unlock(&tcon->open_file_lock); if (tcon->crfid.is_valid && @@ -629,7 +661,6 @@ smb2_is_valid_lease_break(char *buffer) } } spin_unlock(&cifs_tcp_ses_lock); - kfree(lw); cifs_dbg(FYI, "Can not process lease break - no lease matched\n"); return false; } From 0a018944eee913962bce8ffebbb121960d5125d9 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 16 Jul 2020 00:34:21 -0500 Subject: [PATCH 02/16] smb3: warn on confusing error scenario with sec=krb5 When mounting with Kerberos, users have been confused about the default error returned in scenarios in which either keyutils is not installed or the user did not properly acquire a krb5 ticket. Log a warning message in the case that "ENOKEY" is returned from the get_spnego_key upcall so that users can better understand why mount failed in those two cases. CC: Stable Signed-off-by: Steve French --- fs/cifs/smb2pdu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 2f4cdd290c46..492688764004 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -1387,6 +1387,8 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) spnego_key = cifs_get_spnego_key(ses); if (IS_ERR(spnego_key)) { rc = PTR_ERR(spnego_key); + if (rc == -ENOKEY) + cifs_dbg(VFS, "Verify user has a krb5 ticket and keyutils is installed\n"); spnego_key = NULL; goto out; } From 66a4bbc327e740c65b19a471fb9911f7f4b876fb Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 7 Jul 2020 19:27:41 +0800 Subject: [PATCH 03/16] cifs: remove unused variable 'server' Fix build warning by removing unused variable 'server': fs/cifs/inode.c:1089:26: warning: variable server set but not used [-Wunused-but-set-variable] 1089 | struct TCP_Server_Info *server; | ^~~~~~ Signed-off-by: Wei Yongjun Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/inode.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index ce95801e9b66..3989d08396ac 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -1086,7 +1086,6 @@ smb311_posix_get_inode_info(struct inode **inode, struct super_block *sb, unsigned int xid) { struct cifs_tcon *tcon; - struct TCP_Server_Info *server; struct tcon_link *tlink; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); bool adjust_tz = false; @@ -1100,7 +1099,6 @@ smb311_posix_get_inode_info(struct inode **inode, if (IS_ERR(tlink)) return PTR_ERR(tlink); tcon = tlink_tcon(tlink); - server = tcon->ses->server; /* * 1. Fetch file metadata From a3713ec3d775ff9da2e46f6652b23ff598c3ed36 Mon Sep 17 00:00:00 2001 From: Roberto Bergantinos Corpas Date: Fri, 3 Jul 2020 11:29:32 +0200 Subject: [PATCH 04/16] cifs`: handle ERRBaduid for SMB1 If server returns ERRBaduid but does not reset transport connection, we'll keep sending command with a non-valid UID for the server as long as transport is healthy, without actually recovering. This have been observed on the field. This patch adds ERRBaduid handling so that we set CifsNeedReconnect. map_and_check_smb_error() can be modified to extend use cases. Signed-off-by: Roberto Bergantinos Corpas Signed-off-by: Steve French Reviewed-by: Ronnie Sahlberg --- fs/cifs/cifsproto.h | 1 + fs/cifs/netmisc.c | 27 +++++++++++++++++++++++++++ fs/cifs/transport.c | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 7a836ec0438e..ed13be38a265 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -154,6 +154,7 @@ extern int decode_negTokenInit(unsigned char *security_blob, int length, extern int cifs_convert_address(struct sockaddr *dst, const char *src, int len); extern void cifs_set_port(struct sockaddr *addr, const unsigned short int port); extern int map_smb_to_linux_error(char *buf, bool logErr); +extern int map_and_check_smb_error(struct mid_q_entry *mid, bool logErr); extern void header_assemble(struct smb_hdr *, char /* command */ , const struct cifs_tcon *, int /* length of fixed section (word count) in two byte units */); diff --git a/fs/cifs/netmisc.c b/fs/cifs/netmisc.c index b7ca4960d4ca..0e728aac67e9 100644 --- a/fs/cifs/netmisc.c +++ b/fs/cifs/netmisc.c @@ -881,6 +881,33 @@ map_smb_to_linux_error(char *buf, bool logErr) return rc; } +int +map_and_check_smb_error(struct mid_q_entry *mid, bool logErr) +{ + int rc; + struct smb_hdr *smb = (struct smb_hdr *)mid->resp_buf; + + rc = map_smb_to_linux_error((char *)smb, logErr); + if (rc == -EACCES && !(smb->Flags2 & SMBFLG2_ERR_STATUS)) { + /* possible ERRBaduid */ + __u8 class = smb->Status.DosError.ErrorClass; + __u16 code = le16_to_cpu(smb->Status.DosError.Error); + + /* switch can be used to handle different errors */ + if (class == ERRSRV && code == ERRbaduid) { + cifs_dbg(FYI, "Server returned 0x%x, reconnecting session...\n", + code); + spin_lock(&GlobalMid_Lock); + if (mid->server->tcpStatus != CifsExiting) + mid->server->tcpStatus = CifsNeedReconnect; + spin_unlock(&GlobalMid_Lock); + } + } + + return rc; +} + + /* * calculate the size of the SMB message based on the fixed header * portion, the number of word parameters and the data portion of the message diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index 84433d0653f9..ac7632482736 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -936,7 +936,7 @@ cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server, } /* BB special case reconnect tid and uid here? */ - return map_smb_to_linux_error(mid->resp_buf, log_error); + return map_and_check_smb_error(mid, log_error); } struct mid_q_entry * From 8e408fc9fd48664495147a4213467d99a8922c9d Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 15 Jul 2020 08:18:05 +1000 Subject: [PATCH 05/16] cifs: smb1: Try failing back to SetFileInfo if SetPathInfo fails RHBZ 1145308 Some very old server may not support SetPathInfo to adjust the timestamps of directories. For these servers, try to open the directory and use SetFileInfo. Minor correction to patch included that was Reported-by: kernel test robot Signed-off-by: Ronnie Sahlberg Signed-off-by: Steve French Tested-by: Kenneth D'souza --- fs/cifs/cifsproto.h | 2 +- fs/cifs/cifssmb.c | 39 ++++++++++++++++++++++++++++++++++++++- fs/cifs/smb1ops.c | 4 ++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index ed13be38a265..336feff99c93 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -345,7 +345,7 @@ extern int CIFSSMBQFSPosixInfo(const unsigned int xid, struct cifs_tcon *tcon, extern int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, const FILE_BASIC_INFO *data, const struct nls_table *nls_codepage, - int remap_special_chars); + struct cifs_sb_info *cifs_sb); extern int CIFSSMBSetFileInfo(const unsigned int xid, struct cifs_tcon *tcon, const FILE_BASIC_INFO *data, __u16 fid, __u32 pid_of_opener); diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c index bf41ee048396..dc62e2620cc8 100644 --- a/fs/cifs/cifssmb.c +++ b/fs/cifs/cifssmb.c @@ -5913,10 +5913,42 @@ CIFSSMBSetFileDisposition(const unsigned int xid, struct cifs_tcon *tcon, return rc; } +static int +CIFSSMBSetPathInfoFB(const unsigned int xid, struct cifs_tcon *tcon, + const char *fileName, const FILE_BASIC_INFO *data, + const struct nls_table *nls_codepage, + struct cifs_sb_info *cifs_sb) +{ + int oplock = 0; + struct cifs_open_parms oparms; + struct cifs_fid fid; + int rc; + + oparms.tcon = tcon; + oparms.cifs_sb = cifs_sb; + oparms.desired_access = GENERIC_WRITE; + oparms.create_options = cifs_create_options(cifs_sb, 0); + oparms.disposition = FILE_OPEN; + oparms.path = fileName; + oparms.fid = &fid; + oparms.reconnect = false; + + rc = CIFS_open(xid, &oparms, &oplock, NULL); + if (rc) + goto out; + + rc = CIFSSMBSetFileInfo(xid, tcon, data, fid.netfid, current->tgid); + CIFSSMBClose(xid, tcon, fid.netfid); +out: + + return rc; +} + int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, const FILE_BASIC_INFO *data, - const struct nls_table *nls_codepage, int remap) + const struct nls_table *nls_codepage, + struct cifs_sb_info *cifs_sb) { TRANSACTION2_SPI_REQ *pSMB = NULL; TRANSACTION2_SPI_RSP *pSMBr = NULL; @@ -5925,6 +5957,7 @@ CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, int bytes_returned = 0; char *data_offset; __u16 params, param_offset, offset, byte_count, count; + int remap = cifs_remap(cifs_sb); cifs_dbg(FYI, "In SetTimes\n"); @@ -5987,6 +6020,10 @@ SetTimesRetry: if (rc == -EAGAIN) goto SetTimesRetry; + if (rc == -EOPNOTSUPP) + return CIFSSMBSetPathInfoFB(xid, tcon, fileName, data, + nls_codepage, cifs_sb); + return rc; } diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c index 197ed455e657..80287c26cfac 100644 --- a/fs/cifs/smb1ops.c +++ b/fs/cifs/smb1ops.c @@ -688,7 +688,7 @@ cifs_mkdir_setinfo(struct inode *inode, const char *full_path, dosattrs = cifsInode->cifsAttrs|ATTR_READONLY; info.Attributes = cpu_to_le32(dosattrs); rc = CIFSSMBSetPathInfo(xid, tcon, full_path, &info, cifs_sb->local_nls, - cifs_remap(cifs_sb)); + cifs_sb); if (rc == 0) cifsInode->cifsAttrs = dosattrs; } @@ -783,7 +783,7 @@ smb_set_file_info(struct inode *inode, const char *full_path, tcon = tlink_tcon(tlink); rc = CIFSSMBSetPathInfo(xid, tcon, full_path, buf, cifs_sb->local_nls, - cifs_remap(cifs_sb)); + cifs_sb); if (rc == 0) { cinode->cifsAttrs = le32_to_cpu(buf->Attributes); goto out; From 60e5e4b3bc8eccbcda9511fea7141963030d5af7 Mon Sep 17 00:00:00 2001 From: Liao Pingfang Date: Tue, 14 Jul 2020 18:59:12 +0800 Subject: [PATCH 06/16] cifs: Remove the superfluous break Remove the superfuous break, as there is a 'return' before it. Signed-off-by: Liao Pingfang Signed-off-by: Yi Wang Signed-off-by: Steve French --- fs/cifs/sess.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 5d05bd2822d2..6708ab0aad1c 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -1705,7 +1705,6 @@ static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data) #else cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n"); return -ENOSYS; - break; #endif /* CONFIG_CIFS_UPCALL */ case RawNTLMSSP: sess_data->func = sess_auth_rawntlmssp_negotiate; From a03f507de5f44673cd7c67e24f64b479057c2ce0 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2020 17:13:16 -0700 Subject: [PATCH 07/16] cifs: delete duplicated words in header files Drop repeated words in multiple comments. (be, use, the, See) Signed-off-by: Randy Dunlap Cc: Steve French Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Steve French --- fs/cifs/cifsacl.h | 4 ++-- fs/cifs/cifsglob.h | 2 +- fs/cifs/smb2pdu.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/cifs/cifsacl.h b/fs/cifs/cifsacl.h index 17562ea00e18..45665ff87b64 100644 --- a/fs/cifs/cifsacl.h +++ b/fs/cifs/cifsacl.h @@ -132,7 +132,7 @@ struct cifs_ace { /* * The current SMB3 form of security descriptor is similar to what was used for * cifs (see above) but some fields are split, and fields in the struct below - * matches names of fields to the the spec, MS-DTYP (see sections 2.4.5 and + * matches names of fields to the spec, MS-DTYP (see sections 2.4.5 and * 2.4.6). Note that "CamelCase" fields are used in this struct in order to * match the MS-DTYP and MS-SMB2 specs which define the wire format. */ @@ -178,7 +178,7 @@ struct smb3_acl { /* * Used to store the special 'NFS SIDs' used to persist the POSIX uid and gid - * See See http://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx + * See http://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx */ struct owner_sid { u8 Revision; diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index fad37d61910a..af6329fdef9e 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1466,7 +1466,7 @@ struct cifsInodeInfo { struct list_head llist; /* locks helb by this inode */ /* * NOTE: Some code paths call down_read(lock_sem) twice, so - * we must always use use cifs_down_write() instead of down_write() + * we must always use cifs_down_write() instead of down_write() * for this semaphore to avoid deadlocks. */ struct rw_semaphore lock_sem; /* protect the fields above */ diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h index 236814d61248..c3f1baf5bde2 100644 --- a/fs/cifs/smb2pdu.h +++ b/fs/cifs/smb2pdu.h @@ -31,7 +31,7 @@ * Note that, due to trying to use names similar to the protocol specifications, * there are many mixed case field names in the structures below. Although * this does not match typical Linux kernel style, it is necessary to be - * be able to match against the protocol specfication. + * able to match against the protocol specfication. * * SMB2 commands * Some commands have minimal (wct=0,bcc=0), or uninteresting, responses From 1a0e7f7c3c573a79bcd787d8a05e80651041b815 Mon Sep 17 00:00:00 2001 From: Qinglang Miao Date: Sat, 25 Jul 2020 16:56:01 +0800 Subject: [PATCH 08/16] cifs: convert to use be32_add_cpu() Convert cpu_to_be32(be32_to_cpu(E1) + E2) to use be32_add_cpu(). Signed-off-by: Qinglang Miao Signed-off-by: Steve French --- fs/cifs/connect.c | 3 +-- fs/cifs/sess.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index a61abde09ffe..fe4b55977dd8 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5114,8 +5114,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, bcc_ptr += strlen("?????"); bcc_ptr += 1; count = bcc_ptr - &pSMB->Password[0]; - pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu( - pSMB->hdr.smb_buf_length) + count); + be32_add_cpu(&pSMB->hdr.smb_buf_length, count); pSMB->ByteCount = cpu_to_le16(count); rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length, diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 6708ab0aad1c..69cd5856621b 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -938,8 +938,7 @@ sess_sendreceive(struct sess_data *sess_data) struct kvec rsp_iov = { NULL, 0 }; count = sess_data->iov[1].iov_len + sess_data->iov[2].iov_len; - smb_buf->smb_buf_length = - cpu_to_be32(be32_to_cpu(smb_buf->smb_buf_length) + count); + be32_add_cpu(&smb_buf->smb_buf_length, count); put_bcc(count, smb_buf); rc = SendReceive2(sess_data->xid, sess_data->ses, From 565674d613d7f45d87284ba9f0be730ce4b335d0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Jul 2020 09:36:38 -0300 Subject: [PATCH 09/16] cifs: merge __{cifs,smb2}_reconnect[_tcon]() into cifs_tree_connect() They were identical execpt to CIFSTCon() vs. SMB2_tcon(). These are also available via ops->tree_connect(). Signed-off-by: Stefan Metzmacher Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifsproto.h | 3 ++ fs/cifs/cifssmb.c | 112 +------------------------------------------ fs/cifs/connect.c | 100 +++++++++++++++++++++++++++++++++++++++ fs/cifs/smb2pdu.c | 113 +------------------------------------------- 4 files changed, 105 insertions(+), 223 deletions(-) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 336feff99c93..c2ab937a7048 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -272,6 +272,9 @@ extern void cifs_move_llist(struct list_head *source, struct list_head *dest); extern void cifs_free_llist(struct list_head *llist); extern void cifs_del_lock_waiters(struct cifsLockInfo *lock); +extern int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, + const struct nls_table *nlsc); + extern int cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses); extern int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c index dc62e2620cc8..0e763d2dcf16 100644 --- a/fs/cifs/cifssmb.c +++ b/fs/cifs/cifssmb.c @@ -124,116 +124,6 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) */ } -#ifdef CONFIG_CIFS_DFS_UPCALL -static int __cifs_reconnect_tcon(const struct nls_table *nlsc, - struct cifs_tcon *tcon) -{ - int rc; - struct TCP_Server_Info *server = tcon->ses->server; - struct dfs_cache_tgt_list tl; - struct dfs_cache_tgt_iterator *it = NULL; - char *tree; - const char *tcp_host; - size_t tcp_host_len; - const char *dfs_host; - size_t dfs_host_len; - - tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); - if (!tree) - return -ENOMEM; - - if (!tcon->dfs_path) { - if (tcon->ipc) { - scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", - server->hostname); - rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc); - } else { - rc = CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc); - } - goto out; - } - - rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); - if (rc) - goto out; - - extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); - - for (it = dfs_cache_get_tgt_iterator(&tl); it; - it = dfs_cache_get_next_tgt(&tl, it)) { - const char *share, *prefix; - size_t share_len, prefix_len; - bool target_match; - - rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix, - &prefix_len); - if (rc) { - cifs_dbg(VFS, "%s: failed to parse target share %d\n", - __func__, rc); - continue; - } - - extract_unc_hostname(share, &dfs_host, &dfs_host_len); - - if (dfs_host_len != tcp_host_len - || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) { - cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", - __func__, - (int)dfs_host_len, dfs_host, - (int)tcp_host_len, tcp_host); - - rc = match_target_ip(server, dfs_host, dfs_host_len, - &target_match); - if (rc) { - cifs_dbg(VFS, "%s: failed to match target ip: %d\n", - __func__, rc); - break; - } - - if (!target_match) { - cifs_dbg(FYI, "%s: skipping target\n", __func__); - continue; - } - } - - if (tcon->ipc) { - scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$", - (int)share_len, share); - rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc); - } else { - scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, - share); - rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc); - if (!rc) { - rc = update_super_prepath(tcon, prefix, - prefix_len); - break; - } - } - if (rc == -EREMOTE) - break; - } - - if (!rc) { - if (it) - rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, - it); - else - rc = -ENOENT; - } - dfs_cache_free_tgts(&tl); -out: - kfree(tree); - return rc; -} -#else -static inline int __cifs_reconnect_tcon(const struct nls_table *nlsc, - struct cifs_tcon *tcon) -{ - return CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc); -} -#endif - /* reconnect the socket, tcon, and smb session if needed */ static int cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) @@ -338,7 +228,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) } cifs_mark_open_files_invalid(tcon); - rc = __cifs_reconnect_tcon(nls_codepage, tcon); + rc = cifs_tree_connect(0, tcon, nls_codepage); mutex_unlock(&ses->session_mutex); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index fe4b55977dd8..727d676c7fb9 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5532,3 +5532,103 @@ cifs_prune_tlinks(struct work_struct *work) queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, TLINK_IDLE_EXPIRE); } + +#ifdef CONFIG_CIFS_DFS_UPCALL +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +{ + int rc; + struct TCP_Server_Info *server = tcon->ses->server; + const struct smb_version_operations *ops = server->ops; + struct dfs_cache_tgt_list tl; + struct dfs_cache_tgt_iterator *it = NULL; + char *tree; + const char *tcp_host; + size_t tcp_host_len; + const char *dfs_host; + size_t dfs_host_len; + + tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); + if (!tree) + return -ENOMEM; + + if (!tcon->dfs_path) { + if (tcon->ipc) { + scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); + rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + } else { + rc = ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc); + } + goto out; + } + + rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); + if (rc) + goto out; + + extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); + + for (it = dfs_cache_get_tgt_iterator(&tl); it; it = dfs_cache_get_next_tgt(&tl, it)) { + const char *share, *prefix; + size_t share_len, prefix_len; + bool target_match; + + rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix, &prefix_len); + if (rc) { + cifs_dbg(VFS, "%s: failed to parse target share %d\n", + __func__, rc); + continue; + } + + extract_unc_hostname(share, &dfs_host, &dfs_host_len); + + if (dfs_host_len != tcp_host_len + || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) { + cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len, + dfs_host, (int)tcp_host_len, tcp_host); + + rc = match_target_ip(server, dfs_host, dfs_host_len, &target_match); + if (rc) { + cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc); + break; + } + + if (!target_match) { + cifs_dbg(FYI, "%s: skipping target\n", __func__); + continue; + } + } + + if (tcon->ipc) { + scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$", (int)share_len, share); + rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + } else { + scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share); + rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + if (!rc) { + rc = update_super_prepath(tcon, prefix, prefix_len); + break; + } + } + if (rc == -EREMOTE) + break; + } + + if (!rc) { + if (it) + rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, it); + else + rc = -ENOENT; + } + dfs_cache_free_tgts(&tl); +out: + kfree(tree); + return rc; +} +#else +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +{ + const struct smb_version_operations *ops = tcon->ses->server->ops; + + return ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc); +} +#endif diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 492688764004..24c2ac360591 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -152,117 +152,6 @@ out: return; } -#ifdef CONFIG_CIFS_DFS_UPCALL -static int __smb2_reconnect(const struct nls_table *nlsc, - struct cifs_tcon *tcon) -{ - int rc; - struct TCP_Server_Info *server = tcon->ses->server; - struct dfs_cache_tgt_list tl; - struct dfs_cache_tgt_iterator *it = NULL; - char *tree; - const char *tcp_host; - size_t tcp_host_len; - const char *dfs_host; - size_t dfs_host_len; - - tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); - if (!tree) - return -ENOMEM; - - if (!tcon->dfs_path) { - if (tcon->ipc) { - scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", - server->hostname); - rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc); - } else { - rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, - nlsc); - } - goto out; - } - - rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); - if (rc) - goto out; - - extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); - - for (it = dfs_cache_get_tgt_iterator(&tl); it; - it = dfs_cache_get_next_tgt(&tl, it)) { - const char *share, *prefix; - size_t share_len, prefix_len; - bool target_match; - - rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix, - &prefix_len); - if (rc) { - cifs_dbg(VFS, "%s: failed to parse target share %d\n", - __func__, rc); - continue; - } - - extract_unc_hostname(share, &dfs_host, &dfs_host_len); - - if (dfs_host_len != tcp_host_len - || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) { - cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", - __func__, - (int)dfs_host_len, dfs_host, - (int)tcp_host_len, tcp_host); - - rc = match_target_ip(server, dfs_host, dfs_host_len, - &target_match); - if (rc) { - cifs_dbg(VFS, "%s: failed to match target ip: %d\n", - __func__, rc); - break; - } - - if (!target_match) { - cifs_dbg(FYI, "%s: skipping target\n", __func__); - continue; - } - } - - if (tcon->ipc) { - scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$", - (int)share_len, share); - rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc); - } else { - scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, - share); - rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc); - if (!rc) { - rc = update_super_prepath(tcon, prefix, - prefix_len); - break; - } - } - if (rc == -EREMOTE) - break; - } - - if (!rc) { - if (it) - rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, - it); - else - rc = -ENOENT; - } - dfs_cache_free_tgts(&tl); -out: - kfree(tree); - return rc; -} -#else -static inline int __smb2_reconnect(const struct nls_table *nlsc, - struct cifs_tcon *tcon) -{ - return SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nlsc); -} -#endif - static int smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, struct TCP_Server_Info *server) @@ -409,7 +298,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, if (tcon->use_persistent) tcon->need_reopen_files = true; - rc = __smb2_reconnect(nls_codepage, tcon); + rc = cifs_tree_connect(0, tcon, nls_codepage); mutex_unlock(&tcon->ses->session_mutex); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); From 2e5de42445217c7c1310af8f4b3fe6f66bd865b1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:39 -0300 Subject: [PATCH 10/16] cifs: reduce number of referral requests in DFS link lookups When looking up the DFS cache with a referral path that has more than two path components, and is a complete prefix of an existing cache entry, do not request another referral and just return the matched entry as specified in MS-DFSC 3.2.5.5 Receiving a Root Referral Request or Link Referral Request. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/dfs_cache.c | 79 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index df81c718d2fa..dae2f41e4f21 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -490,16 +490,7 @@ static int add_cache_entry(const char *path, unsigned int hash, return 0; } -/* - * Find a DFS cache entry in hash table and optionally check prefix path against - * @path. - * Use whole path components in the match. - * Must be called with htable_rw_lock held. - * - * Return ERR_PTR(-ENOENT) if the entry is not found. - */ -static struct cache_entry *lookup_cache_entry(const char *path, - unsigned int *hash) +static struct cache_entry *__lookup_cache_entry(const char *path) { struct cache_entry *ce; unsigned int h; @@ -517,9 +508,75 @@ static struct cache_entry *lookup_cache_entry(const char *path, if (!found) ce = ERR_PTR(-ENOENT); + return ce; +} + +/* + * Find a DFS cache entry in hash table and optionally check prefix path against + * @path. + * Use whole path components in the match. + * Must be called with htable_rw_lock held. + * + * Return ERR_PTR(-ENOENT) if the entry is not found. + */ +static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *hash) +{ + struct cache_entry *ce = ERR_PTR(-ENOENT); + unsigned int h; + int cnt = 0; + char *npath; + char *s, *e; + char sep; + + npath = kstrndup(path, strlen(path), GFP_KERNEL); + if (!npath) + return ERR_PTR(-ENOMEM); + + s = npath; + sep = *npath; + while ((s = strchr(s, sep)) && ++cnt < 3) + s++; + + if (cnt < 3) { + h = cache_entry_hash(path, strlen(path)); + ce = __lookup_cache_entry(path); + goto out; + } + /* + * Handle paths that have more than two path components and are a complete prefix of the DFS + * referral request path (@path). + * + * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request". + */ + h = cache_entry_hash(npath, strlen(npath)); + e = npath + strlen(npath) - 1; + while (e > s) { + char tmp; + + /* skip separators */ + while (e > s && *e == sep) + e--; + if (e == s) + goto out; + + tmp = *(e+1); + *(e+1) = 0; + + ce = __lookup_cache_entry(npath); + if (!IS_ERR(ce)) { + h = cache_entry_hash(npath, strlen(npath)); + break; + } + + *(e+1) = tmp; + /* backward until separator */ + while (e > s && *e != sep) + e--; + } +out: if (hash) *hash = h; - + kfree(npath); return ce; } From 7d397a034d5c45528c7bdf7fc3752c4793029cce Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:40 -0300 Subject: [PATCH 11/16] cifs: rename reconn_inval_dfs_target() This function has nothing to do with *invalidation* but setting up the next target server from a cached referral. Rename it to reconn_set_next_dfs_target(). While at it, get rid of some meaningless checks. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 727d676c7fb9..21a4685894fa 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -393,15 +393,14 @@ static inline int reconn_set_ipaddr(struct TCP_Server_Info *server) #ifdef CONFIG_CIFS_DFS_UPCALL /* These functions must be called with server->srv_mutex held */ -static void reconn_inval_dfs_target(struct TCP_Server_Info *server, - struct cifs_sb_info *cifs_sb, - struct dfs_cache_tgt_list *tgt_list, - struct dfs_cache_tgt_iterator **tgt_it) +static void reconn_set_next_dfs_target(struct TCP_Server_Info *server, + struct cifs_sb_info *cifs_sb, + struct dfs_cache_tgt_list *tgt_list, + struct dfs_cache_tgt_iterator **tgt_it) { const char *name; - if (!cifs_sb || !cifs_sb->origin_fullpath || !tgt_list || - !server->nr_targets) + if (!cifs_sb || !cifs_sb->origin_fullpath) return; if (!*tgt_it) { @@ -578,7 +577,7 @@ cifs_reconnect(struct TCP_Server_Info *server) * feature is disabled, then we will retry last server we * connected to before. */ - reconn_inval_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); + reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); #endif rc = reconn_set_ipaddr(server); if (rc) { From a52930353eaf443489a350a135c5525a4acbbf56 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:41 -0300 Subject: [PATCH 12/16] cifs: handle empty list of targets in cifs_reconnect() In case there were no cached DFS referrals in reconn_setup_dfs_targets(), set cifs_sb to NULL prior to calling reconn_set_next_dfs_target() so it would not try to access an empty tgt_list. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 21a4685894fa..78e6460d1e49 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -470,11 +470,13 @@ cifs_reconnect(struct TCP_Server_Info *server) sb = NULL; } else { cifs_sb = CIFS_SB(sb); - rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list); - if (rc && (rc != -EOPNOTSUPP)) { - cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n", - __func__); + if (rc) { + cifs_sb = NULL; + if (rc != -EOPNOTSUPP) { + cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n", + __func__); + } } else { server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list); } From 7548e1da8d2d345debb9c0f141c47e4077d6085b Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:42 -0300 Subject: [PATCH 13/16] cifs: handle RESP_GET_DFS_REFERRAL.PathConsumed in reconnect Use PathConsumed field when parsing prefixes of referral paths that either match a cache entry or are a complete prefix path of an existing entry. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/cifsproto.h | 3 +-- fs/cifs/connect.c | 17 +++++++++----- fs/cifs/dfs_cache.c | 57 ++++++++++++++++++++++++++++++++++----------- fs/cifs/dfs_cache.h | 7 +++--- fs/cifs/misc.c | 7 +++--- 5 files changed, 62 insertions(+), 29 deletions(-) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index c2ab937a7048..bb68cbf81074 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -617,8 +617,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov, struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server); void cifs_put_tcp_super(struct super_block *sb); -int update_super_prepath(struct cifs_tcon *tcon, const char *prefix, - size_t prefix_len); +int update_super_prepath(struct cifs_tcon *tcon, char *prefix); #ifdef CONFIG_CIFS_DFS_UPCALL static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 78e6460d1e49..48b5133580d5 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5547,6 +5547,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru size_t tcp_host_len; const char *dfs_host; size_t dfs_host_len; + char *share = NULL, *prefix = NULL; tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); if (!tree) @@ -5569,11 +5570,12 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); for (it = dfs_cache_get_tgt_iterator(&tl); it; it = dfs_cache_get_next_tgt(&tl, it)) { - const char *share, *prefix; - size_t share_len, prefix_len; bool target_match; - rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix, &prefix_len); + kfree(share); + kfree(prefix); + + rc = dfs_cache_get_tgt_share(tcon->dfs_path + 1, it, &share, &prefix); if (rc) { cifs_dbg(VFS, "%s: failed to parse target share %d\n", __func__, rc); @@ -5600,13 +5602,13 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru } if (tcon->ipc) { - scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$", (int)share_len, share); + scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", share); rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); } else { - scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share); + scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); if (!rc) { - rc = update_super_prepath(tcon, prefix, prefix_len); + rc = update_super_prepath(tcon, prefix); break; } } @@ -5614,6 +5616,9 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru break; } + kfree(share); + kfree(prefix); + if (!rc) { if (it) rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, it); diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index dae2f41e4f21..a44f58bbf7ab 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -29,6 +29,7 @@ struct cache_dfs_tgt { char *name; + int path_consumed; struct list_head list; }; @@ -350,7 +351,7 @@ static inline struct timespec64 get_expire_time(int ttl) } /* Allocate a new DFS target */ -static struct cache_dfs_tgt *alloc_target(const char *name) +static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed) { struct cache_dfs_tgt *t; @@ -362,6 +363,7 @@ static struct cache_dfs_tgt *alloc_target(const char *name) kfree(t); return ERR_PTR(-ENOMEM); } + t->path_consumed = path_consumed; INIT_LIST_HEAD(&t->list); return t; } @@ -384,7 +386,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, for (i = 0; i < numrefs; i++) { struct cache_dfs_tgt *t; - t = alloc_target(refs[i].node_name); + t = alloc_target(refs[i].node_name, refs[i].path_consumed); if (IS_ERR(t)) { free_tgts(ce); return PTR_ERR(t); @@ -830,6 +832,7 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) rc = -ENOMEM; goto err_free_it; } + it->it_path_consumed = t->path_consumed; if (ce->tgthint == t) list_add(&it->it_list, head); @@ -1320,23 +1323,26 @@ void dfs_cache_del_vol(const char *fullpath) /** * dfs_cache_get_tgt_share - parse a DFS target * + * @path: DFS full path * @it: DFS target iterator. * @share: tree name. - * @share_len: length of tree name. * @prefix: prefix path. - * @prefix_len: length of prefix path. * * Return zero if target was parsed correctly, otherwise non-zero. */ -int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, - const char **share, size_t *share_len, - const char **prefix, size_t *prefix_len) +int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, + char **share, char **prefix) { - char *s, sep; + char *s, sep, *p; + size_t len; + size_t plen1, plen2; - if (!it || !share || !share_len || !prefix || !prefix_len) + if (!it || !path || !share || !prefix || strlen(path) < it->it_path_consumed) return -EINVAL; + *share = NULL; + *prefix = NULL; + sep = it->it_name[0]; if (sep != '\\' && sep != '/') return -EINVAL; @@ -1345,13 +1351,38 @@ int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, if (!s) return -EINVAL; + /* point to prefix in target node */ s = strchrnul(s + 1, sep); - *share = it->it_name; - *share_len = s - it->it_name; - *prefix = *s ? s + 1 : s; - *prefix_len = &it->it_name[strlen(it->it_name)] - *prefix; + /* extract target share */ + *share = kstrndup(it->it_name, s - it->it_name, GFP_KERNEL); + if (!*share) + return -ENOMEM; + /* skip separator */ + if (*s) + s++; + /* point to prefix in DFS path */ + p = path + it->it_path_consumed; + if (*p == sep) + p++; + + /* merge prefix paths from DFS path and target node */ + plen1 = it->it_name + strlen(it->it_name) - s; + plen2 = path + strlen(path) - p; + if (plen1 || plen2) { + len = plen1 + plen2 + 2; + *prefix = kmalloc(len, GFP_KERNEL); + if (!*prefix) { + kfree(*share); + *share = NULL; + return -ENOMEM; + } + if (plen1) + scnprintf(*prefix, len, "%.*s%c%.*s", (int)plen1, s, sep, (int)plen2, p); + else + strscpy(*prefix, p, len); + } return 0; } diff --git a/fs/cifs/dfs_cache.h b/fs/cifs/dfs_cache.h index bf94d08cfb5a..3d7c05194536 100644 --- a/fs/cifs/dfs_cache.h +++ b/fs/cifs/dfs_cache.h @@ -19,6 +19,7 @@ struct dfs_cache_tgt_list { struct dfs_cache_tgt_iterator { char *it_name; + int it_path_consumed; struct list_head it_list; }; @@ -48,10 +49,8 @@ extern int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, extern int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server); extern void dfs_cache_del_vol(const char *fullpath); - -extern int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, - const char **share, size_t *share_len, - const char **prefix, size_t *prefix_len); +extern int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, + char **share, char **prefix); static inline struct dfs_cache_tgt_iterator * dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl, diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index e44d049142d0..764234803071 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -1164,8 +1164,7 @@ static inline void cifs_put_tcon_super(struct super_block *sb) } #endif -int update_super_prepath(struct cifs_tcon *tcon, const char *prefix, - size_t prefix_len) +int update_super_prepath(struct cifs_tcon *tcon, char *prefix) { struct super_block *sb; struct cifs_sb_info *cifs_sb; @@ -1179,8 +1178,8 @@ int update_super_prepath(struct cifs_tcon *tcon, const char *prefix, kfree(cifs_sb->prepath); - if (*prefix && prefix_len) { - cifs_sb->prepath = kstrndup(prefix, prefix_len, GFP_ATOMIC); + if (prefix && *prefix) { + cifs_sb->prepath = kstrndup(prefix, strlen(prefix), GFP_ATOMIC); if (!cifs_sb->prepath) { rc = -ENOMEM; goto out; From c6a80e1ff4106755b8e72a88e767deb7c9d45050 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 31 Jul 2020 18:13:42 +0100 Subject: [PATCH 14/16] cifs: fix double free error on share and prefix Currently if the call dfs_cache_get_tgt_share fails we cannot fully guarantee that share and prefix are set to NULL and the next iteration of the loop can end up potentially double freeing these pointers. Since the semantics of dfs_cache_get_tgt_share are ambiguous for failure cases with the setting of share and prefix (currently now and the possibly the future), it seems prudent to set the pointers to NULL when the objects are free'd to avoid any double frees. Addresses-Coverity: ("Double free") Fixes: 96296c946a2a ("cifs: handle RESP_GET_DFS_REFERRAL.PathConsumed in reconnect") Signed-off-by: Colin Ian King Signed-off-by: Steve French Reviewed-by: Paulo Alcantara (SUSE) --- fs/cifs/connect.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 48b5133580d5..aae90953e07f 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5574,6 +5574,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru kfree(share); kfree(prefix); + share = NULL; + prefix = NULL; rc = dfs_cache_get_tgt_share(tcon->dfs_path + 1, it, &share, &prefix); if (rc) { From 11375a59a99ca05086a1837c27d79e92dd8d4aed Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:43 -0300 Subject: [PATCH 15/16] cifs: only update prefix path of DFS links in cifs_tree_connect() For DFS root mounts that contain a prefix path, do not change them after failover. E.g., if the user mounts //srvA/root/dir1 and then lost connection to srvA, it will reconnect to //srvB/root/dir1 In case of DFS links, which may resolve to different prefix paths depending on their list of targets, the following must be supported: - mount //srvA/root/link/bar - connect to //srvA/share - set prefix path to "bar" - lost connection to srvA - reconnect to next target: //srvB/share/foo - set new prefix path to "foo/bar" In cifs_tree_connect(), check the server_type field of the cached DFS referral to determine whether or not prefix path should be updated. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index aae90953e07f..637ba0066636 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5548,6 +5548,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru const char *dfs_host; size_t dfs_host_len; char *share = NULL, *prefix = NULL; + struct dfs_info3_param ref = {0}; + bool isroot; tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); if (!tree) @@ -5563,9 +5565,11 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru goto out; } - rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); + rc = dfs_cache_noreq_find(tcon->dfs_path + 1, &ref, &tl); if (rc) goto out; + isroot = ref.server_type == DFS_TYPE_ROOT; + free_dfs_info_param(&ref); extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); @@ -5609,7 +5613,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru } else { scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); - if (!rc) { + /* Only handle prefix paths of DFS link targets */ + if (!rc && !isroot) { rc = update_super_prepath(tcon, prefix); break; } From 7efd081582619e7c270d1c0a422385dcaa99fa9f Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:44 -0300 Subject: [PATCH 16/16] cifs: document and cleanup dfs mount cifs_mount() for DFS mounts is for a long time way too complex to follow, mostly because it lacks some documentation, does a lot of operations like resolving DFS roots and links, checking for path components, perform failover, crap code, etc. Besides adding some documentation to it, do some cleanup and ensure that the following is implemented and supported: * non-DFS mounts * DFS failover * DFS root mounts - tcon and cifs_sb must contain DFS path (NOT including prefix) - if prefix path, then save it in cifs_sb and it must not be changed * DFS link mounts - tcon and cifs_sb must contain DFS path (including prefix) - if prefix path, then save it in cifs_sb and it may be changed * prevent recursion on broken link referrals (MAX_NESTED_LINKS) * check every path component of the currently resolved target (including prefix), and chase them accordingly * make sure that DFS referrals go through newly resolved root servers Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 370 +++++++++++++++++++++++----------------------- 1 file changed, 182 insertions(+), 188 deletions(-) diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 637ba0066636..7e3e5e2098eb 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4423,11 +4423,11 @@ build_unc_path_to_root(const struct smb_vol *vol, static int expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb, - int check_prefix) + char *ref_path) { int rc; struct dfs_info3_param referral = {0}; - char *full_path = NULL, *ref_path = NULL, *mdata = NULL; + char *full_path = NULL, *mdata = NULL; if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) return -EREMOTE; @@ -4436,9 +4436,6 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, if (IS_ERR(full_path)) return PTR_ERR(full_path); - /* For DFS paths, skip the first '\' of the UNC */ - ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1; - rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), ref_path, &referral, NULL); if (!rc) { @@ -4501,13 +4498,10 @@ static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it, return 0; } -static int setup_dfs_tgt_conn(const char *path, +static int setup_dfs_tgt_conn(const char *path, const char *full_path, const struct dfs_cache_tgt_iterator *tgt_it, - struct cifs_sb_info *cifs_sb, - struct smb_vol *vol, - unsigned int *xid, - struct TCP_Server_Info **server, - struct cifs_ses **ses, + struct cifs_sb_info *cifs_sb, struct smb_vol *vol, unsigned int *xid, + struct TCP_Server_Info **server, struct cifs_ses **ses, struct cifs_tcon **tcon) { int rc; @@ -4521,8 +4515,7 @@ static int setup_dfs_tgt_conn(const char *path, if (rc) return rc; - mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref, - &fake_devname); + mdata = cifs_compose_mount_options(cifs_sb->mountdata, full_path + 1, &ref, &fake_devname); free_dfs_info_param(&ref); if (IS_ERR(mdata)) { @@ -4545,7 +4538,7 @@ static int setup_dfs_tgt_conn(const char *path, mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon); rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses, tcon); - if (!rc) { + if (!rc || (*server && *ses)) { /* * We were able to connect to new target server. * Update current volume info with new target server. @@ -4557,14 +4550,10 @@ static int setup_dfs_tgt_conn(const char *path, return rc; } -static int mount_do_dfs_failover(const char *path, - struct cifs_sb_info *cifs_sb, - struct smb_vol *vol, - struct cifs_ses *root_ses, - unsigned int *xid, - struct TCP_Server_Info **server, - struct cifs_ses **ses, - struct cifs_tcon **tcon) +static int do_dfs_failover(const char *path, const char *full_path, struct cifs_sb_info *cifs_sb, + struct smb_vol *vol, struct cifs_ses *root_ses, unsigned int *xid, + struct TCP_Server_Info **server, struct cifs_ses **ses, + struct cifs_tcon **tcon) { int rc; struct dfs_cache_tgt_list tgt_list; @@ -4583,9 +4572,9 @@ static int mount_do_dfs_failover(const char *path, if (rc) break; /* Connect to next DFS target */ - rc = setup_dfs_tgt_conn(path, tgt_it, cifs_sb, vol, xid, server, - ses, tcon); - if (!rc || rc == -EACCES || rc == -EOPNOTSUPP) + rc = setup_dfs_tgt_conn(path, full_path, tgt_it, cifs_sb, vol, xid, server, ses, + tcon); + if (!rc || (*server && *ses)) break; } if (!rc) { @@ -4755,207 +4744,210 @@ static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol, } #ifdef CONFIG_CIFS_DFS_UPCALL -static inline void set_root_tcon(struct cifs_sb_info *cifs_sb, - struct cifs_tcon *tcon, - struct cifs_tcon **root) +static void set_root_ses(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, + struct cifs_ses **root_ses) { - spin_lock(&cifs_tcp_ses_lock); - tcon->tc_count++; - tcon->remap = cifs_remap(cifs_sb); - spin_unlock(&cifs_tcp_ses_lock); - *root = tcon; + if (ses) { + spin_lock(&cifs_tcp_ses_lock); + ses->ses_count++; + ses->tcon_ipc->remap = cifs_remap(cifs_sb); + spin_unlock(&cifs_tcp_ses_lock); + } + *root_ses = ses; +} + +static void put_root_ses(struct cifs_ses *ses) +{ + if (ses) + cifs_put_smb_ses(ses); +} + +/* Check if a path component is remote and then update @dfs_path accordingly */ +static int check_dfs_prepath(struct cifs_sb_info *cifs_sb, struct smb_vol *vol, + const unsigned int xid, struct TCP_Server_Info *server, + struct cifs_tcon *tcon, char **dfs_path) +{ + char *path, *s; + char sep = CIFS_DIR_SEP(cifs_sb), tmp; + char *npath; + int rc = 0; + int added_treename = tcon->Flags & SMB_SHARE_IS_IN_DFS; + int skip = added_treename; + + path = cifs_build_path_to_root(vol, cifs_sb, tcon, added_treename); + if (!path) + return -ENOMEM; + + /* + * Walk through the path components in @path and check if they're accessible. In case any of + * the components is -EREMOTE, then update @dfs_path with the next DFS referral request path + * (NOT including the remaining components). + */ + s = path; + do { + /* skip separators */ + while (*s && *s == sep) + s++; + if (!*s) + break; + /* next separator */ + while (*s && *s != sep) + s++; + /* + * if the treename is added, we then have to skip the first + * part within the separators + */ + if (skip) { + skip = 0; + continue; + } + tmp = *s; + *s = 0; + rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, path); + if (rc && rc == -EREMOTE) { + struct smb_vol v = {NULL}; + /* if @path contains a tree name, skip it in the prefix path */ + if (added_treename) { + rc = cifs_parse_devname(path, &v); + if (rc) + break; + rc = -EREMOTE; + npath = build_unc_path_to_root(&v, cifs_sb, true); + cifs_cleanup_volume_info_contents(&v); + } else { + v.UNC = vol->UNC; + v.prepath = path + 1; + npath = build_unc_path_to_root(&v, cifs_sb, true); + } + if (IS_ERR(npath)) { + rc = PTR_ERR(npath); + break; + } + kfree(*dfs_path); + *dfs_path = npath; + } + *s = tmp; + } while (rc == 0); + + kfree(path); + return rc; } int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) { int rc = 0; unsigned int xid; - struct cifs_ses *ses; - struct cifs_tcon *root_tcon = NULL; + struct TCP_Server_Info *server = NULL; + struct cifs_ses *ses = NULL, *root_ses = NULL; struct cifs_tcon *tcon = NULL; - struct TCP_Server_Info *server; - char *root_path = NULL, *full_path = NULL; - char *old_mountdata, *origin_mountdata = NULL; - int count; + int count = 0; + char *ref_path = NULL, *full_path = NULL; + char *oldmnt = NULL; + char *mntdata = NULL; rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); - if (!rc && tcon) { - /* If not a standalone DFS root, then check if path is remote */ - rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, - cifs_remap(cifs_sb), vol->UNC + 1, NULL, - NULL); - if (rc) { - rc = is_path_remote(cifs_sb, vol, xid, server, tcon); - if (!rc) - goto out; - if (rc != -EREMOTE) - goto error; - } - } /* - * If first DFS target server went offline and we failed to connect it, - * server and ses pointers are NULL at this point, though we still have - * chance to get a cached DFS referral in expand_dfs_referral() and - * retry next target available in it. + * Unconditionally try to get an DFS referral (even cached) to determine whether it is an + * DFS mount. * - * If a NULL ses ptr is passed to dfs_cache_find(), a lookup will be - * performed against DFS path and *no* requests will be sent to server - * for any new DFS referrals. Hence it's safe to skip checking whether - * server or ses ptr is NULL. + * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem + * to respond with PATH_NOT_COVERED to requests that include the prefix. */ - if (rc == -EACCES || rc == -EOPNOTSUPP) - goto error; - - root_path = build_unc_path_to_root(vol, cifs_sb, false); - if (IS_ERR(root_path)) { - rc = PTR_ERR(root_path); - root_path = NULL; - goto error; + if (dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), vol->UNC + 1, NULL, + NULL)) { + /* No DFS referral was returned. Looks like a regular share. */ + if (rc) + goto error; + /* Check if it is fully accessible and then mount it */ + rc = is_path_remote(cifs_sb, vol, xid, server, tcon); + if (!rc) + goto out; + if (rc != -EREMOTE) + goto error; } - - full_path = build_unc_path_to_root(vol, cifs_sb, true); - if (IS_ERR(full_path)) { - rc = PTR_ERR(full_path); - full_path = NULL; - goto error; - } - /* - * Perform an unconditional check for whether there are DFS - * referrals for this path without prefix, to provide support - * for DFS referrals from w2k8 servers which don't seem to respond - * with PATH_NOT_COVERED to requests that include the prefix. - * Chase the referral if found, otherwise continue normally. - */ - old_mountdata = cifs_sb->mountdata; - (void)expand_dfs_referral(xid, ses, vol, cifs_sb, false); - - if (cifs_sb->mountdata == NULL) { - rc = -ENOENT; - goto error; - } - - /* Save DFS root volume information for DFS refresh worker */ - origin_mountdata = kstrndup(cifs_sb->mountdata, - strlen(cifs_sb->mountdata), GFP_KERNEL); - if (!origin_mountdata) { + /* Save mount options */ + mntdata = kstrndup(cifs_sb->mountdata, strlen(cifs_sb->mountdata), GFP_KERNEL); + if (!mntdata) { rc = -ENOMEM; goto error; } - - if (cifs_sb->mountdata != old_mountdata) { - /* If we were redirected, reconnect to new target server */ - mount_put_conns(cifs_sb, xid, server, ses, tcon); - rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); - } - if (rc) { - if (rc == -EACCES || rc == -EOPNOTSUPP) - goto error; - /* Perform DFS failover to any other DFS targets */ - rc = mount_do_dfs_failover(root_path + 1, cifs_sb, vol, NULL, - &xid, &server, &ses, &tcon); - if (rc) - goto error; - } - - kfree(root_path); - root_path = build_unc_path_to_root(vol, cifs_sb, false); - if (IS_ERR(root_path)) { - rc = PTR_ERR(root_path); - root_path = NULL; + /* Get path of DFS root */ + ref_path = build_unc_path_to_root(vol, cifs_sb, false); + if (IS_ERR(ref_path)) { + rc = PTR_ERR(ref_path); + ref_path = NULL; goto error; } - /* Cache out resolved root server */ - (void)dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), - root_path + 1, NULL, NULL); - kfree(root_path); - root_path = NULL; - - set_root_tcon(cifs_sb, tcon, &root_tcon); - - for (count = 1; ;) { - if (!rc && tcon) { - rc = is_path_remote(cifs_sb, vol, xid, server, tcon); - if (!rc || rc != -EREMOTE) - break; - } - /* - * BB: when we implement proper loop detection, - * we will remove this check. But now we need it - * to prevent an indefinite loop if 'DFS tree' is - * misconfigured (i.e. has loops). - */ - if (count++ > MAX_NESTED_LINKS) { - rc = -ELOOP; - break; - } + set_root_ses(cifs_sb, ses, &root_ses); + do { + /* Save full path of last DFS path we used to resolve final target server */ kfree(full_path); - full_path = build_unc_path_to_root(vol, cifs_sb, true); + full_path = build_unc_path_to_root(vol, cifs_sb, !!count); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); - full_path = NULL; break; } - - old_mountdata = cifs_sb->mountdata; - rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb, - true); + /* Chase referral */ + oldmnt = cifs_sb->mountdata; + rc = expand_dfs_referral(xid, root_ses, vol, cifs_sb, ref_path + 1); if (rc) break; - - if (cifs_sb->mountdata != old_mountdata) { + /* Connect to new DFS target only if we were redirected */ + if (oldmnt != cifs_sb->mountdata) { mount_put_conns(cifs_sb, xid, server, ses, tcon); - rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, - &tcon); - /* - * Ensure that DFS referrals go through new root server. - */ - if (!rc && tcon && - (tcon->share_flags & (SHI1005_FLAGS_DFS | - SHI1005_FLAGS_DFS_ROOT))) { - cifs_put_tcon(root_tcon); - set_root_tcon(cifs_sb, tcon, &root_tcon); - } + rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); } - if (rc) { - if (rc == -EACCES || rc == -EOPNOTSUPP) - break; - /* Perform DFS failover to any other DFS targets */ - rc = mount_do_dfs_failover(full_path + 1, cifs_sb, vol, - root_tcon->ses, &xid, - &server, &ses, &tcon); - if (rc == -EACCES || rc == -EOPNOTSUPP || !server || - !ses) - goto error; + if (rc && !server && !ses) { + /* Failed to connect. Try to connect to other targets in the referral. */ + rc = do_dfs_failover(ref_path + 1, full_path, cifs_sb, vol, root_ses, &xid, + &server, &ses, &tcon); } - } - cifs_put_tcon(root_tcon); + if (rc == -EACCES || rc == -EOPNOTSUPP || !server || !ses) + break; + if (!tcon) + continue; + /* Make sure that requests go through new root servers */ + if (tcon->share_flags & (SHI1005_FLAGS_DFS | SHI1005_FLAGS_DFS_ROOT)) { + put_root_ses(root_ses); + set_root_ses(cifs_sb, ses, &root_ses); + } + /* Check for remaining path components and then continue chasing them (-EREMOTE) */ + rc = check_dfs_prepath(cifs_sb, vol, xid, server, tcon, &ref_path); + /* Prevent recursion on broken link referrals */ + if (rc == -EREMOTE && ++count > MAX_NESTED_LINKS) + rc = -ELOOP; + } while (rc == -EREMOTE); if (rc) goto error; - - spin_lock(&cifs_tcp_ses_lock); - if (!tcon->dfs_path) { - /* Save full path in new tcon to do failover when reconnecting tcons */ - tcon->dfs_path = full_path; - full_path = NULL; - tcon->remap = cifs_remap(cifs_sb); - } - cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path, - strlen(tcon->dfs_path), - GFP_ATOMIC); + put_root_ses(root_ses); + root_ses = NULL; + kfree(ref_path); + ref_path = NULL; + /* + * Store DFS full path in both superblock and tree connect structures. + * + * For DFS root mounts, the prefix path (cifs_sb->prepath) is preserved during reconnect so + * only the root path is set in cifs_sb->origin_fullpath and tcon->dfs_path. And for DFS + * links, the prefix path is included in both and may be changed during reconnect. See + * cifs_tree_connect(). + */ + cifs_sb->origin_fullpath = kstrndup(full_path, strlen(full_path), GFP_KERNEL); if (!cifs_sb->origin_fullpath) { - spin_unlock(&cifs_tcp_ses_lock); rc = -ENOMEM; goto error; } + spin_lock(&cifs_tcp_ses_lock); + tcon->dfs_path = full_path; + full_path = NULL; + tcon->remap = cifs_remap(cifs_sb); spin_unlock(&cifs_tcp_ses_lock); - rc = dfs_cache_add_vol(origin_mountdata, vol, cifs_sb->origin_fullpath); - if (rc) { - kfree(cifs_sb->origin_fullpath); + /* Add original volume information for DFS cache to be used when refreshing referrals */ + rc = dfs_cache_add_vol(mntdata, vol, cifs_sb->origin_fullpath); + if (rc) goto error; - } /* * After reconnecting to a different server, unique ids won't * match anymore, so we disable serverino. This prevents @@ -4977,9 +4969,11 @@ out: return mount_setup_tlink(cifs_sb, ses, tcon); error: + kfree(ref_path); kfree(full_path); - kfree(root_path); - kfree(origin_mountdata); + kfree(mntdata); + kfree(cifs_sb->origin_fullpath); + put_root_ses(root_ses); mount_put_conns(cifs_sb, xid, server, ses, tcon); return rc; }